Server

Nginx Reverse Proxy untuk Aplikasi Web - Setup SSL Load Balancing dan Security Hardening

Pertama kali saya deploy aplikasi web langsung ke port 8080 dan akses via IP:port, rasanya kok kurang profesional ya. User harus ngetik port manual, browser kadang warning soal HTTP, dan kalau mau SSL harus setup ulang di tiap aplikasi. Setelah kenal Nginx sebagai reverse proxy, semuanya berubah. Satu entry point, SSL termination di satu tempat, dan aplikasi backend tetap aman di balik Nginx.

Reverse proxy itu konsepnya sederhana: Nginx duduk di depan aplikasi kamu, nerima request dari user, terus forward ke backend yang sebenarnya. Tapi manfaatnya luar biasa banyak - dari SSL, load balancing, caching, sampai security hardening. Mari kita setup dari nol.

Konsep Dasar Reverse Proxy

Bayangkan kamu punya 3 aplikasi berjalan di server yang sama:

  • App A di port 3000 (Node.js)
  • App B di port 8000 (Python/Flask)
  • App C di port 8080 (PHP/CodeIgniter)

Tanpa reverse proxy, user harus akses http://server:3000, http://server:8000, dst. Dengan Nginx sebagai reverse proxy, semua bisa diakses lewat port 80/443 dengan path atau subdomain berbeda:

  • https://app.example.com/ -> App A (port 3000)
  • https://app.example.com/api/ -> App B (port 8000)
  • https://app.example.com/admin/ -> App C (port 8080)

Nginx juga bisa handle SSL termination, jadi backend kamu cukup jalan di HTTP biasa. Nginx yang urus semua hal berbau HTTPS ke client.

Install Nginx di Ubuntu

Kalau belum terinstall, pasang dulu Nginx:


sudo apt update
sudo apt install nginx -y

# Cek status
sudo systemctl status nginx

# Enable auto-start saat boot
sudo systemctl enable nginx

Setelah install, buka browser dan akses http://IP_SERVER_KAMU. Harusnya muncul halaman default Nginx. Kalau belum muncul, cek firewall:


sudo ufw allow 'Nginx Full'
sudo ufw status

Setup Reverse Proxy Dasar

Contoh paling simpel: proxy semua request ke aplikasi Node.js di port 3000. Buat file config baru:


sudo nano /etc/nginx/sites-available/myapp

Isi dengan konfigurasi berikut:


server {
    listen 80;
    server_name app.example.com;

    location / {
        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_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_cache_bypass $http_upgrade;
    }
}

Penjelasan directive penting:

  • proxy_pass - alamat backend tujuan
  • proxy_http_version 1.1 - pakai HTTP/1.1 untuk koneksi yang lebih efisien
  • proxy_set_header Upgrade - support WebSocket connection
  • proxy_set_header Host - forward hostname asli dari client
  • X-Real-IP dan X-Forwarded-For - supaya backend tahu IP asli client, bukan IP Nginx

Aktifkan site dan restart Nginx:


sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Selalu jalankan nginx -t dulu sebelum reload. Syntax error di config bisa bikin Nginx mati total.

Multi-App dengan Path-Based Routing

Ini yang sering bikin orang bingung. Kamu bisa routing berdasarkan path URL ke backend berbeda:


server {
    listen 80;
    server_name mydomain.com;

    # Frontend (React/Vue build)
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # API backend
    location /api/ {
        proxy_pass http://127.0.0.1:8000/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Admin panel
    location /admin/ {
        proxy_pass http://127.0.0.1:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Perhatikan trailing slash di proxy_pass. Kalau kamu tulis proxy_pass http://127.0.0.1:8000/; dengan trailing slash, maka request ke /api/users akan diteruskan ke backend sebagai /users (path /api di-strip). Tanpa trailing slash, path diteruskan apa adanya.

Setup SSL dengan Let's Encrypt

Sekarang HTTPS sudah wajib. Browser Chrome bakal kasih warning "Not Secure" kalau website kamu masih HTTP. Untungnya ada Let's Encrypt yang kasih SSL gratis.


# Install Certbot
sudo apt install certbot python3-certbot-nginx -y

# Generate SSL certificate
sudo certbot --nginx -d app.example.com

# Auto-renewal (sudah ter-setup otomatis, tapi bisa test manual)
sudo certbot renew --dry-run

Certbot otomatis modifikasi config Nginx kamu. Hasilnya kurang lebih seperti ini:


server {
    listen 80;
    server_name app.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    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;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    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;
    }
}

Perhatikan ada dua blok server. Yang pertama redirect HTTP ke HTTPS. Yang kedua handle SSL dan proxy ke backend.

Load Balancing ke Multiple Backend

Kalau aplikasi kamu sudah besar dan perlu handle traffic tinggi, kamu bisa jalankan beberapa instance backend dan Nginx akan distribusikan request:


upstream backend_cluster {
    # Round-robin (default)
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;

    # Weighted - server pertama dapat 2x traffic
    # server 127.0.0.1:3001 weight=2;
    # server 127.0.0.1:3002 weight=1;
    # server 127.0.0.1:3003 weight=1;

    # Backup server (hanya aktif kalau semua utama down)
    # server 127.0.0.1:3004 backup;

    # Keepalive connection ke backend
    keepalive 32;
}

server {
    listen 443 ssl;
    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;

    location / {
        proxy_pass http://backend_cluster;
        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;
    }
}

Metode load balancing yang tersedia:

  • Round-robin (default) - request didistribusikan bergantian
  • Weighted - server dengan weight lebih tinggi dapat request lebih banyak
  • Least connections - request dikirim ke server dengan koneksi paling sedikit
  • IP hash - client yang sama selalu ke server yang sama (sticky session)

Untuk least connections, tambahkan directive:


upstream backend_cluster {
    least_conn;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
}

Caching untuk Performa

Nginx bisa cache response dari backend supaya request berikutnya lebih cepat. Ini sangat efektif untuk konten yang jarang berubah:


# Di blok http (nginx.conf)
proxy_cache_path /var/cache/nginx levels=1:2 
    keys_zone=my_cache:10m max_size=1g 
    inactive=60m use_temp_path=off;

server {
    listen 443 ssl;
    server_name app.example.com;

    # Static files - cache lama
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2)$ {
        proxy_pass http://127.0.0.1:3000;
        proxy_cache my_cache;
        proxy_cache_valid 200 30d;
        proxy_cache_valid 404 1m;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # Dynamic content - cache pendek
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_cache my_cache;
        proxy_cache_valid 200 10m;
        proxy_cache_bypass $http_cache_control;
        add_header X-Cache-Status $upstream_cache_status;
    }
}

Header X-Cache-Status berguna banget untuk debugging. Nilainya bisa HIT (served from cache), MISS (fetch dari backend), atau BYPASS (cache di-skip).

Security Hardening

Ini bagian yang sering di-skip tapi sangat penting. Berikut config Nginx yang lebih aman:


server {
    listen 443 ssl http2;
    server_name app.example.com;

    # SSL config
    ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.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;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # Hide Nginx version
    server_tokens off;

    # Limit request body size (anti-DDoS)
    client_max_body_size 10M;

    # Rate limiting
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

    location /api/ {
        limit_req zone=api_limit burst=20 nodelay;
        proxy_pass http://127.0.0.1:8000/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Block access to hidden files
    location ~ /\. {
        deny all;
        return 404;
    }
}

Penjelasan security headers:

  • X-Frame-Options - mencegah clickjacking (website kamu tidak bisa di-embed di iframe orang lain)
  • X-Content-Type-Options - mencegah MIME type sniffing
  • Strict-Transport-Security (HSTS) - paksa browser selalu pakai HTTPS
  • Content-Security-Policy - batasi resource yang bisa dimuat (anti-XSS)
  • server_tokens off - sembunyikan versi Nginx dari response header

Rate Limiting untuk Proteksi API

Rate limiting wajib dipasang di endpoint API supaya tidak ada yang bisa spam request. Definisikan zone-nya di blok http:


# Di nginx.conf, blok http {}
limit_req_zone $binary_remote_addr zone=api_zone:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=login_zone:10m rate=1r/s;

Lalu terapkan di location block:


# API - max 5 request per detik
location /api/ {
    limit_req zone=api_zone burst=10 nodelay;
    proxy_pass http://127.0.0.1:8000/;
}

# Login - max 1 request per detik (lebih ketat)
location /auth/login {
    limit_req zone=login_zone burst=3 nodelay;
    proxy_pass http://127.0.0.1:8000/auth/login;
}

Parameter burst membolehkan request melebihi rate dalam waktu singkat. nodelay artinya request burst langsung diproses, tidak ditahan.

WebSocket Proxy

Kalau aplikasi kamu pakai WebSocket (chat, real-time dashboard, live notification), Nginx perlu config khusus:


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_set_header X-Real-IP $remote_addr;

    # Timeout untuk koneksi idle
    proxy_read_timeout 86400s;
    proxy_send_timeout 86400s;
}

Tanpa header Upgrade dan Connection, WebSocket handshake akan gagal. Ini error yang sering bikin orang bingung kenapa WebSocket tidak connect padahal backend sudah jalan.

Monitoring dan Log

Nginx punya access log dan error log yang sangat berguna untuk debugging:


# Lihat access log real-time
sudo tail -f /var/log/nginx/access.log

# Lihat error log
sudo tail -f /var/log/nginx/error.log

# Cek status koneksi aktif
sudo ss -tlnp | grep nginx

# Hitung request per menit dari access log
awk '{print $4}' /var/log/nginx/access.log | cut -d: -f1-3 | uniq -c | tail -20

Kamu juga bisa tambahkan nginx-module-vts untuk dashboard monitoring real-time yang menampilkan request rate, bandwidth, dan response time per upstream.

Troubleshooting Umum

Beberapa masalah yang sering muncul saat pakai Nginx reverse proxy:

  • 502 Bad Gateway - backend mati atau port salah. Cek: curl http://127.0.0.1:PORT
  • 504 Gateway Timeout - backend terlalu lambat. Tambah: proxy_read_timeout 300s;
  • 413 Request Entity Too Large - upload file kebesaran. Ubah: client_max_body_size 50m;
  • CORS errors - backend tidak set header CORS. Bisa ditambahkan di Nginx:

location /api/ {
    add_header Access-Control-Allow-Origin * always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;

    if ($request_method = 'OPTIONS') {
        return 204;
    }

    proxy_pass http://127.0.0.1:8000/;
}
  • WebSocket tidak connect - pastikan header Upgrade dan Connection sudah di-set (lihat section WebSocket di atas)
  • Proxy headers tidak sampai ke backend - cek proxy_set_header X-Real-IP dan pastikan backend baca dari header yang benar

Config Lengkap untuk Production

Ini template yang sudah saya pakai di production, tinggal sesuaikan:


# /etc/nginx/sites-available/production
upstream app_backend {
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    keepalive 32;
}

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    # SSL
    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_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # Security
    server_tokens off;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;

    # Gzip
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;

    # Size limits
    client_max_body_size 20M;

    # Static files
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2|ttf)$ {
        proxy_pass http://app_backend;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # Application
    location / {
        proxy_pass http://app_backend;
        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_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 60s;
        proxy_read_timeout 60s;
        proxy_send_timeout 60s;
    }

    # Block hidden files
    location ~ /\. {
        deny all;
    }
}

Nginx reverse proxy memang terlihat simpel di permukaan, tapi konfigurasinya sangat fleksibel. Dari satu file config, kamu bisa handle SSL, caching, load balancing, security, rate limiting, dan WebSocket. Skill yang wajib dikuasai kalau kamu serius di web development atau DevOps.


You may also like


0 Comments


Leave a Reply

Comments with links or spam keywords will be rejected.
Scroll to Top