Modern web uygulamaları artık sadece istek-cevap döngüsüyle sınırlı kalmıyor. Gerçek zamanlı bildirimler, canlı sohbet sistemleri, anlık veri akışları ve oyun platformları gibi uygulamalar için kalıcı bağlantılara ihtiyaç duyuyoruz. İşte tam bu noktada WebSocket ve Long Polling devreye giriyor. Nginx’i bu iki teknoloji için doğru yapılandırmak hem performans hem de kararlılık açısından kritik önem taşıyor. Bu yazıda sıfırdan üretime kadar tüm süreci ele alacağız.
WebSocket ve Long Polling Nedir?
Önce temel kavramları netleştirelim. İkisi de “gerçek zamanlı” iletişim için kullanılıyor ama çalışma şekilleri oldukça farklı.
WebSocket, HTTP üzerinden başlayan ama sonra ayrı bir protokole geçen tam çift yönlü bir bağlantı protokolüdür. İstemci ile sunucu arasında kalıcı bir tünel açılır ve her iki taraf da istediği zaman veri gönderebilir. Bağlantı kurulduktan sonra HTTP overhead’i ortadan kalkar, bu da düşük gecikme ve yüksek verimlilik demektir.
Long Polling ise HTTP’nin üzerine inşa edilmiş bir teknik. İstemci sunucuya bir istek atar, sunucu yeni bir veri olana kadar bağlantıyı açık tutar. Veri geldiğinde ya da belirli bir süre dolduğunda cevap döner, istemci hemen yeni bir istek açar. Eski tarayıcılarla uyumlu olması ve güvenlik duvarlarından daha kolay geçmesi büyük avantaj.
Nginx’i WebSocket Proxy Olarak Yapılandırmak
Nginx, varsayılan ayarlarıyla WebSocket bağlantılarını desteklemez. Bunun nedeni HTTP/1.1’deki Upgrade başlığının özel olarak işlenmesi gerekmesidir. Nginx bu başlığı görmezden gelir ve bağlantı HTTP/1.0’a düşer, WebSocket握手 başarısız olur.
Temel WebSocket Proxy Konfigürasyonu
En basit senaryo: Arkada Node.js ile çalışan bir Socket.io uygulamanız var ve Nginx’i önüne koyuyorsunuz.
# /etc/nginx/conf.d/websocket-app.conf
upstream websocket_backend {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://websocket_backend;
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;
}
}
Buradaki en kritik satırlar proxy_http_version 1.1 ve proxy_set_header Upgrade/Connection satırlarıdır. WebSocket handshake’i için HTTP/1.1 şart, HTTP/1.0 ile bu iş yürümez. Upgrade başlığı protokol değişikliği talebini, Connection: upgrade ise bu talebin kalıcı bağlantıya geçiş içerdiğini belirtir.
Timeout Değerlerini Ayarlamak
Varsayılan Nginx timeout değerleri WebSocket için çok kısa. Bir kullanıcı saatler boyu açık bir WebSocket bağlantısı tutabilir, Nginx bunu 60-75 saniyede keser.
# /etc/nginx/conf.d/websocket-app.conf
server {
listen 80;
server_name app.example.com;
location /ws/ {
proxy_pass http://websocket_backend;
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;
# WebSocket bağlantıları için timeout ayarları
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 75s;
# Buffer ayarları - WebSocket için küçük tutun
proxy_buffering off;
proxy_buffer_size 4k;
}
location / {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_read_timeout 60s;
}
}
proxy_read_timeout: Nginx’in backend’den veri bekleyeceği maksimum süre. WebSocket için saatler seviyesine çıkarın. proxy_send_timeout: Nginx’in backend’e veri göndermek için bekleyeceği süre. proxy_buffering off: WebSocket için buffering kapatın, anlık iletim önemlidir.
SSL/TLS ile Güvenli WebSocket (WSS)
Production ortamında her zaman WSS kullanmalısınız. Let’s Encrypt ile SSL almış bir senaryo:
# /etc/nginx/conf.d/websocket-ssl.conf
upstream ws_nodes {
ip_hash;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
keepalive 64;
}
server {
listen 80;
server_name app.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
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;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# WebSocket endpoint
location /socket.io/ {
proxy_pass http://ws_nodes;
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 https;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_buffering off;
}
# Normal HTTP içerik
location / {
proxy_pass http://ws_nodes;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
}
ip_hash direktifi önemli. Birden fazla backend sunucu varken aynı kullanıcının her istekte aynı sunucuya gitmesini sağlar. WebSocket oturumları sunucu tarafında state tuttuğu için bu kritik.
Long Polling Konfigürasyonu
Long Polling, HTTP’nin standart istek-cevap modelini biraz büker. İstemci istek atar, sunucu bağlantıyı açık tutar ve ancak yeni veri geldiğinde cevap verir. Nginx’in buradaki görevi bu uzun beklemeleri sabırla beklemek ve timeout’a uğramamak.
# /etc/nginx/conf.d/longpolling.conf
upstream polling_backend {
server 127.0.0.1:8080;
keepalive 100;
}
server {
listen 443 ssl http2;
server_name poll.example.com;
ssl_certificate /etc/letsencrypt/live/poll.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/poll.example.com/privkey.pem;
# Long polling endpoint
location /api/events {
proxy_pass http://polling_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Long polling için uzun timeout
proxy_read_timeout 120s;
proxy_send_timeout 120s;
proxy_connect_timeout 10s;
# Chunked response için buffering kapatın
proxy_buffering off;
proxy_cache off;
# CORS başlıkları gerekiyorsa
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Credentials true always;
}
# Diğer API endpointleri
location /api/ {
proxy_pass http://polling_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_read_timeout 30s;
}
}
Long Polling’de proxy_set_header Connection "" kullanımına dikkat edin. Bu, Nginx’in upstream bağlantısını keepalive modunda açık tutmasını sağlar. Her polling isteği için yeni TCP bağlantısı açmak yerine mevcut bağlantıyı yeniden kullanır, bu da çok büyük performans farkı yaratır.
Karışık Uygulama: Hem WebSocket Hem Long Polling
Gerçek dünya senaryosu: Socket.io kullanan bir uygulama. Socket.io ilk bağlantıda Long Polling dener, tarayıcı destekliyorsa WebSocket’e geçer. Bu ikili yapıyı Nginx’te yönetmek biraz dikkat ister.
# /etc/nginx/conf.d/socketio-app.conf
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream socketio_backend {
ip_hash;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
keepalive 64;
}
server {
listen 443 ssl http2;
server_name realtime.example.com;
ssl_certificate /etc/letsencrypt/live/realtime.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/realtime.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Socket.io hem WS hem polling kullanır
location /socket.io/ {
proxy_pass http://socketio_backend;
proxy_http_version 1.1;
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;
# Her iki mod için uygun timeout
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_buffering off;
# Rate limiting - kötüye kullanımı önle
limit_req zone=ws_limit burst=20 nodelay;
}
location / {
proxy_pass http://socketio_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 60s;
}
}
Buradaki map bloğu çok zarif bir çözüm. $http_upgrade başlığı geliyorsa Connection: upgrade gönderiyor (WebSocket modu), başlık yoksa Connection: close gönderiyor (normal HTTP/Long Polling modu). Bunu nginx.conf‘un http bloğuna ya da ayrı bir include dosyasına koyun.
Rate Limiting ve Güvenlik
WebSocket bağlantıları uzun süre açık kalır, bu da hem avantaj hem tehdit demek. Bir saldırgan binlerce WebSocket bağlantısı açarak sunucunuzu yorabilir. Rate limiting şart.
# /etc/nginx/nginx.conf içindeki http bloğuna ekleyin
http {
# WebSocket bağlantı hızı limiti
limit_req_zone $binary_remote_addr zone=ws_limit:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=ws_conn:10m;
# Long polling için ayrı zone
limit_req_zone $binary_remote_addr zone=poll_limit:10m rate=30r/s;
include /etc/nginx/conf.d/*.conf;
}
# Konfigürasyonu test et ve yeniden yükle
nginx -t && systemctl reload nginx
# Aktif WebSocket bağlantılarını izle
ss -tn state established '( dport = :443 )' | wc -l
# Nginx bağlantı istatistikleri
curl -s http://127.0.0.1/nginx_status
Gerçek Dünya Senaryosu: Canlı Sohbet Uygulaması
Diyelim ki bir müşteri destek chat sistemi kurdunuz. Backend olarak Node.js + Redis pub/sub kullanıyorsunuz, birden fazla uygulama sunucusu var ve Nginx önde duruyor.
# /etc/nginx/conf.d/chat-app.conf
upstream chat_api {
server 10.0.1.10:3000;
server 10.0.1.11:3000;
server 10.0.1.12:3000;
keepalive 32;
}
upstream chat_ws {
ip_hash;
server 10.0.1.10:3001;
server 10.0.1.11:3001;
server 10.0.1.12:3001;
keepalive 128;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' keep-alive;
}
server {
listen 443 ssl http2;
server_name chat.example.com;
ssl_certificate /etc/ssl/certs/chat.example.com.crt;
ssl_certificate_key /etc/ssl/private/chat.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:ChatSSL:20m;
ssl_session_timeout 1d;
# Statik dosyalar
location /static/ {
root /var/www/chat;
expires 30d;
add_header Cache-Control "public, immutable";
}
# REST API
location /api/ {
proxy_pass http://chat_api;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 30s;
limit_req zone=poll_limit burst=50 nodelay;
}
# WebSocket chat endpoint
location /chat/ws {
proxy_pass http://chat_ws;
proxy_http_version 1.1;
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_read_timeout 7200s;
proxy_send_timeout 7200s;
proxy_buffering off;
# Bağlantı başına limit
limit_conn ws_conn 5;
limit_req zone=ws_limit burst=10 nodelay;
# Access log formatını özelleştir
access_log /var/log/nginx/ws-access.log combined;
}
# Fallback - eski tarayıcılar için long polling
location /chat/poll {
proxy_pass http://chat_api;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_read_timeout 90s;
proxy_buffering off;
proxy_cache off;
limit_req zone=poll_limit burst=15;
}
location / {
root /var/www/chat;
try_files $uri $uri/ /index.html;
}
}
Bu konfigürasyonda REST API, WebSocket ve Long Polling fallback üçlüsünü ayrı upstream gruplara yönlendirdik. Chat WebSocket endpoint’i 2 saatlik timeout ile açık kalabiliyor, bu gerçek bir chat uygulaması için makul bir değer.
Performans İzleme ve Hata Ayıklama
Sorun çıktığında neye bakacağınızı bilmek çok önemli.
# WebSocket handshake hatalarını izle
tail -f /var/log/nginx/error.log | grep -E "(upgrade|websocket|101)"
# Aktif bağlantı sayısını anlık izle
watch -n 2 'ss -s && echo "---" && cat /proc/$(cat /var/run/nginx.pid)/net/sockstat'
# Nginx worker başına bağlantı durumu
ps aux | grep nginx | grep worker
cat /proc/$(pgrep -d, -x nginx)/net/sockstat
# WebSocket bağlantısını elle test et
curl -i -N
-H "Connection: Upgrade"
-H "Upgrade: websocket"
-H "Host: app.example.com"
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ=="
-H "Sec-WebSocket-Version: 13"
https://app.example.com/ws/
Başarılı bir WebSocket handshake’de HTTP/1.1 101 Switching Protocols cevabı görmelisiniz. 200 OK geliyorsa Nginx Upgrade başlığını backend’e iletmiyor demektir, konfigürasyonu kontrol edin.
Sık Karşılaşılan Sorunlar ve Çözümleri
502 Bad Gateway hatası: Backend’e bağlantı kurulamıyor. proxy_connect_timeout değerini artırın ve backend’in gerçekten o portta dinlediğini doğrulayın.
Bağlantı sürekli kesiliyor: proxy_read_timeout değeri çok düşük. WebSocket için en az 3600s ayarlayın. Ayrıca bazı güvenlik duvarları 60-300 saniye sonra idle bağlantıları kesiyor, bu durumda uygulama tarafında heartbeat mekanizması ekleyin.
ip_hash ile yük dengeleme çalışmıyor: IPv6 adresleri bazen ip_hash’i karıştırır. hash $remote_addr consistent; direktifini deneyin.
Yüksek bağlantı sayısında performans düşüşü: worker_connections değerini artırın ve ulimit -n limitini kontrol edin.
# worker_connections ve dosya limiti kontrolü
nginx -V 2>&1 | grep -o 'with-[^ ]*'
cat /proc/$(cat /var/run/nginx.pid)/limits | grep "open files"
# Sistem limiti artırma
echo "nginx soft nofile 65535" >> /etc/security/limits.conf
echo "nginx hard nofile 65535" >> /etc/security/limits.conf
# nginx.conf'ta
worker_processes auto;
events {
worker_connections 65535;
use epoll;
multi_accept on;
}
Sonuç
Nginx ile WebSocket ve Long Polling konfigürasyonu ilk bakışta karmaşık görünse de temelde birkaç kritik noktaya dayanıyor: Upgrade başlığının doğru iletilmesi, timeout değerlerinin gerçekçi ayarlanması ve yük dengeleme durumunda session sürekliliğinin sağlanması. HTTP/1.1 protokol versiyonu ve buffering ayarları ise çoğu sorunun kaynağı olan iki detay.
Production’a geçmeden önce konfigürasyonunuzu nginx -t ile test edin, bağlantı limitlerini ve sistem dosya descriptor limitlerini gözden geçirin. Monitoring tarafında aktif WebSocket bağlantı sayısını ve bağlantı kesme sıklığını mutlaka izleyin. Bir chat uygulamasında dakikada 10’dan fazla beklenmedik bağlantı kapanması varsa bir yerde timeout ya da heartbeat sorunu var demektir.
En önemli tavsiye: Geliştirme ortamında her şey çalışıyor diye production’ı ihmal etmeyin. Gerçek yük altında, özellikle güvenlik duvarı ve load balancer’ların olduğu ortamlarda WebSocket bağlantıları bambaşka davranır. Yük testi yapın, wrk veya k6 gibi araçlarla hem WebSocket hem Long Polling endpoint’lerini stres altında test edin ve ancak sonra production’a alın.