WebSocket #
WebSocket memungkinkan komunikasi dua arah antara browser dan server melalui satu koneksi TCP yang persisten — berbeda dari HTTP biasa yang request-response dan stateless. Nginx mendukung proxying WebSocket, tapi butuh konfigurasi tambahan karena WebSocket dimulai sebagai HTTP upgrade request.
Cara Kerja WebSocket Upgrade #
1. Browser kirim HTTP request dengan header Upgrade:
GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
2. Server merespons 101 Switching Protocols:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
3. Koneksi TCP tetap terbuka
Browser ↔ Server: komunikasi dua arah secara real-time
Nginx perlu meneruskan header Upgrade dan Connection ke backend agar proses upgrade ini bisa terjadi.
Konfigurasi Dasar WebSocket #
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location /ws/ {
proxy_pass http://websocket_backend;
# Dua baris wajib untuk WebSocket
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Header standar
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Timeout untuk koneksi WebSocket yang long-lived
# Default proxy_read_timeout hanya 60s — terlalu pendek untuk WebSocket
proxy_read_timeout 86400s; # 24 jam
proxy_send_timeout 86400s;
proxy_connect_timeout 5s;
}
}
Nilai $http_upgrade diteruskan apa adanya dari request klien — bisa kosong untuk request HTTP biasa, atau “websocket” untuk upgrade request. Connection "upgrade" memberitahu backend bahwa ini adalah upgrade request.
Kombinasi HTTP dan WebSocket di Satu Server #
Aplikasi real-time biasanya punya endpoint WebSocket dan endpoint HTTP REST di server yang sama. Map variable membantu routing:
http {
# Deteksi apakah ini WebSocket upgrade request
map $http_upgrade $connection_upgrade {
default upgrade; # Untuk WebSocket
'' close; # Untuk HTTP biasa: tutup koneksi setelah response
}
upstream app_backend {
server localhost:3000;
keepalive 32;
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Semua request (HTTP dan WebSocket) ke backend yang sama
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
# Gunakan variabel dari map
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $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;
# Timeout panjang untuk WebSocket
proxy_read_timeout 86400s;
}
# Endpoint HTTP khusus dengan timeout lebih pendek (optional)
location /api/ {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_read_timeout 30s; # Timeout lebih pendek untuk API
}
}
}
Pattern dengan map $http_upgrade $connection_upgrade adalah idiom standar untuk menangani keduanya secara elegan dalam satu konfigurasi.
Load Balancing WebSocket #
WebSocket adalah koneksi stateful — sekali klien terhubung ke server tertentu, semua pesan dalam sesi itu harus ke server yang sama. Gunakan ip_hash atau hash untuk session affinity:
upstream ws_servers {
ip_hash; # Pastikan klien yang sama selalu ke server yang sama
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000;
}
server {
location /ws/ {
proxy_pass http://ws_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400s;
}
}
Atau gunakan hash berdasarkan cookie session untuk konsistensi yang lebih baik dari ip_hash:
upstream ws_servers {
hash $cookie_session_id consistent;
server 10.0.0.1:3000;
server 10.0.0.2:3000;
}
Monitoring Koneksi WebSocket #
# Lihat jumlah koneksi WebSocket yang aktif
# State ESTABLISHED ke port backend = koneksi WebSocket aktif
ss -tn | grep :3000 | grep ESTABLISHED | wc -l
# Monitoring lebih detail
ss -tnp | grep nginx | awk '{print $1}' | sort | uniq -c
# Di log Nginx, koneksi WebSocket terlihat sebagai request dengan
# status 101 dan bytes_sent yang terus bertambah
grep ' 101 ' /var/log/nginx/access.log | tail -20
Troubleshooting WebSocket #
Koneksi WebSocket langsung terputus setelah 60 detik:
Penyebab: proxy_read_timeout default 60 detik terlampaui.
Solusi: Naikkan proxy_read_timeout atau implementasikan heartbeat/ping-pong di aplikasi.
Error 400 Bad Request saat upgrade:
Penyebab: Header Upgrade atau Connection tidak diteruskan dengan benar.
Solusi: Pastikan proxy_http_version 1.1 dan proxy_set_header Upgrade $http_upgrade ada di konfigurasi.
WebSocket bekerja dengan HTTP tapi tidak dengan HTTPS:
Penyebab: JavaScript menggunakan ws:// (bukan wss://) untuk koneksi WebSocket di halaman HTTPS.
Solusi: Pastikan kode frontend menggunakan wss:// untuk koneksi WebSocket di halaman HTTPS.
Ringkasan #
- Dua directive wajib untuk WebSocket:
proxy_http_version 1.1danproxy_set_header Upgrade $http_upgrade+proxy_set_header Connection "upgrade".- Naikkan
proxy_read_timeoutke nilai yang besar (misalnya86400s) — default 60 detik akan memutus koneksi WebSocket yang lama idle.- Gunakan
map $http_upgrade $connection_upgradeuntuk menangani HTTP dan WebSocket di satu location secara elegan.- WebSocket bersifat stateful — gunakan
ip_hashatauhash $cookie_...untuk session affinity saat load balancing.- Di halaman HTTPS, koneksi WebSocket harus menggunakan
wss://, bukanws://.