Reverse proxy adalah server yang berdiri di depan app: dia menerima request dari internet, meneruskannya ke app di belakangnya, lalu mengembalikan jawaban app ke pengirimnya. Bayangin pelayan restoran. Tamu gak masuk ke dapur; mereka pesan ke pelayan, pelayan mengantar pesanannya ke dapur, lalu membawa makanannya kembali. Dapurnya gak kelihatan dari luar. Kalau dapurnya tutup, pelayan yang bilang "maaf, dapur tutup". Dan kalau kokinya dua, pelayan membagi pesanan bergiliran.
🧪 Tebakan cepat: nginx meneruskan request ke catatan-api cuma dengan
proxy_pass http://127.0.0.1:3000;, tanpa satu punproxy_set_header. Budi membuka situsnya dari IP198.51.100.23. Apa isireq.ipdi Express? Tebak dulu, jawabannya ada di bagian "Header yang hilang di jalan".
Nginx di depan catatan-api
Di pelajaran Nginx Basics, nginx di web-1 melayani file statis. Sekarang location / diteruskan ke app Express catatan-api, yang mendengarkan di 127.0.0.1:3000:
server {
listen 80;
listen [::]:80;
server_name catatan.example.com www.catatan.example.com;
location ^~ /lampiran/ {
alias /srv/catatan-api/storage/lampiran/;
}
location / {
proxy_pass http://127.0.0.1:3000;
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_pass http://127.0.0.1:3000;meneruskan request ke app.127.0.0.1(localhost) artinya mesin itu sendiri, jadi request-nya gak keluar dari web-1./lampiran/tetap dilayani nginx langsung dari disk, lebih cepat daripada lewat Node.- Empat
proxy_set_headermenitipkan info tentang request aslinya. Gunanya di bagian berikutnya. - Di app,
app.listen(3000, "127.0.0.1")mengunci port 3000 ke localhost. Orang di internet cuma bisa masuk lewat nginx, gak bisa lewathttp://203.0.113.10:3000.
Keuntungan reverse proxy: SSL termination (nginx yang memegang HTTPS, app tetap HTTP polos; pelajaran berikutnya), load balancing ke beberapa app, file statis dan caching tanpa membebani app, dan lapisan keamanan: app gak pernah tersentuh langsung dari internet.
Header yang hilang di jalan
Dari sudut pandang Express, yang tersambung ke app bukan browser budi, tapi nginx, dari 127.0.0.1. Tanpa proxy_set_header, nginx cuma mengirim Host: 127.0.0.1:3000 (alamat di proxy_pass), jadi Express melihat:
| Config | req.ip |
req.hostname |
req.protocol |
|---|---|---|---|
proxy_pass saja |
127.0.0.1 |
127.0.0.1 |
http |
+ empat proxy_set_header |
127.0.0.1 |
catatan.example.com |
http |
+ header + trust proxy |
198.51.100.23 |
catatan.example.com |
dari X-Forwarded-Proto |
Baris pertama: req.ip berisi 127.0.0.1. Itu jawaban tebakannya. Semua user terlihat datang dari IP yang sama, jadi rate limit per IP dan log jadi gak berguna.
Host $hostmeneruskan nama domain yang diketik user, jadi link dan redirect yang dibuat app benar.X-Forwarded-For $proxy_add_x_forwarded_formenambahkan IP pengirim ($remote_addr) ke ujung daftarX-Forwarded-Foryang sudah ada.X-Real-IPberisi IP itu saja.X-Forwarded-Proto $schemememberi tahu app apakah user datang lewathttpatauhttps. Begitu HTTPS dipasang di nginx, app tetap menerima HTTP polos, jadi tanpa header ini app mengira semua requesthttpdan bisa salah membuat redirect atau cookieSecure.
Header saja belum cukup. Express gak percaya X-Forwarded-For begitu saja, karena siapa pun bisa mengirim header itu. Kamu harus bilang siapa proxy yang dipercaya:
const app = express();
app.set("trust proxy", "loopback"); // percaya proxy di 127.0.0.1, yaitu nginx
app.get("/health", (req, res) => {
res.json({ status: "ok", port: 3000 });
});
app.listen(3000, "127.0.0.1");
Dengan "loopback", Express membaca X-Forwarded-For dari kanan dan melewati alamat yang dipercaya, jadi req.ip berisi 198.51.100.23, dan req.protocol diambil dari X-Forwarded-Proto.
Kalau app-nya mati: 502
sudo systemctl stop catatan-api, lalu curl dari laptop:
$ curl -i http://catatan.example.com/health
HTTP/1.1 502 Bad Gateway
Server: nginx/1.24.0 (Ubuntu)
Date: Fri, 09 Oct 2026 03:20:11 GMT
Content-Type: text/html
Content-Length: 166
Connection: keep-alive
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.24.0 (Ubuntu)</center>
</body>
</html>
502 Bad Gateway artinya nginx-nya hidup, tapi app di belakangnya gak menjawab. Penyebabnya tertulis di /var/log/nginx/error.log:
2026/10/09 10:20:11 [error] 2841#2841: *57 connect() failed (111: Connection refused) while connecting to upstream, client: 198.51.100.23, server: catatan.example.com, request: "GET /health HTTP/1.1", upstream: "http://127.0.0.1:3000/health", host: "catatan.example.com"
Connection refused berarti gak ada yang mendengarkan di port 3000. Kalau app-nya hidup tapi terlalu lambat (lebih dari 60 detik, proxy_read_timeout bawaan), nginx menjawab 504 Gateway Timeout.
Load balancing: upstream dan round robin
Satu proses Node cuma memakai satu core CPU. catatan-api dijalankan dua kali, di port 3000 dan 3001, dan nginx membagi request ke keduanya lewat upstream:
upstream catatan_api {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
# di dalam location /
proxy_pass http://catatan_api;
Cara bawaannya round robin: request ke-0 ke server pertama, ke-1 ke server kedua, lalu kembali ke awal. Rumusnya servers[i % servers.length]:
function roundRobin(servers, requestCount) {
const result = [];
for (let i = 0; i < requestCount; i++) {
result.push(servers[i % servers.length]);
}
return result;
}
console.log(roundRobin(["A", "B", "C"], 5)); // [ 'A', 'B', 'C', 'A', 'B' ]
i % servers.lengthadalah sisa bagi: dengan 3 server hasilnya berputar 0, 1, 2, 0, 1.- Dengan satu server (
["X"]), semua request keX. - Kalau satu instance mati, nginx mencoba server berikutnya untuk request yang sama, dan melewati yang mati selama 10 detik (
fail_timeout). User tetap dapat jawaban. Baru kalau semua mati, keluar 502. - Pilihan lain:
weight=3(server itu dapat 3 kali lebih banyak),least_conn(ke yang koneksinya paling sedikit),ip_hash(IP yang sama selalu ke server yang sama).
⚠️ Jebakan yang sering ketemu
trust proxydiisitrue. Express lalu mengambil IP paling kiri diX-Forwarded-For, yang bisa dikarang client. Pakai"loopback"atau IP proxy-mu.- Lupa
proxy_set_header Host $host. App melihat127.0.0.1:3000, atau nama upstream seperticatatan_api, jadi redirect dan link absolut yang dibuatnya salah. - App mendengarkan di semua alamat.
app.listen(3000)bisa dibuka langsung dihttp://203.0.113.10:3000, melewati nginx. Kunci ke127.0.0.1. - Garis miring di
proxy_pass.location /api/ { proxy_pass http://127.0.0.1:3000/; }membuang/api/sebelum diteruskan, sedangkan tanpa/di akhir path-nya diteruskan utuh.
TL;DR: Reverse proxy (nginx) menerima request dari client dan meneruskannya ke app dengan proxy_pass http://127.0.0.1:3000;, memberi SSL termination, load balancing, caching, dan lapisan keamanan. Teruskan info aslinya dengan proxy_set_header Host $host, X-Real-IP, X-Forwarded-For $proxy_add_x_forwarded_for, dan X-Forwarded-Proto $scheme, lalu set app.set("trust proxy", "loopback") supaya req.ip berisi IP asli, bukan 127.0.0.1. App yang mati menghasilkan 502 Bad Gateway dengan connect() failed (111: Connection refused) di error log. upstream membagi request bergiliran (round robin, servers[i % servers.length]) dan melewati instance yang mati.