Nginx ile WebSocket Bağlantı Sorunları ve Çözümleri

Production ortamında bir WebSocket uygulaması çalıştırıyorsun, her şey güzel görünüyor ama bağlantılar sürekli kopuyor, 502 hatası alıyorsun ya da WebSocket hiç bağlanamıyor. Nginx’in önünde durduğu bu senaryolarda sorunun nerede olduğunu bulmak bazen saatlerce sürebiliyor. Bu yazıda gerçek dünyada karşılaştığım WebSocket bağlantı sorunlarını, debug süreçlerini ve çözümleri ele alacağım.

WebSocket ve Nginx: Neden Sorun Çıkar?

Nginx, varsayılan olarak HTTP/1.0 ile proxy bağlantısı kurar ve HTTP’nin kalıcı bağlantı mekanizmalarını otomatik olarak yönetmez. WebSocket bağlantısı ise HTTP/1.1 üzerinden bir “upgrade” isteğiyle başlar ve kalıcı, çift yönlü bir TCP bağlantısına dönüşür. Bu noktada Nginx’in devreye girmesi, eğer doğru yapılandırılmamışsa bağlantının kopmasına ya da hiç kurulamamasına neden olur.

Temel sorun şu: Nginx bir reverse proxy olarak çalışırken WebSocket handshake’ini doğru iletmezse bağlantı 101 Switching Protocols yerine 400 ya da 502 döner. Ya da bağlantı kurulur ama Nginx’in timeout değerleri WebSocket’in uzun ömürlü yapısıyla çeliştiği için bağlantı aniden düşer.

Temel WebSocket Proxy Konfigürasyonu

Önce doğru çalışan bir temel konfigürasyondan başlayalım. Büyük ihtimalle sorunun kaynağı burada yatıyor.

# /etc/nginx/sites-available/websocket-app
server {
    listen 80;
    server_name ws.example.com;

    location /ws/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        # Bu iki satır kritik - WebSocket upgrade için zorunlu
        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;

        # Timeout değerleri - varsayılanlar WebSocket için yetersiz
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
        proxy_connect_timeout 75s;
    }
}

Bu konfigürasyondaki en kritik nokta proxy_http_version 1.1 satırıdır. Nginx varsayılan olarak HTTP/1.0 kullanır ve HTTP/1.0, WebSocket upgrade mekanizmasını desteklemez. Bu satırı unutursan 400 Bad Request ya da bağlantı reddi alırsın.

Log Analizi ile Sorunu Teşhis Etmek

Sorunu anlamadan çözüme atlamak vakit kaybettirir. Önce loglara bakalım.

Nginx Log Formatını Genişletmek

Varsayılan log formatı WebSocket sorunları için yeterince bilgi vermez. Debug için özel bir format tanımlayalım:

# /etc/nginx/nginx.conf içine http bloğuna ekle
http {
    log_format ws_debug '$remote_addr - $remote_user [$time_local] '
                        '"$request" $status $body_bytes_sent '
                        '"$http_referer" "$http_user_agent" '
                        'upgrade="$http_upgrade" '
                        'connection="$http_connection" '
                        'rt=$request_time uct=$upstream_connect_time '
                        'uht=$upstream_header_time urt=$upstream_response_time';

    access_log /var/log/nginx/ws_access.log ws_debug;
}

Bu formatla WebSocket bağlantılarında upgrade ve connection header’larının doğru iletilip iletilmediğini görebilirsin. Logda upgrade="" görüyorsan sorun headers’da demektir.

Gerçek Zamanlı Log Takibi

# WebSocket bağlantılarını gerçek zamanlı izle
tail -f /var/log/nginx/ws_access.log | grep -E "(101|400|502|upgrade)"

# Sadece hatalı bağlantıları filtrele
tail -f /var/log/nginx/error.log | grep -i "websocket|upgrade|connect"

# Son 100 satırda 502 hatalarını say
tail -100 /var/log/nginx/ws_access.log | awk '{print $9}' | sort | uniq -c

Sık Karşılaşılan Sorunlar ve Çözümleri

1. 502 Bad Gateway Sorunu

Bu en yaygın sorun. Logda şunu görüyorsun:

2024/01/15 14:23:11 [error] 1234#1234: *567 connect() failed (111: Connection refused)
while connecting to upstream, client: 192.168.1.100, server: ws.example.com,
request: "GET /ws/ HTTP/1.1", upstream: "http://127.0.0.1:3000/ws/"

Backend servisi çalışmıyor ya da yanlış port dinliyor demektir. Kontrol edelim:

# Backend servis durumunu kontrol et
systemctl status node-app.service

# Port dinleniyor mu?
ss -tlnp | grep :3000
netstat -tlnp | grep :3000

# Nginx'in upstream'e ulaşabildiğini test et
curl -v http://127.0.0.1:3000/ws/ 
  -H "Upgrade: websocket" 
  -H "Connection: Upgrade" 
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" 
  -H "Sec-WebSocket-Version: 13"

2. Bağlantı Aniden Kopuyor (Timeout)

Kullanıcılar “bağlantı kesildi” şikayeti yapıyor ama log’da belirgin bir hata görmüyorsun. Bu genellikle timeout sorunudur.

# Nginx timeout değerlerini kontrol et
nginx -T | grep -i timeout

# Mevcut bağlantı sayısını izle
watch -n 1 'ss -s | grep -E "estab|TIME-WAIT|CLOSE-WAIT"'

Nginx’in varsayılan proxy_read_timeout değeri 60 saniyedir. WebSocket bağlantısı 60 saniye boyunca hiç veri göndermezse Nginx bağlantıyı keser. Çözüm hem timeout’u artırmak hem de ping/pong mekanizması kullanmaktır:

# /etc/nginx/sites-available/websocket-app
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";

    # Uzun süreli bağlantılar için timeout değerleri
    proxy_read_timeout 86400s;   # 24 saat
    proxy_send_timeout 86400s;
    
    # Keepalive için
    proxy_socket_keepalive on;
    
    # Buffer'ları devre dışı bırak (gerçek zamanlı iletişim için)
    proxy_buffering off;
    proxy_cache off;
}

3. SSL/TLS ile WebSocket (wss://) Sorunu

HTTPS arkasında WebSocket çalıştırıyorsan ve bağlantı kurulamıyorsa konfigürasyona dikkat et:

# /etc/nginx/sites-available/secure-websocket
server {
    listen 443 ssl http2;
    server_name ws.example.com;

    ssl_certificate /etc/letsencrypt/live/ws.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ws.example.com/privkey.pem;

    # wss:// bağlantısı için upstream'e http:// ile bağlanıyoruz
    # (SSL termination Nginx'te yapılıyor)
    location /socket.io/ {
        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;
        
        # SSL arkasındaki gerçek IP için
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

# HTTP'yi HTTPS'e yönlendir ama WebSocket bağlantısını koru
server {
    listen 80;
    server_name ws.example.com;
    return 301 https://$host$request_uri;
}

Load Balancer Arkasında WebSocket

Birden fazla backend sunucu kullanıyorsan WebSocket için sticky session gereklidir. WebSocket bağlantısı başladıktan sonra aynı backend’e gitmesi gerekir.

# /etc/nginx/conf.d/upstream.conf
upstream websocket_backends {
    # ip_hash ile aynı istemci her zaman aynı backend'e gider
    ip_hash;
    
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    server 10.0.0.3:3000;
    
    # Bağlantı sayısı bazlı yük dağılımı için
    # least_conn;  # ip_hash ile birlikte kullanılamaz
    
    keepalive 32;  # Upstream keepalive bağlantı sayısı
}

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

    location /ws/ {
        proxy_pass http://websocket_backends;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        
        # Upstream keepalive için
        proxy_set_header Connection "";
    }
}

Dikkat: ip_hash kullanırken Connection "" ile Connection "upgrade" arasındaki farka dikkat et. WebSocket başlangıç isteğinde upgrade kullanmalısın, keepalive için boş bırakıyorsun. Bu iki direktifi ayrı location bloklarında yönetmek daha temiz bir yaklaşımdır.

Debug Modu ile Derinlemesine Analiz

Sorun hala çözülmediyse Nginx’in debug logunu aktif edelim. Ama dikkat, production’da çok yoğun log üretir.

# Nginx'i debug modda yeniden başlat (test için)
# Önce nginx.conf'a ekle:
# error_log /var/log/nginx/debug.log debug;

# Sadece belirli bir IP için debug log
events {
    debug_connection 192.168.1.100;  # Test eden istemci IP'si
}

# Nginx'i test et ve yeniden yükle
nginx -t && nginx -s reload

# Debug logunu izle
tail -f /var/log/nginx/debug.log | grep -i "upstream|websocket|upgrade|101"

Debug logunda şunu arıyorsun:

# Başarılı WebSocket handshake'i şöyle görünür:
grep "101|switching|upgrade" /var/log/nginx/debug.log

# Header iletimini kontrol et
grep "send header|recv header" /var/log/nginx/debug.log | head -50

Firewall ve Network Sorunları

Bazen sorun Nginx’te değil, ağda olur. WebSocket bağlantısını kesen bir güvenlik duvarı ya da proxy olabilir.

# WebSocket bağlantısını manuel test et
# websocat aracıyla:
apt install websocat  # Ubuntu
websocat ws://ws.example.com/ws/

# ya da wscat ile:
npm install -g wscat
wscat -c ws://ws.example.com/ws/

# Bağlantı kurulup kurulmadığını tcpdump ile izle
tcpdump -i eth0 -w /tmp/ws_capture.pcap 'port 80 and tcp'
# Wireshark ile aç ve WebSocket handshake'i kontrol et

# Iptables kurallarını kontrol et
iptables -L INPUT -n -v | grep -E "80|443|3000"

Connection: upgrade Header’ı ve Proxy Zincirleri

Birden fazla Nginx instance’ı ya da HAProxy gibi bir şey varsa zincir boyunca header’ların doğru iletildiğinden emin olmalısın.

# İç proxy (upstream Nginx veya load balancer)
location /ws/ {
    proxy_pass http://app_server;
    proxy_http_version 1.1;
    
    # map kullanarak dinamik Connection header yönetimi
    # (upgrade olmayan bağlantılar için keepalive desteği)
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
}
# nginx.conf http bloğuna map ekle
http {
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }
    
    # Bu map sayesinde:
    # - WebSocket isteği gelirse Connection: upgrade iletilir
    # - Normal HTTP isteği gelirse Connection: close kullanılır
}

Bu map yapısı hem WebSocket hem de normal HTTP trafiğini doğru şekilde yönetir. Production’da bunu kullanmak en doğru yaklaşımdır.

Sistem Kaynaklı Sorunlar

File Descriptor Limitleri

Çok sayıda WebSocket bağlantısı varsa sistem limitleri devreye girebilir:

# Nginx worker'ının açık dosya sayısını kontrol et
cat /proc/$(cat /var/run/nginx.pid)/limits | grep "open files"

# Sistem genelinde kontrol
ulimit -n
cat /proc/sys/fs/file-max

# Nginx'te worker bağlantı sayısını artır
# nginx.conf:
worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 65535;
    multi_accept on;
    use epoll;
}
# Sistem limitlerini kalıcı olarak artır
cat >> /etc/security/limits.conf << 'EOF'
nginx soft nofile 65535
nginx hard nofile 65535
EOF

# Kernel parametreleri
cat >> /etc/sysctl.conf << 'EOF'
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_keepalive_intvl = 15
EOF

sysctl -p

Gerçek Dünya Senaryosu: Chat Uygulaması Bağlantı Sorunları

Geçen yıl bir chat uygulaması için şu semptomlarla karşılaştım: Kullanıcılar birkaç dakikada bir bağlantı kopması yaşıyor, logda 502 ve timeout hataları görünüyordu. İşte adım adım debug süreci:

# 1. Adım: Hata dağılımını analiz et
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

# Çıktı:
# 45823  200
# 1205   502
# 89     101
# 12     400

# 2. Adım: 502'lerin ne zaman yoğunlaştığını bul
grep " 502 " /var/log/nginx/access.log | 
  awk '{print $4}' | cut -d: -f2 | 
  sort | uniq -c | sort -rn | head -10

# 3. Adım: 502 öncesi neler oluyor?
grep " 502 " /var/log/nginx/access.log | tail -20

# 4. Adım: Backend sağlık durumu
# Upstream response time'larına bak
grep " 502 " /var/log/nginx/ws_access.log | 
  awk '{print $NF}' | sort -n | tail -10

Sonuçta sorun şuydu: Node.js uygulaması her 2 saatte bir garbage collection sırasında 65 saniye boyunca yanıt vermez hale geliyordu. Nginx’in proxy_connect_timeout değeri 60 saniyeydi ve bu süre aşılınca 502 veriyordu. Çözüm hem Node.js tarafında optimizasyon yapmak hem de Nginx’te timeout değerini artırmaktı.

Nginx Sağlık Kontrol Endpoint’i

WebSocket sorunlarını proaktif tespit etmek için bir health check mekanizması kur:

# /etc/nginx/sites-available/websocket-app
server {
    listen 80;
    server_name ws.example.com;

    # Backend sağlık kontrolü için
    location /health {
        proxy_pass http://127.0.0.1:3000/health;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        access_log off;
    }

    # Nginx'in kendi durumu
    location /nginx-status {
        stub_status on;
        allow 127.0.0.1;
        allow 10.0.0.0/8;
        deny all;
    }

    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_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}
# Health check scripti
#!/bin/bash
NGINX_STATUS=$(curl -s http://localhost/nginx-status)
ACTIVE=$(echo "$NGINX_STATUS" | grep "Active connections" | awk '{print $3}')
echo "Aktif bağlantı sayısı: $ACTIVE"

# Backend kontrolü
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/health)
if [ "$HTTP_CODE" != "200" ]; then
    echo "UYARI: Backend sağlık kontrolü başarısız! HTTP: $HTTP_CODE"
    # Alerting sistemine bildir
fi

Sonuç

WebSocket sorunları Nginx’te genellikle üç kategoride toplanır: yanlış header konfigürasyonu, timeout değerleri ve sistem limitleri. Çoğu durumda proxy_http_version 1.1, Upgrade ve Connection header’larının doğru ayarlanması ile proxy_read_timeout değerinin artırılması sorunu çözer.

Sorun giderme sıralamasını şöyle özetleyeyim:

  • İlk kontrol: proxy_http_version 1.1 ve upgrade header’ları var mı?
  • İkinci kontrol: Timeout değerleri WebSocket için yeterince uzun mu?
  • Üçüncü kontrol: Backend gerçekten çalışıyor mu, porta ulaşılabiliyor mu?
  • Dördüncü kontrol: SSL/TLS varsa certificate ve protokol uyumu doğru mu?
  • Beşinci kontrol: Sistem limitleri (file descriptor, worker connections) yeterli mi?
  • Altıncı kontrol: Load balancer varsa sticky session açık mı?

Debug sürecinde log formatını zenginleştirmek ve gerçek zamanlı log takibi yapmak en hızlı sonucu verir. websocat ya da wscat gibi araçlarla bağlantıyı manuel test etmek de sorunun Nginx’te mi yoksa uygulamada mı olduğunu hızla anlamana yardımcı olur. Production’da acele kararlar vermek yerine önce logları oku, sonra değişiklik yap.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir