NGINX
Referensi konfigurasi NGINX yang komprehensif: server block, location, reverse proxy, SSL/TLS, load balancing, rate limiting, security headers, logging, dan performance tuning.
Instalasi & Manajemen Service
Install, cek versi, dan kelola NGINX dengan systemctl.
Install
# Debian / Ubuntu apt update && apt install -y nginx # Tambah repo nginx.org untuk versi stable terbaru curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor -o /etc/apt/keyrings/nginx.gpg echo "deb [signed-by=/etc/apt/keyrings/nginx.gpg] http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" > /etc/apt/sources.list.d/nginx.list apt update && apt install -y nginx
# RHEL / CentOS / Rocky / Alma dnf install -y nginx # Atau dari repo nginx.org cat > /etc/yum.repos.d/nginx.repo << 'EOF' [nginx-stable] name=nginx stable repo baseurl=http://nginx.org/packages/centos/$releasever/$basearch/ gpgcheck=1 enabled=1 gpgkey=https://nginx.org/keys/nginx_signing.key EOF dnf install -y nginx
Versi dan build info
nginx -v # versi singkat nginx -V # versi + compile flags + module yang terinstall
Manajemen service
systemctl enable --now nginx # aktifkan dan jalankan sekarang systemctl status nginx systemctl start nginx systemctl stop nginx systemctl restart nginx # stop + start (downtime singkat) systemctl reload nginx # reload config tanpa downtime (graceful)
Test config dan signal
nginx -t sebelum reload. Config yang salah akan ditolak tanpa mematikan service yang berjalan.nginx -t # test konfigurasi — wajib sebelum reload nginx -T # test + dump seluruh config yang sudah di-parse nginx -s reload # kirim signal HUP (graceful reload) nginx -s reopen # buka ulang log file (setelah logrotate) nginx -s quit # graceful shutdown (tunggu request selesai) nginx -s stop # immediate shutdown
Struktur direktori default
/etc/nginx/nginx.conf # file konfigurasi utama /etc/nginx/conf.d/ # config tambahan (di-include oleh nginx.conf) /etc/nginx/sites-available/ # server block tersedia (Debian/Ubuntu) /etc/nginx/sites-enabled/ # symlink ke sites-available yang aktif /var/log/nginx/access.log # access log /var/log/nginx/error.log # error log /var/www/html/ # document root default /usr/share/nginx/html/ # document root (RHEL/Alpine) /run/nginx.pid # PID file
Enable site (Debian/Ubuntu pattern)
# Buat config di sites-available, lalu buat symlink ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/myapp nginx -t && systemctl reload nginx # Disable rm /etc/nginx/sites-enabled/myapp nginx -t && systemctl reload nginx
Struktur Konfigurasi
Hirarki konteks dan anatomi nginx.conf.
Hirarki konteks
http berlaku untuk semua server; direktif di server berlaku untuk semua location di dalamnya — kecuali di-override.# main context — global settings
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events { # event processing
worker_connections 1024;
}
http { # HTTP settings (shared oleh semua server)
include mime.types;
sendfile on;
keepalive_timeout 65;
server { # virtual host
listen 80;
server_name example.com;
location / { # request routing
root /var/www/html;
}
}
}nginx.conf minimal — production
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
charset utf-8;
server_tokens off; # sembunyikan versi NGINX di response header
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
# Gzip
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml image/svg+xml;
# Logging
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
access_log /var/log/nginx/access.log main;
include /etc/nginx/conf.d/*.conf;
}Include pattern
include /etc/nginx/mime.types; include /etc/nginx/conf.d/*.conf; # semua file .conf di direktori include /etc/nginx/sites-enabled/*; # pattern tanpa ekstensi
Server Block (Virtual Host)
Definisi virtual host untuk melayani domain dan port berbeda.
HTTP server block dasar
server {
listen 80;
listen [::]:80; # IPv6
server_name example.com www.example.com;
root /var/www/example.com;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}HTTPS server block
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}Redirect HTTP → HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}Redirect www → non-www (atau sebaliknya)
# www → non-www
server {
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
return 301 https://example.com$request_uri;
}
# non-www → www
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
return 301 https://www.example.com$request_uri;
}Default server — catch-all
default_server untuk menangkap request dengan Host header yang tidak cocok dengan server block manapun. Tanpa ini, NGINX menggunakan server block pertama sebagai default.server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _; # _ = match semua nama yang tidak cocok
return 444; # tutup koneksi tanpa response
}
# Atau tampilkan halaman 403:
server {
listen 80 default_server;
server_name _;
return 403;
}Multiple domain dan wildcard
server {
server_name example.com www.example.com; # multiple nama
server_name *.example.com; # wildcard subdomain
server_name example.com ~^wwwd+.; # campuran nama dan regex
}Location Block
Routing request berdasarkan URI. Urutan prioritas penting — baca catatan.
Modifier dan prioritas
= (exact match) → 2. ^~ (prefix, stop regex check) → 3. ~ / ~* (regex, pertama yang cocok) → 4. prefix terpanjang tanpa modifier.location = /favicon.ico { } # exact match — prioritas tertinggi
location ^~ /images/ { } # prefix, stop cek regex jika cocok
location ~ \.php$ { } # regex case-sensitive
location ~* \.png$ { } # regex case-insensitive
location /api/ { } # prefix match biasa
location / { } # fallback — cocok dengan semua URIroot vs alias
root: path final = root + URI. alias: path final = alias saja (URI-prefix diganti). Gunakan alias saat direktori tidak mencerminkan struktur URI.# root — URI /images/cat.png → /var/www/html/images/cat.png
location /images/ {
root /var/www/html;
}
# alias — URI /images/cat.png → /data/pics/cat.png
location /images/ {
alias /data/pics/; # trailing slash wajib pada alias
}try_files
try_files mencoba path secara berurutan. Argumen terakhir adalah fallback — bisa URI, named location, atau kode status.# Coba file, lalu direktori, lalu 404
location / {
try_files $uri $uri/ =404;
}
# SPA (React/Vue/Angular) — semua route ke index.html
location / {
try_files $uri $uri/ /index.html;
}
# PHP — coba file statis, lalu pass ke PHP-FPM
location / {
try_files $uri $uri/ @php;
}
location @php {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}Named location
# Named location dimulai dengan @ — hanya bisa di-referensi internal
location / {
try_files $uri $uri/ @backend;
}
location @backend {
proxy_pass http://127.0.0.1:3000;
include /etc/nginx/proxy_params;
}Nested location
location /api/ {
proxy_pass http://127.0.0.1:3000;
location /api/public/ {
proxy_pass http://127.0.0.1:3000;
limit_req zone=public_api burst=50;
}
location /api/admin/ {
allow 10.0.0.0/8;
deny all;
proxy_pass http://127.0.0.1:3000;
}
}Reverse Proxy
Konfigurasi NGINX sebagai reverse proxy ke aplikasi backend.
proxy_pass dasar
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000; # backend URL
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection ""; # hapus untuk HTTP/1.1 keepalive
}
}proxy_params — file reusable
/etc/nginx/proxy_params dan include di setiap location. Lebih DRY.# /etc/nginx/proxy_params proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header Connection ""; # Gunakan di location: # include /etc/nginx/proxy_params;
Proxy timeout dan buffering
location / {
proxy_pass http://127.0.0.1:3000;
include /etc/nginx/proxy_params;
# Timeout
proxy_connect_timeout 10s; # batas waktu koneksi ke backend
proxy_send_timeout 60s; # batas waktu kirim request ke backend
proxy_read_timeout 60s; # batas waktu tunggu response backend
# Buffering — aktifkan untuk throughput lebih baik
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
# Matikan buffering untuk SSE / streaming
# proxy_buffering off;
}WebSocket proxy
Upgrade dan Connection. Naikkan proxy_read_timeout karena koneksi WebSocket berumur panjang.location /ws/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s; # 1 jam — WS perlu koneksi panjang
proxy_send_timeout 3600s;
}Proxy untuk backend HTTPS
location / {
proxy_pass https://backend.internal:8443;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
proxy_ssl_session_reuse on;
}Error dari backend — intercept
location / {
proxy_pass http://127.0.0.1:3000;
proxy_intercept_errors on; # tangkap error dari backend
error_page 502 503 504 /50x.html;
}
location = /50x.html {
root /usr/share/nginx/html;
internal; # hanya bisa diakses secara internal
}Sub-path proxy — strip prefix
proxy_pass berisi URI (ada path setelah port), NGINX otomatis mengganti prefix location dengan URI tersebut.# Request /api/users → backend menerima /users
location /api/ {
proxy_pass http://127.0.0.1:3000/; # trailing slash = strip /api/
}
# Request /api/users → backend menerima /api/users (tidak strip)
location /api/ {
proxy_pass http://127.0.0.1:3000; # tanpa trailing slash
}SSL / TLS
Konfigurasi HTTPS yang aman: protokol, cipher, sertifikat, dan Let's Encrypt.
SSL modern — TLSv1.2 + TLSv1.3
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:
ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:
ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off; # TLSv1.3 memilih cipher sendiriSession cache dan DH params
ssl_session_cache shared:SSL:10m; # cache 10MB antar worker ssl_session_timeout 1d; ssl_session_tickets off; # nonaktifkan untuk forward secrecy # Generate DH params (jalankan sekali): # openssl dhparam -out /etc/nginx/dhparam.pem 2048 ssl_dhparam /etc/nginx/dhparam.pem;
OCSP Stapling
ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem; resolver 1.1.1.1 8.8.8.8 valid=300s; resolver_timeout 5s;
HSTS — HTTP Strict Transport Security
preload mendaftarkan domain ke browser preload list — tidak bisa dibatalkan dengan mudah. Pastikan HTTPS sudah stabil sebelum mengaktifkan preload.add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
Konfigurasi SSL lengkap per server
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_dhparam /etc/nginx/dhparam.pem;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}Let's Encrypt — Certbot
# Install certbot apt install -y certbot python3-certbot-nginx # Issue sertifikat + auto-configure NGINX certbot --nginx -d example.com -d www.example.com # Issue sertifikat saja (tanpa ubah config) certbot certonly --nginx -d example.com # Wildcard (butuh DNS challenge) certbot certonly --manual --preferred-challenges dns -d "*.example.com" -d example.com # Renew manual certbot renew --dry-run # test renew certbot renew # renew semua sertifikat yang hampir expired # Cek tanggal expiry certbot certificates
Self-signed certificate (dev/internal)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/self.key -out /etc/nginx/ssl/self.crt -subj "/CN=localhost" # Atau dengan SAN (Subject Alternative Names) openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/dev.key -out /etc/nginx/ssl/dev.crt -subj "/CN=dev.local" -addext "subjectAltName=DNS:dev.local,DNS:*.dev.local,IP:127.0.0.1"
Mutual TLS (mTLS) — autentikasi client
server {
ssl_client_certificate /etc/nginx/ssl/ca.crt; # CA yang menandatangani client cert
ssl_verify_client on; # wajib, tolak jika tidak ada cert
# ssl_verify_client optional; # opsional, cek $ssl_client_verify
location /api/ {
# Hanya izinkan jika client cert valid
if ($ssl_client_verify != SUCCESS) { return 403; }
proxy_pass http://127.0.0.1:3000;
}
}File Statis & Cache
Serve file statis secara efisien dengan cache header yang tepat.
Serve static files
server {
listen 80;
server_name static.example.com;
root /var/www/static;
location / {
try_files $uri $uri/ =404;
autoindex off; # matikan directory listing
}
# Asset dengan hash di nama file — cache agresif
location ~* \.(?:js|css|woff2|woff|ttf)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Gambar
location ~* \.(?:jpg|jpeg|png|gif|ico|svg|webp|avif)$ {
expires 30d;
add_header Cache-Control "public, must-revalidate";
access_log off;
}
# HTML — jangan di-cache
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-store";
}
}Gzip compression
# Di konteks http {}
gzip on;
gzip_vary on; # tambahkan Vary: Accept-Encoding
gzip_proxied any; # compress untuk semua proxied request
gzip_comp_level 6; # 1 (cepat) – 9 (kecil); 6 = sweet spot
gzip_min_length 1024; # tidak compress file < 1KB
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
application/rss+xml
image/svg+xml
font/woff2;
# image/png, image/jpeg tidak perlu (sudah terkompresi)Open file cache
open() dan stat() untuk file yang sering diakses.open_file_cache max=10000 inactive=30s; open_file_cache_valid 60s; open_file_cache_min_uses 2; open_file_cache_errors on;
Proxy cache — cache response backend
# Di konteks http {}
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=app_cache:10m # nama zone dan ukuran key store (10MB)
max_size=1g # batas total cache di disk
inactive=60m # hapus jika tidak diakses 60 menit
use_temp_path=off;
# Di location {}
location / {
proxy_pass http://127.0.0.1:3000;
proxy_cache app_cache;
proxy_cache_valid 200 302 10m; # cache 200/302 selama 10 menit
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status; # debug: HIT/MISS/BYPASS
}Load Balancing
Distribusi traffic ke multiple backend server.
Upstream block dan algoritma
upstream app_backend {
# round-robin (default, tidak perlu direktif)
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000;
}
upstream app_least_conn {
least_conn; # pilih server dengan koneksi aktif paling sedikit
server 10.0.0.1:3000;
server 10.0.0.2:3000;
}
upstream app_sticky {
ip_hash; # sticky session berdasarkan IP client
server 10.0.0.1:3000;
server 10.0.0.2:3000;
}
upstream app_hash {
hash $request_uri consistent; # sticky berdasarkan URI
server 10.0.0.1:3000;
server 10.0.0.2:3000;
}Weight, backup, dan down
upstream app_backend {
server 10.0.0.1:3000 weight=3; # terima 3x lebih banyak traffic
server 10.0.0.2:3000 weight=1;
server 10.0.0.3:3000 backup; # hanya aktif jika server lain down
server 10.0.0.4:3000 down; # tandai sebagai offline sementara
}Health check pasif
max_fails dan fail_timeout.upstream app_backend {
server 10.0.0.1:3000 max_fails=3 fail_timeout=30s;
# max_fails=3 : tandai down setelah 3 kali gagal dalam fail_timeout
# fail_timeout : durasi menandai down DAN jendela waktu hitung kegagalan
server 10.0.0.2:3000 max_fails=3 fail_timeout=30s;
}Keepalive connection ke upstream
upstream app_backend {
server 10.0.0.1:3000;
server 10.0.0.2:3000;
keepalive 32; # pool 32 koneksi idle per worker
}
# Di location, tambahkan:
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection ""; # wajib untuk keepalive upstream
}Proxy ke Unix socket
upstream php_fpm {
server unix:/run/php/php8.2-fpm.sock;
}
upstream node_app {
server unix:/run/node/app.sock;
}Security Headers
Header HTTP yang meningkatkan keamanan browser.
Security headers esensial
http {} untuk semua site, atau di server {} / location {} untuk spesifik. Keyword always memastikan header dikirim bahkan pada response error.# Sembunyikan versi NGINX server_tokens off; # Cegah clickjacking add_header X-Frame-Options "SAMEORIGIN" always; # Cegah MIME-type sniffing add_header X-Content-Type-Options "nosniff" always; # XSS protection (browser lama) add_header X-XSS-Protection "1; mode=block" always; # Referrer policy add_header Referrer-Policy "strict-origin-when-cross-origin" always; # HSTS add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # Permissions Policy (batasi API browser) add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
Content Security Policy (CSP)
Content-Security-Policy-Report-Only untuk monitoring, baru switch ke enforcement.# Mode report-only dulu (tidak blokir, hanya lapor) add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; report-uri /csp-report" always; # Mode enforcement add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
Sembunyikan informasi server
server_tokens off; # hapus versi NGINX dari header Server dan error page # Header Server masih menampilkan "nginx" — untuk menyembunyikan sepenuhnya # butuh modul ngx_http_headers_more (tersedia di nginx-extras di Debian): # more_clear_headers Server; # more_set_headers "Server: webserver";
Rate Limiting & Access Control
Batasi request rate, koneksi bersamaan, dan akses berdasarkan IP.
limit_req — rate limiting request
rate=10r/s artinya 10 request per detik. burst mengizinkan lonjakan singkat. nodelay: lonjakan diproses sekarang, tidak antri.# Di konteks http {}
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;
limit_req_status 429; # kode HTTP saat di-limit (default 503)
# Di location {}
location /api/ {
limit_req zone=req_limit burst=20 nodelay;
}
location /auth/login {
limit_req zone=login_limit burst=3 nodelay;
}limit_conn — batasi koneksi bersamaan
# Di konteks http {}
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
limit_conn_status 429;
# Di location {}
location /download/ {
limit_conn conn_limit 5; # maks 5 koneksi bersamaan per IP
limit_rate 500k; # throttle bandwidth per koneksi (500 KB/s)
limit_rate_after 5m; # mulai throttle setelah 5MB pertama
}Allow / Deny — IP whitelist/blacklist
# Whitelist — hanya izinkan IP/range tertentu
location /admin/ {
allow 10.0.0.0/8; # jaringan internal
allow 192.168.1.0/24; # LAN
allow 203.0.113.5; # IP spesifik
deny all; # blokir semua yang lain
}
# Blacklist — blokir IP tertentu
location / {
deny 198.51.100.0/24;
deny 203.0.113.99;
allow all;
}geo — klasifikasi berdasarkan IP
# Di konteks http {}
geo $is_internal {
default 0;
10.0.0.0/8 1;
172.16.0.0/12 1;
192.168.0.0/16 1;
127.0.0.1 1;
}
geo $country_block {
default 0;
# Isi dengan range IP sesuai geolocation database
}
# Di location {}
location / {
if ($is_internal = 0) {
limit_req zone=req_limit burst=10;
}
}map — transform variabel
# Di konteks http {}
map $http_user_agent $is_bot {
default 0;
~*bot 1;
~*crawler 1;
~*spider 1;
~*scrapy 1;
}
map $request_method $cors_method {
OPTIONS 1;
default 0;
}
# Di location {}
location / {
if ($is_bot) { return 403; }
}Basic Auth
# Buat file password (install apache2-utils dulu) htpasswd -c /etc/nginx/.htpasswd user1 htpasswd /etc/nginx/.htpasswd user2 # tambah user berikutnya
location /private/ {
auth_basic "Area Terbatas";
auth_basic_user_file /etc/nginx/.htpasswd;
}Blokir user agent dan request mencurigakan
# Di konteks server {}
# Blokir user agent kosong
if ($http_user_agent = "") { return 444; }
# Blokir metode yang tidak diperlukan
if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE|PATCH|OPTIONS)$) {
return 405;
}
# Blokir akses ke file tersembunyi (.git, .env, dll.)
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}Redirect & Rewrite
Atur ulang URL dengan return dan rewrite.
return — redirect sederhana
return lebih efisien dari rewrite karena NGINX tidak perlu memproses regex. Gunakan return untuk redirect dan rewrite hanya saat butuh transformasi URL.# Kode redirect return 301 https://example.com$request_uri; # permanent return 302 https://example.com/sale/; # temporary return 307 https://example.com$request_uri; # temporary, pertahankan method return 308 https://example.com$request_uri; # permanent, pertahankan method # Kode lain return 404; return 403; return 410; # Gone — resource dihapus permanen
rewrite — transformasi URL
# Syntax: rewrite regex replacement [flag]; # Flag: last (proses ulang), break (stop), redirect (302), permanent (301) # Hapus trailing slash (kecuali root) rewrite ^/(.+)/$ /$1 permanent; # Tambahkan trailing slash ke direktori rewrite ^/blog$ /blog/ permanent; # Ubah format URL rewrite ^/posts/(\d+)/(.*)$ /article?id=$1&slug=$2 last; # Versi lama ke baru rewrite ^/old-path/(.*)$ /new-path/$1 permanent;
Custom error pages
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
# Redirect ke URL eksternal
error_page 404 https://example.com/not-found;
# Error page dari backend
error_page 404 = @not_found;
location @not_found {
proxy_pass http://127.0.0.1:3000/error/404;
}
location = /404.html {
root /var/www/errors;
internal;
}Trailing slash normalization
# Paksa trailing slash untuk direktori
location /docs {
return 301 /docs/;
}
# Hapus trailing slash (kecuali root /)
location ~ ^(.+)/$ {
return 301 $1;
}Logging
Format log, conditional logging, dan manajemen file log.
Format log custom
# Di konteks http {}
# Format standar
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
# Format dengan request time dan upstream info
log_format detailed '$remote_addr - [$time_local] '
'"$request" $status $body_bytes_sent '
'$request_time $upstream_response_time '
'"$http_referer" "$http_user_agent" '
'"$http_x_forwarded_for"';
# Format JSON — untuk log aggregation (ELK, Loki, dll.)
log_format json_log escape=json
'{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"method":"$request_method",'
'"uri":"$uri",'
'"args":"$args",'
'"status":$status,'
'"bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_time":"$upstream_response_time",'
'"referrer":"$http_referer",'
'"user_agent":"$http_user_agent"'
'}';
access_log /var/log/nginx/access.log main;
access_log /var/log/nginx/json.log json_log;Error log levels
# Level: debug | info | notice | warn | error | crit | alert | emerg
error_log /var/log/nginx/error.log warn; # production
error_log /var/log/nginx/error.log debug; # troubleshooting
# Nonaktifkan log per-location
location /health {
access_log off;
return 200 "ok";
}Conditional logging — filter 2xx
# Di konteks http {}
map $status $loggable {
~^[23] 0; # jangan log 2xx dan 3xx
default 1; # log 4xx dan 5xx
}
# Di konteks server/location {}
access_log /var/log/nginx/error_only.log main if=$loggable;
# Jangan log health check
map $request_uri $log_health {
"~*/health$" 0;
default 1;
}
access_log /var/log/nginx/access.log main if=$log_health;Logrotate config
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
nginx -s reopen 2>/dev/null || true
endscript
}Performance Tuning
Optimasi worker, koneksi, buffer, dan I/O.
Worker dan koneksi
worker_processes auto menyesuaikan jumlah worker dengan jumlah CPU core. Total koneksi bersamaan = worker_processes × worker_connections.# Di konteks main
worker_processes auto; # satu worker per CPU core
worker_rlimit_nofile 65535; # naikkan batas open file descriptor
events {
worker_connections 4096; # maks koneksi per worker
use epoll; # I/O event model terbaik untuk Linux
multi_accept on; # terima semua koneksi baru sekaligus
}Keepalive dan timeout
http {
keepalive_timeout 65; # timeout idle keepalive connection (detik)
keepalive_requests 1000; # maks request per keepalive connection
keepalive_time 1h; # durasi maksimal keepalive connection
send_timeout 60s; # timeout antar dua operasi write
client_header_timeout 10s; # timeout baca request header
client_body_timeout 10s; # timeout baca request body
reset_timedout_connection on; # reset koneksi yang timeout (bebaskan memory)
}Sendfile dan buffer
http {
sendfile on; # transfer file langsung kernel-to-kernel (bypass userspace)
tcp_nopush on; # kirim header + awal body dalam satu TCP packet
tcp_nodelay on; # nonaktifkan Nagle algorithm (cocok untuk keepalive)
# Buffer ukuran request client
client_body_buffer_size 128k;
client_max_body_size 10m; # maks ukuran upload (default 1m)
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
# Buffer output
output_buffers 2 32k;
postpone_output 1460;
}Variabel yang berguna untuk debugging
$request_time # total waktu proses request (detik) $upstream_response_time # waktu response backend $upstream_addr # alamat backend yang digunakan $upstream_status # status code dari backend $upstream_cache_status # cache: HIT / MISS / BYPASS / EXPIRED $ssl_protocol # TLSv1.2 / TLSv1.3 $ssl_cipher # cipher yang dipakai $request_id # unique request ID (NGINX 1.11.0+) $connection # nomor koneksi $bytes_sent # total bytes dikirim ke client
Snippet Siap Pakai
Konfigurasi lengkap untuk use-case umum — copy dan sesuaikan.
Reverse proxy ke Node.js / Next.js
server {
listen 80;
server_name app.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_read_timeout 60s;
}
# Static assets dengan cache panjang
location /_next/static/ {
proxy_pass http://127.0.0.1:3000;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
location /favicon.ico {
proxy_pass http://127.0.0.1:3000;
access_log off;
}
}SPA (React/Vue/Angular) + API proxy
server {
listen 443 ssl http2;
server_name spa.example.com;
ssl_certificate /etc/letsencrypt/live/spa.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/spa.example.com/privkey.pem;
root /var/www/spa/dist;
index index.html;
# Gzip
gzip on;
gzip_types text/plain text/css application/javascript application/json;
# API — proxy ke backend
location /api/ {
proxy_pass http://127.0.0.1:8080/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
limit_req zone=req_limit burst=30 nodelay;
}
# Static assets dengan hash — cache agresif
location ~* \.[0-9a-f]{8}\.(js|css|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
}Load balancer multi-backend
upstream api_pool {
least_conn;
server 10.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 10.0.0.2:3000 max_fails=3 fail_timeout=30s;
server 10.0.0.3:3000 max_fails=3 fail_timeout=30s backup;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
location / {
proxy_pass http://api_pool;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
limit_req zone=api_limit burst=50 nodelay;
}
location /health {
access_log off;
return 200 "ok";
add_header Content-Type text/plain;
}
}