Node.js Application #

Node.js adalah platform runtime JavaScript berbasis engine V8 yang berjalan secara single-threaded dan asinkron menggunakan event loop. Karakteristik ini membuat Node.js sangat handal untuk menangani lalu lintas I/O yang padat. Namun, untuk menjalankan aplikasi Node.js (seperti Express, NestJS, Fastify, atau Next.js) di lingkungan produksi tingkat tinggi, membiarkan proses Node.js melayani trafik HTTP internet secara langsung adalah sebuah anti-pattern yang berbahaya.

Proses Node.js tidak dirancang untuk menangani SSL/TLS termination secara efisien, menyajikan berkas statis berukuran besar, menepis serangan DDoS, atau mendistribusikan beban kerja ke core CPU lainnya. Oleh karena itu, kita selalu menempatkan Nginx di depan aplikasi Node.js sebagai Reverse Proxy tingkat tinggi. Di artikel ini, kita akan membahas cara mengonfigurasi reverse proxy Node.js secara optimal, menerapkan taktik static assets offloading, mengelola batas unggah berkas kustom, serta menyusun skema load balancing menggunakan kluster PM2.

Arsitektur Alur Request Node.js di Belakang Nginx #

Nginx bertindak sebagai gerbang terdepan (perisai) yang menerima koneksi HTTPS dari klien luar, melakukan dekripsi SSL, menyajikan aset statis langsung dari sistem berkas lokal (disk), dan hanya meneruskan permintaan dinamis (seperti API request) ke kluster backend Node.js.

Berikut adalah diagram alur keputusan request di Nginx sebelum diteruskan ke Node.js:

flowchart TD
    Klien["Klien Browser"] -->|"HTTPS (Port 443)"| Nginx["Nginx Reverse Proxy"]
    Nginx -->|"Cek File Statik di Disk"| StaticCheck{"Apakah File Aset Statik?"}
    StaticCheck -->|"Ya: JS/CSS/Images"| ServeStatic["Sajikan Langsung dari Disk /dist"]
    StaticCheck -->|"Tidak: Permintaan Dinamis/API"| ProxyPass["Meneruskan Request via Keepalive Upstream"]
    ProxyPass --> Upstream["Upstream Node.js Cluster (PM2)"]
    Upstream --> Node1["Node.js Instance 1 (Port 3000)"]
    Upstream --> Node2["Node.js Instance 2 (Port 3001)"]
    Upstream --> Node3["Node.js Instance 3 (Port 3002)"]

    classDef default fill:#f9f9f9,stroke:#d1d5db,stroke-width:1px,color:#111827;
    classDef nginxStyle fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e3a8a;
    classDef nodeStyle fill:#f0fdf4,stroke:#15803d,stroke-width:2px,color:#166534;
    class Nginx,ServeStatic nginxStyle;
    class Upstream,Node1,Node2,Node3 nodeStyle;

Konfigurasi Reverse Proxy Header #

Saat Nginx meneruskan request HTTP ke Node.js menggunakan direktif proxy_pass, Nginx bertindak sebagai klien baru bagi Node.js. Akibatnya, IP address klien asli akan hilang dan digantikan oleh IP localhost Nginx (127.0.0.1). Demikian pula dengan skema koneksi (HTTP vs HTTPS).

Untuk mencegah kehilangan konteks ini, kita harus meneruskan beberapa header proxy standar agar Node.js dapat mengenali identitas klien asli:

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

    # 1. Teruskan header Host asli dari browser klien
    proxy_set_header Host $host;

    # 2. Teruskan IP asli klien ke backend
    proxy_set_header X-Real-IP $remote_addr;

    # 3. Teruskan daftar IP perantara (jika melewati proxy berlapis)
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # 4. Beritahu backend skema protokol yang dipakai (http atau https)
    proxy_set_header X-Forwarded-Proto $scheme;
}

Konfigurasi di Sisi Aplikasi Node.js #

Setelah Nginx meneruskan header di atas, kita wajib mengonfigurasi aplikasi Node.js kita agar bersedia mempercayai (trust) header proxy tersebut. Jika tidak, fungsi pelacak IP atau secure session cookies (HTTPS-only) di Node.js kita tidak akan berfungsi.

  • Pada Express.js:
    const express = require('express');
    const app = express();
    
    // Aktifkan trust proxy di Express
    app.enable('trust proxy');
    
    app.get('/api/ip', (req, res) => {
        // req.ip kini berisi IP klien asli yang diteruskan oleh Nginx
        res.json({ client_ip: req.ip, protocol: req.protocol });
    });
    
  • Pada NestJS:
    const app = await NestFactory.create<NestExpressApplication>(AppModule);
    // NestJS menggunakan Express di latar belakang secara default
    app.set('trust proxy', true);
    

TCP Keepalive Optimization untuk Koneksi Upstream #

Secara default, Nginx akan menutup koneksi TCP ke backend Node.js segera setelah respons HTTP selesai dikirimkan ke klien. Perilaku ini sangat tidak efisien untuk aplikasi dengan trafik tinggi, karena CPU server kita akan dibebang untuk melakukan handshake TCP berulang kali.

Kita harus mengonfigurasi connection pooling (Keepalive) agar koneksi TCP antara Nginx dan Node.js tetap dipertahankan terbuka di memori.

upstream nodejs_backend {
    server 127.0.0.1:3000;
    
    # Pertahankan maksimal 32 koneksi idle tetap terbuka ke backend
    keepalive 32;
}

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

    location / {
        proxy_pass http://nodejs_backend;

        # Wajib: Gunakan HTTP/1.1 (default proxy Nginx adalah HTTP/1.0 yang tidak mendukung Keepalive)
        proxy_http_version 1.1;

        # Wajib: Kosongkan header Connection klien agar Nginx tidak menutup koneksi
        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;
    }
}

Static Assets Offloading: Meniadakan Beban Node.js #

Salah satu optimasi terbesar yang bisa kita berikan pada server adalah Static Offloading. Node.js sangat lambat dan boros CPU saat membaca file statis (seperti berkas gambar, font, CSS, JS hasil kompilasi) dari disk dan mengalirkannya ke klien. Nginx, di sisi lain, ditulis dalam bahasa C tingkat rendah dan terintegrasi dengan syscall sendfile kernel Linux untuk transfer data zero-copy super cepat.

Kita mengonfigurasi Nginx agar menyajikan seluruh aset statik secara langsung dari disk, tanpa pernah meneruskan request tersebut ke runtime Node.js.

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

    # Lokasi direktori output build frontend kita (misalnya dist/ hasil build Vite)
    root /var/www/my-node-app/dist;

    # 1. Lokasi aset statik (JS, CSS, Gambar, Font)
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        # Sajikan langsung dari root direktori di atas
        try_files $uri =404;

        # Aktifkan caching agresif di browser selama 1 tahun
        expires 1y;
        add_header Cache-Control "public, immutable";
        
        access_log off; # Matikan access log agar tidak mengotori disk
    }

    # 2. Lokasi request dinamis (API atau routing halaman dinamis)
    location / {
        proxy_pass http://nodejs_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;
    }
}

Mengelola Batas Unggah File Klien #

Jika aplikasi Node.js kita memiliki fitur unggah file (seperti unggah gambar profil atau dokumen), kita wajib menyetel batas ukuran unggahan di Nginx. Secara default, Nginx membatasi ukuran request body maksimal sebesar 1 Megabyte. Jika klien mencoba mengunggah file di atas 1MB, Nginx langsung memotong koneksi dan mengembalikan galat 413 Request Entity Too Large.

Berikut adalah konfigurasi tuning untuk unggah file besar secara aman:

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

    location /api/upload/ {
        proxy_pass http://nodejs_backend;
        
        # 1. Naikkan batas unggah file maksimal ke 50 Megabyte
        client_max_body_size 50m;

        # 2. Sesuaikan alokasi buffer RAM untuk body request
        # Request di bawah 256KB akan disimpan di RAM, di atas itu akan ditulis ke file temp disk
        client_body_buffer_size 256k;

        # 3. Naikkan timeout jika klien mengunggah via jaringan lambat
        client_body_timeout 120s;
        
        # 4. Naikkan timeout menunggu backend memproses file besar
        proxy_read_timeout 120s;
        proxy_send_timeout 120s;
    }
}

Load Balancing Upstream dengan Cluster PM2 #

Node.js berjalan di satu core CPU. Untuk memaksimalkan penggunaan multi-core CPU pada server produksi kita, kita menggunakan process manager seperti PM2 dalam mode Cluster. PM2 akan membuat beberapa instansi proses aplikasi Node.js kita berdasarkan jumlah core CPU yang tersedia.

Sebagai contoh, jika server kita memiliki 4 core CPU, PM2 dapat dikonfigurasi untuk menjalankan 4 proses Node.js yang mendengarkan port yang berbeda (misalnya port 3000, 3001, 3002, dan 3003) atau membiarkan PM2 melakukan port sharing internal.

Di tingkat Nginx, kita mengonfigurasi load balancing upstream untuk membagi rata beban request di antara seluruh instansi Node.js tersebut:

# Konfigurasi upstream load balancing
upstream pm2_node_cluster {
    # Distribusikan beban request ke 4 instansi port proses PM2 kita
    server 127.0.0.1:3000 max_fails=3 fail_timeout=10s;
    server 127.0.0.1:3001 max_fails=3 fail_timeout=10s;
    server 127.0.0.1:3002 max_fails=3 fail_timeout=10s;
    server 127.0.0.1:3003 max_fails=3 fail_timeout=10s;

    # Gunakan keepalive untuk koneksi upstream
    keepalive 64;
}

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

    location / {
        # Teruskan beban trafik ke cluster upstream di atas
        proxy_pass http://pm2_node_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;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Strategi Deployment Zero-Downtime dengan PM2 Reload #

Ketika melakukan pembaruan kode aplikasi di server produksi, mematikan proses Node.js secara kasar (pm2 restart) akan memutus koneksi aktif klien dan menyebabkan galat 502 Bad Gateway di sisi Nginx selama beberapa detik sebelum aplikasi menyala kembali.

Untuk mencapai zero-downtime deployment, kita harus memadukan mekanisme PM2 Graceful Reload dengan konfigurasi toleransi kesalahan (fault tolerance) di Nginx.

  1. Gunakan pm2 reload (bukan restart): pm2 reload akan merestart instansi satu per satu secara bergiliran. Instansi baru akan dinyalakan terlebih dahulu sebelum instansi lama dimatikan.

  2. Konfigurasikan Nginx Failover: Kita mengonfigurasi direktif proxy_next_upstream agar Nginx secara otomatis mengalihkan request ke instansi Node.js lainnya jika salah satu instansi sedang dimatikan atau tidak merespons selama proses reload:

upstream pm2_node_cluster {
    server 127.0.0.1:3000 max_fails=3 fail_timeout=10s;
    server 127.0.0.1:3001 max_fails=3 fail_timeout=10s;
    server 127.0.0.1:3002 max_fails=3 fail_timeout=10s;
    server 127.0.0.1:3003 max_fails=3 fail_timeout=10s;
    keepalive 64;
}

server {
    # ...
    location / {
        proxy_pass http://pm2_node_cluster;
        
        # Alihkan request ke upstream lain jika backend mengembalikan error
        proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
        proxy_next_upstream_tries 3;
        proxy_next_upstream_timeout 5s;
        
        # ... konfigurasi proxy_set_header lainnya ...
    }
}

Dengan setelan di atas, saat PM2 mematikan satu instansi Node.js untuk reload, Nginx yang mendeteksi kegagalan koneksi atau error 502 akan langsung melemparkan request tersebut ke instansi berikutnya dalam kelompok upstream tanpa memicu error di browser klien.


Contoh Konfigurasi Server Block Produksi Lengkap (Node.js/Next.js) #

Berikut adalah berkas konfigurasi server block HTTPS tingkat produksi lengkap yang memadukan optimasi SSL, kompresi Gzip, static offloading, tuning unggah file, dan pooling koneksi keepalive:

# Definisi cluster upstream backend
upstream nodejs_prod_cluster {
    server 127.0.0.1:3000 max_fails=3 fail_timeout=10s;
    server 127.0.0.1:3001 max_fails=3 fail_timeout=10s;
    
    keepalive 32;
}

# Redireksi otomatis HTTP ke HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name app.unisbadri.com;
    
    return 301 https://$host$request_uri;
}

# Blok Server HTTPS Utama
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name app.unisbadri.com;

    # Konfigurasi SSL Sertifikat (Let's Encrypt)
    ssl_certificate /etc/letsencrypt/live/app.unisbadri.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.unisbadri.com/privkey.pem;
    
    # Pengerasan SSL Parameter
    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;
    
    # SSL Session Caching
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # Lokasi Root Aset Statik Aplikasi (Next.js public / build assets)
    root /var/www/my-next-app/.next;

    # Offloading Aset Statik Next.js (_next/static/)
    location /_next/static/ {
        alias /var/www/my-next-app/.next/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    # Offloading File Aset Umum (public/ folder)
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2|webp)$ {
        root /var/www/my-next-app/public;
        try_files $uri =404;
        expires 30d;
        add_header Cache-Control "public, no-transform";
        access_log off;
    }

    # Endpoint Khusus Unggah File Besar
    location /api/upload/ {
        proxy_pass http://nodejs_prod_cluster;
        
        # Batasan unggah file 100MB
        client_max_body_size 100m;
        client_body_buffer_size 512k;
        client_body_timeout 180s;
        
        proxy_read_timeout 180s;
        proxy_send_timeout 180s;

        # Konfigurasi reverse proxy HTTP/1.1
        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;
    }

    # Penanganan Request Umum / Dinamis
    location / {
        proxy_pass http://nodejs_prod_cluster;
        
        # Batasan upload default untuk request biasa (1 Megabyte)
        client_max_body_size 1m;

        # Konfigurasi standar reverse proxy
        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;

        # Penanganan Buffering Proxy
        proxy_buffering on;
        proxy_buffers 16 16k;
        proxy_buffer_size 32k;
    }
}

Tabel Troubleshooting Masalah Umum Integrasi #

Berikut adalah daftar galat umum yang sering muncul saat menghubungkan Nginx dengan Node.js beserta langkah solusinya:

Kode ErrorGejala MasalahPenyebab di LapanganSolusi Praktis
502 Bad GatewayNginx gagal meneruskan request. Halaman menampilkan error 502.Aplikasi Node.js mati, PM2 crash, atau port backend salah.Cek status PM2 dengan pm2 list atau jalankan netstat -plnt untuk memverifikasi port.
504 Gateway TimeoutNginx memutuskan koneksi sebelum Node.js selesai memproses.Node.js memproses tugas berat (seperti memproses database besar) di luar batas waktu timeout.Naikkan durasi direktif proxy_read_timeout di Nginx ke nilai yang lebih tinggi.
413 Request Entity Too LargeKlien gagal mengunggah file.Ukuran request body melebihi batas default 1MB.Tambahkan direktif client_max_body_size 50m; (sesuai limit kebutuhan) di dalam location block.
404 Not FoundBerkas statis tidak muncul di browser klien.Path folder di direktif root atau alias Nginx salah tunjuk atau tidak memiliki hak akses baca.Verifikasi keselarasan path folder kustom dan cek hak akses menggunakan chmod / chown.

Ringkasan dan Praktik Terbaik #

  • Selalu Aktifkan trust proxy: Pastikan aplikasi backend Node.js kita telah mengaktifkan setelan trust proxy agar pembacaan IP address klien dan protokol koneksi (HTTP/HTTPS) dari log server Node.js akurat.
  • Lakukan Offload Aset Statik: Membiarkan Node.js menyajikan file statis membuang siklus kerja event loop yang berharga. Selalu gunakan Nginx untuk menyajikan berkas statik langsung dari disk.
  • Gunakan PM2 Cluster Mode: Manfaatkan seluruh core CPU fisik di server kita dengan menyalakan PM2 mode cluster agar backend kita memiliki skalabilitas dan ketahanan tinggi (high availability).
  • Uji Konfigurasi Sebelum Reload: Selalu jalankan sudo nginx -t sebelum melakukan reload atau restart Nginx agar tidak memutus koneksi server produksi berjalan akibat salah tulis sintaksis.

← Sebelumnya: Dynamic Module   Berikutnya: PHP-FPM (Laravel/WordPress) →

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