proxy_pass #
proxy_pass adalah directive inti dari reverse proxy Nginx. Ia menentukan ke mana request harus diteruskan. Meskipun terlihat sederhana, ada satu detail — trailing slash — yang bisa menyebabkan bug halus yang sangat sulit di-debug. Artikel ini akan membedah semua aspeknya sampai tuntas.
Penggunaan Paling Dasar #
server {
listen 443 ssl;
server_name example.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Setiap request ke example.com akan diteruskan ke proses yang berjalan di port 3000. Ini yang paling sering kita butuhkan untuk mendeploy aplikasi Node.js, Python, atau framework backend lainnya.
Detail Kritis: Trailing Slash di proxy_pass #
Ini adalah sumber bug paling umum di konfigurasi reverse proxy. Perilaku proxy_pass berubah secara fundamental tergantung ada atau tidaknya URI (termasuk trailing slash) di nilai proxy_pass.
Aturan: Tanpa URI di proxy_pass #
location /api/ {
proxy_pass http://localhost:3000;
# Tidak ada URI setelah port — hanya host:port
}
Ketika proxy_pass tidak menyertakan URI, Nginx meneruskan URI request apa adanya ke backend. Tidak ada modifikasi path.
Request: GET /api/users/123
Backend menerima: GET /api/users/123
Aturan: Dengan URI di proxy_pass #
location /api/ {
proxy_pass http://localhost:3000/;
# Ada trailing slash — ini adalah URI
}
Ketika proxy_pass menyertakan URI (bahkan hanya /), Nginx menggantikan bagian yang cocok dengan location dengan URI yang ada di proxy_pass.
Request: GET /api/users/123
Nginx: cocokkan /api/ dari URI
sisa setelah /api/ = users/123
gabungkan dengan URI di proxy_pass: / + users/123 = /users/123
Backend menerima: GET /users/123
Perbandingan Lengkap dalam Satu Tabel #
| Konfigurasi | Request | Yang Diterima Backend |
|---|---|---|
proxy_pass http://backend di location /api/ | GET /api/users | GET /api/users |
proxy_pass http://backend/ di location /api/ | GET /api/users | GET /users |
proxy_pass http://backend/v2/ di location /api/ | GET /api/users | GET /v2/users |
proxy_pass http://backend/service di location /api/ | GET /api/users | GET /serviceusers ← BUG! |
proxy_pass http://backend/service/ di location /api/ | GET /api/users | GET /service/users |
Baris keempat adalah jebakan paling umum: jika URI di proxy_pass tidak diakhiri / tapi location-nya diakhiri /, path akan digabung tanpa pemisah.
flowchart TD
REQ["Request: GET /api/users/123"] --> Q{"Ada URI\ndi proxy_pass?"}
Q -- "Tidak\nproxy_pass http://backend" --> NOURI["Teruskan URI apa adanya\nBackend terima: /api/users/123"]
Q -- "Ya\nproxy_pass http://backend/" --> WITHURI["Ganti bagian location\ndengan URI di proxy_pass\nBackend terima: /users/123"]
Q -- "Ya dengan prefix\nproxy_pass http://backend/v2/" --> WPREFIX["Ganti bagian location\ndengan prefix baru\nBackend terima: /v2/users/123"]
style NOURI fill:none,stroke-width:2px
style WITHURI fill:none,stroke-width:2px
style WPREFIX fill:none,stroke-width:2pxKapan Menggunakan Masing-Masing #
# ─── Tanpa trailing slash: backend expect path lengkap ────────────────────────
# Gunakan ini saat backend dikode untuk menerima path /api/...
location /api/ {
proxy_pass http://localhost:3000;
# GET /api/users → backend: GET /api/users (sesuai route di backend)
}
# ─── Dengan trailing slash: strip prefix /api/ ────────────────────────────────
# Gunakan ini saat backend dikode tanpa prefix /api/ di route-nya
location /api/ {
proxy_pass http://localhost:3000/;
# GET /api/users → backend: GET /users (prefix /api/ dihapus)
}
# ─── Dengan path prefix: ubah prefix ──────────────────────────────────────────
# Gunakan ini untuk versioning atau path mapping
location /api/ {
proxy_pass http://localhost:3000/v2/;
# GET /api/users → backend: GET /v2/users
}
proxy_http_version: Wajib Diubah ke 1.1 #
Nginx secara default menggunakan HTTP/1.0 untuk koneksi ke backend. HTTP/1.0 tidak mendukung keepalive atau chunked transfer encoding. Selalu ubah ke 1.1 untuk aplikasi modern:
location / {
proxy_pass http://backend;
# Wajib untuk:
# - Keepalive connections ke backend
# - Chunked transfer encoding
# - WebSocket
proxy_http_version 1.1;
# Hapus header Connection dari klien agar tidak mengganggu keepalive
proxy_set_header Connection "";
}
Proxy ke Unix Socket #
Untuk performa lebih baik pada mesin yang sama, gunakan Unix socket daripada TCP localhost. Unix socket menghindari overhead TCP stack sepenuhnya — tidak ada handshake, tidak ada network stack, komunikasi langsung melalui kernel.
# ─── Aplikasi yang listen di Unix socket ─────────────────────────────────────
location / {
proxy_pass http://unix:/run/myapp/myapp.sock;
# Format: http://unix:/path/to/socket
}
# ─── Gunicorn (Python) via Unix socket ───────────────────────────────────────
location / {
proxy_pass http://unix:/run/gunicorn/gunicorn.sock;
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;
}
# ─── PHP-FPM via Unix socket (menggunakan fastcgi, bukan proxy) ──────────────
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
Benchmark umum: Unix socket bisa 10-30% lebih cepat dari TCP localhost untuk request kecil yang banyak (seperti API calls). Untuk file besar, perbedaannya tidak signifikan.
Membuat Aplikasi Listen di Unix Socket #
# Node.js: jalankan di Unix socket
node server.js --socket /run/myapp/myapp.sock
# Atau dalam kode:
const app = express();
app.listen('/run/myapp/myapp.sock');
# Gunicorn:
gunicorn --bind unix:/run/gunicorn/gunicorn.sock myapp:app
# Pastikan Nginx worker punya izin baca/tulis ke socket
chown nginx:nginx /run/myapp/myapp.sock
chmod 660 /run/myapp/myapp.sock
WebSocket: Upgrade Koneksi #
WebSocket memerlukan upgrade dari HTTP ke protokol WebSocket. Nginx perlu dikonfigurasi secara khusus untuk meneruskan Upgrade header:
http {
# Map untuk menentukan nilai Connection header
# Jika ada Upgrade header → Connection: upgrade
# Jika tidak ada → Connection: close
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
location /ws/ {
proxy_pass http://localhost:4000;
proxy_http_version 1.1;
# Dua header ini wajib untuk WebSocket
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# Header standar
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;
# Timeout panjang untuk koneksi WebSocket yang long-lived
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
}
Bagaimana WebSocket handshake bekerja melalui Nginx:
sequenceDiagram
participant B as Browser
participant N as Nginx
participant WS as WebSocket Server
B->>N: GET /ws/ HTTP/1.1\nUpgrade: websocket\nConnection: Upgrade
N->>WS: GET /ws/ HTTP/1.1\nUpgrade: websocket\nConnection: Upgrade
WS->>N: HTTP/1.1 101 Switching Protocols\nUpgrade: websocket\nConnection: Upgrade
N->>B: HTTP/1.1 101 Switching Protocols
Note over B,WS: Koneksi WebSocket terbuka — dua arah
B->>N: WebSocket Frame
N->>WS: WebSocket Frame
WS->>N: WebSocket Frame
N->>B: WebSocket FramePath Rewriting Sebelum Proxy #
Kadang kita perlu mengubah path request sebelum diteruskan ke backend. Ada beberapa cara:
Cara 1: Trailing Slash (Paling Sederhana) #
# /app/users → backend menerima /users (prefix /app/ dihapus)
location /app/ {
proxy_pass http://localhost:8080/;
}
Cara 2: rewrite dengan break #
# Hapus prefix /api/v1/ sebelum diteruskan
location /api/v1/ {
rewrite ^/api/v1/(.*) /$1 break;
# break: hentikan rewrite processing, teruskan dengan URI baru
proxy_pass http://localhost:3000;
# /api/v1/users → backend: /users
# /api/v1/products/123 → backend: /products/123
}
Cara 3: Regex location dengan capture group #
# Ubah /service/v2/endpoint ke /endpoint?version=2
location ~* ^/service/v(\d+)/(.+)$ {
proxy_pass http://backend/$2?version=$1;
# /service/v2/users → backend: /users?version=2
}
Cara 4: sub_filter untuk Konten Response #
Kadang backend mengembalikan URL absolut dalam response yang perlu diubah:
location /app/ {
proxy_pass http://internal-backend/;
# Ganti semua URL internal dengan URL publik dalam response HTML
sub_filter 'http://internal-backend' 'https://example.com/app';
sub_filter_once off; # Ganti semua kemunculan, bukan hanya pertama
sub_filter_types text/html text/javascript application/json;
}
Proxy ke Upstream Group #
Dalam praktik production, proxy_pass hampir selalu mengarah ke upstream block daripada alamat tunggal. Ini memungkinkan load balancing dan failover otomatis:
upstream app_backend {
# Round-robin secara default
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000;
# Pool keepalive connections
keepalive 32;
}
server {
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
Detail upstream dan load balancing dibahas secara mendalam di Section 06.
Template Konfigurasi Production-Ready #
Berikut template konfigurasi reverse proxy yang lengkap dan siap production untuk aplikasi Node.js/Python:
# /etc/nginx/conf.d/myapp.com.conf
# ─── HTTP → HTTPS Redirect ───────────────────────────────────────────────────
server {
listen 80;
server_name myapp.com www.myapp.com;
return 301 https://$host$request_uri;
}
# ─── HTTPS + Reverse Proxy ───────────────────────────────────────────────────
server {
listen 443 ssl;
http2 on;
server_name myapp.com www.myapp.com;
# SSL
ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;
# Keamanan
server_tokens off;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin" always;
# ─── Static files langsung dari Nginx (lebih cepat dari backend) ─────────
location /static/ {
alias /var/www/myapp/static/;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# ─── Favicon dan robots.txt ───────────────────────────────────────────────
location = /favicon.ico { alias /var/www/myapp/static/favicon.ico; access_log off; }
location = /robots.txt { alias /var/www/myapp/static/robots.txt; access_log off; }
# ─── Aplikasi utama → Node.js backend ────────────────────────────────────
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
# Headers agar backend tahu informasi klien asli
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;
# Keepalive
proxy_set_header Connection "";
# Timeout
proxy_connect_timeout 10s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
# Buffer
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 16 4k;
# Error handling
proxy_intercept_errors on;
error_page 502 503 504 /maintenance.html;
}
# ─── WebSocket ────────────────────────────────────────────────────────────
location /ws/ {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 3600s;
}
# ─── Halaman maintenance ──────────────────────────────────────────────────
location = /maintenance.html {
root /var/www/myapp;
internal;
}
# ─── Blokir akses sensitif ────────────────────────────────────────────────
location ~ /\. { deny all; }
}
Debugging proxy_pass #
# 1. Pastikan backend bisa diakses dari Nginx
curl -v http://localhost:3000/api/health
# 2. Cek apakah Nginx menerima dan meneruskan dengan benar
# Tambahkan header debug sementara
location /api/ {
proxy_pass http://localhost:3000;
add_header X-Proxy-By "nginx" always;
add_header X-Upstream-Addr $upstream_addr always; # IP backend yang dipilih
}
# 3. Lihat upstream response time di log
log_format proxy_log '$remote_addr - [$time_local] "$request" '
'$status $body_bytes_sent '
'$upstream_response_time ' # Waktu backend merespons
'$upstream_addr'; # Backend yang dipakai
# 4. Cek error log untuk pesan koneksi
tail -f /var/log/nginx/error.log | grep -E "connect|upstream"
# Error umum:
# "connect() failed (111: Connection refused)" → backend tidak berjalan
# "no live upstreams while connecting to upstream" → semua backend down
# "upstream timed out (110: Connection timed out)" → backend terlalu lambat
Proxy ke Beberapa Backend Berdasarkan Kondisi #
Dalam skenario yang lebih kompleks, kita bisa meneruskan request ke backend yang berbeda berdasarkan kondisi tertentu — seperti versi API, tipe perangkat, atau A/B testing:
http {
# A/B testing: 20% traffic ke versi baru
split_clients "${remote_addr}${uri}" $app_version {
20% "v2"; # 20% traffic ke versi baru
* "v1"; # 80% traffic ke versi lama
}
upstream app_v1 { server localhost:3000; }
upstream app_v2 { server localhost:3001; }
server {
location / {
proxy_pass http://app_$app_version;
# 20% request → http://app_v2 (localhost:3001)
# 80% request → http://app_v1 (localhost:3000)
add_header X-App-Version $app_version always;
}
}
}
# Routing berdasarkan custom header
map $http_x_api_version $upstream_name {
"v1" api_v1_backend;
"v2" api_v2_backend;
default api_v2_backend; # Default ke versi terbaru
}
upstream api_v1_backend { server localhost:3000; }
upstream api_v2_backend { server localhost:3001; }
server {
location /api/ {
proxy_pass http://$upstream_name;
proxy_http_version 1.1;
proxy_set_header Host $host;
# X-API-Version: v1 → diteruskan ke localhost:3000
# X-API-Version: v2 → diteruskan ke localhost:3001
}
}
Ringkasan #
- Tanpa URI di
proxy_pass: URI diteruskan apa adanya. Dengan URI (termasuk/): bagian location digantikan — ini perbedaan kritis yang sering menyebabkan bug.- Selalu gunakan
proxy_http_version 1.1danproxy_set_header Connection ""untuk keepalive dan fitur HTTP modern.- Unix socket (
http://unix:/path/to/sock) 10-30% lebih cepat dari TCP localhost untuk komunikasi antar proses di mesin yang sama.- WebSocket membutuhkan
UpgradedanConnectionheader, plus timeout yang panjang (proxy_read_timeout 3600s).- Gunakan
map $http_upgrade $connection_upgradeuntuk menentukan nilai Connection header secara kondisional — diperlukan untuk server yang melayani HTTP reguler dan WebSocket sekaligus.- Selalu sertakan
X-Real-IP,X-Forwarded-For, danX-Forwarded-Protoagar backend tahu informasi asli klien.
← Sebelumnya: Konsep Reverse Proxy Berikutnya: Proxy Headers →