Konsep Reverse Proxy

Konsep Reverse Proxy #

Sebelum masuk ke konfigurasi, penting untuk memahami apa yang sebenarnya terjadi ketika Nginx bekerja sebagai reverse proxy. Pemahaman konseptual ini akan membuat semua konfigurasi berikutnya — proxy_pass, header, buffer, cache — jauh lebih masuk akal dan memudahkan kita melakukan debugging ketika ada masalah.

Forward Proxy vs Reverse Proxy #

Kata “proxy” berarti perantara. Yang membedakan forward proxy dan reverse proxy adalah untuk siapa ia menjadi perantara.

Forward Proxy: Bekerja untuk Klien #

Forward proxy adalah perantara yang berdiri di sisi klien. Klien mengkonfigurasi browser atau aplikasinya untuk mengirim semua request melalui proxy. Server tujuan melihat IP proxy, bukan IP klien asli.

Klien → [Forward Proxy] → Internet → Server
         (klien tahu ada proxy)
         (server tidak tahu IP klien asli)

Kasus penggunaan: VPN perusahaan, filter konten, bypass geo-restriction, anonymization.

Reverse Proxy: Bekerja untuk Server #

Reverse proxy adalah perantara yang berdiri di sisi server. Klien mengirim request ke proxy tanpa tahu ada server lain di baliknya. Proxy meneruskan request ke server yang tepat dan mengembalikan responsnya.

Klien → Internet → [Nginx Reverse Proxy] → Server Aplikasi
                    (klien tidak tahu ada backend)
                    (backend tahu ada proxy)

Nginx sebagai reverse proxy terlihat seperti server akhir dari sudut pandang browser — browser hanya berkomunikasi dengan Nginx di port 443, tidak pernah langsung ke Node.js di port 3000 atau Python di port 8000 yang ada di belakangnya.

flowchart LR
    subgraph CLIENT["Sisi Klien"]
        B["Browser"]
    end
    subgraph NGINX["Reverse Proxy"]
        N["Nginx\nPort 443 HTTPS"]
    end
    subgraph BACKEND["Sisi Server — tidak terekspos ke internet"]
        A1["Node.js App\nPort 3000"]
        A2["Python API\nPort 8000"]
        A3["Static Files\nDisk"]
    end

    B -- "HTTPS request" --> N
    N -- "HTTP internal" --> A1
    N -- "HTTP internal" --> A2
    N -- "read file" --> A3

    style CLIENT fill:none,stroke-width:2px
    style NGINX fill:none,stroke-width:2px
    style BACKEND fill:none,stroke-width:2px

Mengapa Hampir Semua Aplikasi Production Menggunakan Reverse Proxy #

Menempatkan Nginx di depan server aplikasi sudah menjadi standar industri bukan tanpa alasan. Ada manfaat nyata yang tidak mudah didapat tanpanya:

1. SSL/TLS Termination #

Nginx menangani seluruh kompleksitas enkripsi/dekripsi HTTPS. Backend menerima HTTP biasa tanpa perlu mengurus sertifikat, TLS handshake, atau overhead kriptografi. Nginx sangat dioptimalkan untuk ini — menggunakan hardware acceleration jika tersedia, mengelola session cache, dan mendukung TLS 1.3 secara efisien.

Browser ──HTTPS──► Nginx ──HTTP──► Node.js
                   (Nginx urus TLS)  (tidak perlu urus TLS)

2. Serving File Statis yang Jauh Lebih Efisien #

Nginx menggunakan sendfile() — system call yang mentransfer file langsung dari disk ke network socket tanpa melewati user space. Framework aplikasi seperti Express.js atau Django jauh lebih lambat untuk ini karena data harus melewati runtime bahasa.

Nginx serving static:  Disk → Kernel → Network (zero-copy)
Node.js serving static: Disk → Kernel → Node.js (user space) → Kernel → Network

3. Melindungi Port Aplikasi #

Server aplikasi berjalan di port non-standar (3000, 8000, 8080) yang tidak perlu terekspos ke internet. Hanya port 80/443 Nginx yang membuka ke luar. Ini menyederhanakan firewall rules dan mengurangi attack surface.

4. Buffering untuk Koneksi Lambat #

Nginx menerima seluruh request dari klien yang lambat (slow client attack) secara penuh sebelum meneruskan ke backend. Tanpa ini, satu klien dengan koneksi lambat bisa menahan thread di server aplikasi selama beberapa detik — menghabiskan resource yang seharusnya melayani klien lain.

5. Satu Entry Point untuk Banyak Service #

Routing berbasis path memungkinkan banyak service berjalan di belakang satu domain:

example.com/           → React SPA (static files langsung dari Nginx)
example.com/api/       → Node.js REST API (port 3000)
example.com/admin/     → Python Django (port 8000)
example.com/ws/        → WebSocket server (port 4000)
example.com/files/     → Dedicated file storage service (port 9000)

Dari sudut pandang klien: satu domain, satu port, satu sertifikat SSL. Di baliknya bisa ada puluhan microservice berbeda.

6. Rate Limiting dan Access Control #

Nginx bisa membatasi request per IP, per endpoint, atau berdasarkan kondisi tertentu sebelum request menyentuh backend sama sekali. Ini jauh lebih efisien daripada melakukan rate limiting di level aplikasi.


Alur Request di Reverse Proxy: Dua Koneksi Terpisah #

Memahami detail alur ini sangat penting untuk debugging. Nginx membuat dua koneksi yang sepenuhnya terpisah: satu ke klien, satu ke backend.

sequenceDiagram
    participant B as Browser
    participant N as Nginx
    participant A as Node.js App

    Note over B,N: Koneksi 1 (TCP + TLS ke port 443)
    B->>N: GET /api/users HTTP/1.1\nHost: example.com\nCookie: session=abc123

    Note over N: Nginx cek konfigurasi:\nlocation /api/ → proxy_pass http://localhost:3000

    Note over N,A: Koneksi 2 (TCP ke port 3000, bisa keepalive)
    N->>A: GET /api/users HTTP/1.1\nHost: example.com\nX-Real-IP: 203.0.113.1\nX-Forwarded-For: 203.0.113.1\nX-Forwarded-Proto: https

    A->>N: HTTP/1.1 200 OK\nContent-Type: application/json\n[JSON body]

    Note over N: Nginx terima response,\nbuffer jika perlu,\npotentially cache

    N->>B: HTTP/2 200 OK\nContent-Type: application/json\n[JSON body]

    Note over N: Catat ke access log

Dua koneksi terpisah ini memiliki implikasi penting:

  • Dua set timeout — timeout ke klien dan timeout ke backend dikonfigurasi secara terpisah
  • Nginx bisa menggunakan protokol berbeda — HTTPS ke klien tapi HTTP ke backend, atau HTTP/2 ke klien tapi HTTP/1.1 ke backend
  • Nginx bisa memodifikasi request dan response sebelum diteruskan

Apa yang Nginx Lakukan terhadap Request #

Ketika meneruskan request ke backend, Nginx secara default melakukan beberapa modifikasi:

Yang Dihapus #

Header yang DIHAPUS secara default:
  Connection        — hop-by-hop header, tidak relevan ke next hop
  Keep-Alive        — hop-by-hop header
  Upgrade           — hop-by-hop header (perlu ditambahkan kembali untuk WebSocket)
  Header dengan nama yang mengandung underscore (misalnya: X-Custom_Header)

Yang Diubah #

Header yang DIUBAH:
  Host  →  diubah ke nama host backend (bukan domain asli dari klien)
           contoh: dari "example.com" menjadi "localhost" atau "10.0.0.1"

Yang Diteruskan Apa Adanya #

Semua header request lain diteruskan apa adanya, termasuk Cookie, Authorization, Content-Type, Accept, User-Agent, dll.

Implikasi untuk Backend #

Karena modifikasi di atas, backend secara default tidak tahu:

  • IP asli klien — yang terlihat adalah IP Nginx (127.0.0.1 atau IP internal)
  • Domain yang diakses — karena header Host diubah
  • Apakah koneksi asli HTTPS atau HTTP — karena Nginx terminasi TLS

Ini mengapa kita perlu menambahkan header secara eksplisit:

location /api/ {
    proxy_pass http://localhost:3000;

    # Informasi asli klien yang perlu disampaikan ke backend
    proxy_set_header Host               $host;               # domain asli
    proxy_set_header X-Real-IP          $remote_addr;        # IP asli klien
    proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;  # chain IP
    proxy_set_header X-Forwarded-Proto  $scheme;             # http atau https
    proxy_set_header X-Forwarded-Host   $host;
    proxy_set_header X-Forwarded-Port   $server_port;
}

Cara Backend Membaca Header X-Forwarded #

Karena Nginx mengirim informasi klien melalui header X-Forwarded-*, setiap framework aplikasi perlu dikonfigurasi untuk membaca header tersebut. Tanpa konfigurasi ini, framework akan tetap melihat IP Nginx sebagai IP klien.

Express.js (Node.js) #

// Percayai proxy yang berada di depan Express
app.set('trust proxy', 1);

// Setelah ini, req.ip akan berisi IP asli klien dari X-Real-IP
// req.protocol akan berisi 'https' jika X-Forwarded-Proto: https
console.log(req.ip);        // IP asli klien
console.log(req.protocol);  // 'https'

Django (Python) #

# settings.py
# Daftar IP proxy yang dipercaya
ALLOWED_HOSTS = ['example.com']
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# Django akan menggunakan HTTP_X_FORWARDED_FOR untuk request.META['REMOTE_ADDR']

Laravel (PHP) #

// app/Http/Middleware/TrustProxies.php
protected $proxies = '*';  // atau IP Nginx yang spesifik
protected $headers = Request::HEADER_X_FORWARDED_FOR |
                     Request::HEADER_X_FORWARDED_HOST |
                     Request::HEADER_X_FORWARDED_PORT |
                     Request::HEADER_X_FORWARDED_PROTO;

Arsitektur Multi-Layer: Nginx di Depan Nginx #

Di lingkungan yang lebih kompleks, kita bisa punya beberapa lapisan Nginx:

flowchart TD
    B["Browser"] --> CDN["CDN\nCloudflare / Akamai"]
    CDN --> LB["Nginx Load Balancer\nIP Publik\nTerminasi TLS"]
    LB --> N1["Nginx Instance 1\n(Web Server + Reverse Proxy)"]
    LB --> N2["Nginx Instance 2\n(Web Server + Reverse Proxy)"]
    N1 --> APP["App Servers\nNode.js / Python / PHP"]
    N2 --> APP

    style B fill:none,stroke-width:2px
    style CDN fill:none,stroke-width:2px
    style LB fill:none,stroke-width:2px
    style N1 fill:none,stroke-width:2px
    style N2 fill:none,stroke-width:2px
    style APP fill:none,stroke-width:2px

Dalam arsitektur multi-layer, masalah umum adalah X-Forwarded-For yang berisi chain IP — klien asli + beberapa IP proxy. Konfigurasi penting:

# Di Nginx layer dalam (yang langsung ke backend):
# Percayai X-Forwarded-For yang dikirim Nginx layer luar
set_real_ip_from 10.0.0.0/8;           # IP range Nginx load balancer
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;                  # Rekursif untuk menemukan IP asli

# Setelah set_real_ip_from, $remote_addr akan berisi IP klien asli
proxy_set_header X-Real-IP $remote_addr;

Apa yang Perlu Disiapkan Sebelum Mengkonfigurasi Reverse Proxy #

Sebelum mulai menulis konfigurasi proxy_pass, ada beberapa hal yang perlu kita pastikan:

# 1. Pastikan backend berjalan dan bisa diakses dari Nginx
curl http://localhost:3000/health
# Harus mendapat response, bukan "Connection refused"

# 2. Cek port yang digunakan backend
ss -tlnp | grep 3000   # Atau: netstat -tlnp | grep 3000

# 3. Pastikan Nginx bisa resolve nama host backend (jika pakai hostname)
nginx -t  # Validasi konfigurasi dulu

# 4. Perhatikan user yang menjalankan Nginx
ps aux | grep nginx | grep worker
# Biasanya: www-data (Ubuntu) atau nginx (CentOS)
# User ini yang melakukan koneksi ke backend

# 5. Jika backend di server lain, cek firewall
# Port backend harus bisa diakses dari IP server Nginx
telnet 10.0.0.2 3000

Perbedaan Nginx Reverse Proxy vs CDN #

Pertanyaan yang sering muncul: jika CDN (Cloudflare, Akamai, CloudFront) sudah berfungsi sebagai reverse proxy, mengapa masih perlu Nginx?

Jawaban singkat: keduanya komplementer, bukan alternatif.

AspekNginx Reverse ProxyCDN
LokasiDi server kita sendiriDistributed global (edge nodes)
LatensiSetelah request sampai ke data center kitaMelayani dari edge terdekat klien
KontrolPenuh — kita kontrol semua konfigurasiTerbatas pada fitur yang CDN sediakan
SSL TerminationDi server kitaDi edge CDN
Backend RoutingSangat fleksibel (path, header, regex)Terbatas
Custom LogicSangat fleksibel (Lua, modules)Terbatas
BiayaHanya server kitaBiaya per bandwidth/request
CacheDi server kita, dekat backendDi edge global
# Arsitektur umum: CDN + Nginx bekerja bersama
# Klien → CDN (Cloudflare) → Nginx → Backend

# Di Nginx: percayai IP dari CDN
# (agar $remote_addr berisi IP klien asli, bukan IP Cloudflare)
set_real_ip_from 103.21.244.0/22;   # Cloudflare IP range
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
# IPv6 Cloudflare
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;

real_ip_header CF-Connecting-IP;  # Header Cloudflare berisi IP klien asli

Health Check Backend #

Sebelum reverse proxy bisa meneruskan request, kita perlu tahu apakah backend dalam kondisi sehat. Nginx open-source mendukung passive health check — ia mendeteksi backend yang bermasalah berdasarkan respons error yang terjadi.

upstream app_backend {
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    server 10.0.0.3:3000;

    # Passive health check:
    # Jika backend mengembalikan error 3x dalam 30 detik,
    # tandai sebagai "down" selama 30 detik
    # (selama itu request tidak dikirim ke backend ini)
    # server akan dicoba lagi setelah 30 detik
}

server {
    location / {
        proxy_pass http://app_backend;

        # Retry ke backend lain jika backend pertama error
        proxy_next_upstream error timeout http_500 http_502 http_503;
        proxy_next_upstream_tries 3;       # Maksimum 3 kali retry
        proxy_next_upstream_timeout 10s;   # Total waktu untuk semua retry
    }
}

Nginx Plus mendukung active health check — Nginx secara proaktif mengirim health check request ke backend tanpa perlu menunggu request klien yang gagal. Untuk Nginx open-source, ini bisa dicapai menggunakan modul ngx_upstream_check_module atau mengandalkan passive health check.

# Endpoint health check yang umum di backend:
# GET /health
# GET /healthz (Kubernetes-style)
# GET /api/health
# GET /_health

# Respons yang diharapkan:
# HTTP 200 OK dengan body: {"status": "ok"}
# atau cukup HTTP 200 tanpa body

Logging untuk Reverse Proxy #

Log Nginx mencatat request dari sisi Nginx — bukan sisi backend. Untuk debugging reverse proxy yang efektif, format log perlu mencakup informasi backend:

http {
    log_format proxy_detailed
        '$remote_addr - $remote_user [$time_local] '
        '"$request" $status $body_bytes_sent '
        '"$http_referer" "$http_user_agent" '
        '$upstream_addr '              # IP backend yang dipilih
        '$upstream_status '           # Status dari backend (200, 500, dsb)
        '$upstream_response_time '    # Waktu backend merespons
        '$request_time '              # Total waktu request (termasuk kirim ke klien)
        '$upstream_cache_status';     # HIT/MISS/BYPASS (jika pakai cache)

    server {
        access_log /var/log/nginx/myapp-access.log proxy_detailed;

        location / {
            proxy_pass http://backend;
        }
    }
}

Dengan format ini, kita bisa menjawab pertanyaan kritis:

  • $upstream_response_time jauh lebih kecil dari $request_time → klien lambat mengunduh
  • $upstream_response_time besar → backend lambat
  • $upstream_status adalah 500 tapi $status adalah 200 → Nginx menyembunyikan error backend (cek proxy_intercept_errors)
# Analisis log: endpoint mana yang paling lambat?
awk '{print $NF, $(NF-2)}' /var/log/nginx/access.log | sort -n | tail -20

# Analisis: backend mana yang sering error?
awk '{print $(NF-3), $(NF-2)}' /var/log/nginx/access.log | grep "500\|502\|503" | sort | uniq -c

# Rata-rata response time per endpoint
awk '{print $7, $(NF-2)}' /var/log/nginx/access.log | awk '{sum[$1]+=$2; count[$1]++} END {for(k in sum) print k, sum[k]/count[k]}' | sort -k2 -n

Ringkasan #

  • Reverse proxy berdiri di sisi server — klien berkomunikasi hanya dengan Nginx, tidak pernah langsung ke backend. Ini memberikan manfaat: SSL termination, serving static files efisien, proteksi port, buffering, dan routing terpusat.
  • Dua koneksi terpisah: Nginx membuat koneksi ke klien dan ke backend secara independen — protokol, timeout, dan header bisa berbeda di kedua sisi.
  • Secara default Nginx menghapus beberapa header (Connection, hop-by-hop) dan mengubah Host saat meneruskan request ke backend.
  • Backend tidak otomatis tahu IP klien asli — perlu header eksplisit X-Real-IP, X-Forwarded-For, X-Forwarded-Proto.
  • Framework aplikasi perlu dikonfigurasi untuk membaca header X-Forwarded-* agar request.ip dan request.protocol menunjukkan nilai asli, bukan IP Nginx.

← Sebelumnya: Custom Error Page   Berikutnya: proxy_pass →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact