Reverse Proxy & Load Balancing — DevOps

Apa itu Reverse Proxy? Reverse proxy adalah server yang menerima request dari client dan meneruskannya ke backend server. Client tidak tahu server mana yang…

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 pun proxy_set_header. Budi membuka situsnya dari IP 198.51.100.23. Apa isi req.ip di 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;
    }
}

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.

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' ]

⚠️ Jebakan yang sering ketemu

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.

Yang akan kamu pelajari