Nginx ile Proxy Yönlendirme Sorunlarını Giderme

Nginx ile proxy yönlendirme yapılandırmak, modern web altyapısının vazgeçilmez bir parçası. Ama iş sorun gidermeye gelince, nerede yanlış gittiğini bulmak bazen gerçek bir kâbusa dönüşebilir. Yıllar içinde onlarca farklı ortamda Nginx proxy sorunlarıyla boğuştum ve her seferinde “bir dahaki sefere not alsaydım” diye düşündüm. İşte bu yüzden bu yazıyı yazıyorum, hem kendime hem de benzer sorunlarla boğuşanlara rehber olsun diye.

Sorunun Köküne İnmeden Önce: Log Yapılandırmasını Doğru Kur

Sorun gidermede ilk adım her zaman aynıdır: doğru loglamayı açmak. Nginx’in varsayılan log formatı yeterli değildir. Proxy sorunlarında upstream cevaplarını, bağlantı sürelerini ve header bilgilerini görmek zorundasınız.

Aşağıdaki log formatını /etc/nginx/nginx.conf dosyanıza ekleyin:

http {
    log_format proxy_debug '$remote_addr - $remote_user [$time_local] '
                           '"$request" $status $body_bytes_sent '
                           '"$http_referer" "$http_user_agent" '
                           'upstream_addr=$upstream_addr '
                           'upstream_status=$upstream_status '
                           'upstream_response_time=$upstream_response_time '
                           'upstream_connect_time=$upstream_connect_time '
                           'upstream_header_time=$upstream_header_time '
                           'request_time=$request_time '
                           'upstream_cache_status=$upstream_cache_status';

    access_log /var/log/nginx/proxy_access.log proxy_debug;
}

Bu formatı aktif ettikten sonra gerçek zamanlı olarak logları izlemek için:

tail -f /var/log/nginx/proxy_access.log | grep -E "upstream_status=5|upstream_status=4"

Bu komut sadece hata veren upstream cevaplarını filtreleyerek ekrana döker. 502, 503, 504 gibi sorunları anında yakalamak için hayat kurtarıcıdır.

502 Bad Gateway: En Yaygın Düşman

502 hatası, Nginx’in upstream sunucuya bağlanamadığı veya geçersiz bir cevap aldığı anlamına gelir. Nedenleri çok çeşitli olabilir ama en sık karşılaştıklarımı şöyle sıralayayım:

Upstream servis çalışmıyor: Önce bunu kontrol edin, çünkü en basit sebep çoğunlukla bu olur.

# Upstream servis dinliyor mu kontrol et
ss -tlnp | grep :8080
# veya
netstat -tlnp | grep :8080

# Servis durumunu kontrol et
systemctl status myapp.service

# Nginx error log'una bak
tail -100 /var/log/nginx/error.log | grep "connect() failed"

Socket veya port yanlış yapılandırılmış: Unix socket kullanan Node.js veya PHP-FPM uygulamalarında socket dosyasının yolu sıklıkla yanlış belirtilir.

# PHP-FPM socket dosyasının varlığını kontrol et
ls -la /run/php/php8.1-fpm.sock

# Socket izinlerini kontrol et
stat /run/php/php8.1-fpm.sock

Eğer socket dosyası var ama Nginx erişemiyorsa, izin sorunu vardır. www-data kullanıcısının socket’e erişimi olmalı:

# PHP-FPM pool konfigürasyonunu düzenle
# /etc/php/8.1/fpm/pool.d/www.conf dosyasında:
# listen.owner = www-data
# listen.group = www-data
# listen.mode = 0660

# Değişiklik sonrası reload
systemctl reload php8.1-fpm
systemctl reload nginx

504 Gateway Timeout: Zaman Aşımı Sorunları

504 hatası, upstream sunucunun belirli süre içinde cevap vermediği durumlarda ortaya çıkar. Bu sorunu gidermek için önce hangi aşamada timeout yaşandığını anlamanız gerekir.

Nginx’te birden fazla timeout değeri vardır ve bunlar birbirinden farklı şeyleri kontrol eder:

  • proxy_connect_timeout: Nginx’in upstream’e bağlantı kurmak için beklediği süre
  • proxy_send_timeout: İstek upstream’e gönderilirken iki yazma işlemi arasındaki maksimum süre
  • proxy_read_timeout: Upstream’den cevap beklerken iki okuma işlemi arasındaki maksimum süre
  • keepalive_timeout: Bağlantının keepalive modunda ne kadar açık tutulacağı

Uzun süren işlemler (büyük dosya yükleme, ağır veritabanı sorguları) için timeout değerlerini artırmanız gerekebilir:

# /etc/nginx/conf.d/myapp.conf
server {
    listen 80;
    server_name myapp.example.com;

    location / {
        proxy_pass http://backend:8080;
        
        # Timeout değerlerini artır
        proxy_connect_timeout 60s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;
        
        # Buffer ayarları - büyük response'lar için
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
        
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Gerçek dünya senaryosu: Bir e-ticaret sitesinde toplu sipariş export işlemi her zaman 504 veriyordu. Sorun, backend’in 60 saniyeden uzun süren CSV oluşturma işlemiydi. Timeout değerlerini artırmak kısa vadeli çözümdü, asıl çözüm ise işlemi asenkrona almaktı. Ama acil durumlarda bu yapılandırma değişikliği canınızı kurtarabilir.

Header Sorunları ve X-Forwarded-For Problemi

Proxy arkasındaki uygulamalar genellikle gerçek istemci IP adresine ihtiyaç duyar. Nginx doğru yapılandırılmadığında uygulama hep 127.0.0.1 veya Nginx’in IP adresini görür.

Önce sorunun varlığını doğrulayalım:

# Backend uygulamanızın aldığı header'ları görmek için
# Nginx'e küçük bir debug endpoint ekleyin (geliştirme ortamında)
location /debug-headers {
    add_header Content-Type text/plain;
    return 200 "Host: $hostnReal-IP: $remote_addrnForwarded-For: $http_x_forwarded_forn";
}

Doğru header iletimi için yapılandırma:

# /etc/nginx/conf.d/proxy_params.conf
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_set_header X-Forwarded-Host  $server_name;
proxy_set_header X-Forwarded-Port  $server_port;

# WebSocket desteği için
proxy_http_version 1.1;
proxy_set_header Upgrade    $http_upgrade;
proxy_set_header Connection $connection_upgrade;

Bu dosyayı oluşturduktan sonra her location bloğunda include /etc/nginx/conf.d/proxy_params.conf; ile dahil edebilirsiniz. Tekrar eden yapılandırmaları azaltmanın pratik yolu budur.

Dikkat: Eğer Nginx’in önünde başka bir load balancer veya CDN varsa (CloudFlare, AWS ALB gibi), X-Forwarded-For header’ı zaten ayarlanmış olabilir. $proxy_add_x_forwarded_for mevcut değeri koruyarak kendi IP’sini ekler, bu genellikle istenen davranıştır.

Upstream Bloğu ile Yük Dengeleme Sorunları

Load balancing yapılandırmalarında proxy yönlendirme sorunları daha da karmaşık bir hal alır. Hangi sunucunun hata verdiğini bulmak için upstream loglamasını kullanmanız şarttır.

# Upstream bloğu ile birden fazla backend
upstream myapp_backend {
    least_conn;  # En az bağlantı algoritması
    
    server backend1:8080 weight=3 max_fails=3 fail_timeout=30s;
    server backend2:8080 weight=2 max_fails=3 fail_timeout=30s;
    server backend3:8080 backup;  # Yedek sunucu
    
    keepalive 32;  # Persistent bağlantı sayısı
}

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

    location / {
        proxy_pass http://myapp_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";  # Keepalive için boş bırak
        
        # Hatalı upstream'i devre dışı bırak
        proxy_next_upstream error timeout http_502 http_503;
        proxy_next_upstream_tries 3;
        proxy_next_upstream_timeout 10s;
    }
}

Hangi backend’in sorun yaşadığını gerçek zamanlı izlemek için:

# Her upstream sunucu için ayrı log filtresi
tail -f /var/log/nginx/proxy_access.log | 
    awk '{print $0}' | 
    grep -oP 'upstream_addr=K[^s]+' | 
    sort | uniq -c | sort -rn

Bu komut hangi upstream adresine kaç istek gittiğini sayar. Eğer bir sunucu beklenenden çok daha az istek alıyorsa, fail_timeout süresi dolmamış ve geçici olarak devre dışı bırakılmış olabilir.

SSL Termination ve HTTPS Proxy Sorunları

SSL termination yapan Nginx kurulumlarında sıklıkla karşılaşılan sorun, backend’e HTTP ile gidilmesine rağmen uygulamanın HTTPS redirect döndürmesi ve sonsuz yönlendirme döngüsüne girmesidir.

Sorunun teşhisi:

# Curl ile redirect zincirini kontrol et
curl -Lv https://myapp.example.com 2>&1 | grep -E "Location:|< HTTP"

# Redirect sayısını sınırlandırarak test et
curl -L --max-redirs 5 -v https://myapp.example.com

Çözüm, X-Forwarded-Proto header’ını doğru iletmek ve backend’i buna göre yapılandırmaktır:

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

    ssl_certificate     /etc/ssl/certs/myapp.crt;
    ssl_certificate_key /etc/ssl/private/myapp.key;

    location / {
        proxy_pass http://backend:8080;
        
        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;  # Sabit olarak https yaz
        proxy_set_header X-Forwarded-Port  443;
        
        # HTTP'den HTTPS'e yönlendirmeyi Nginx seviyesinde yap
        # Backend'in kendi redirect'ini yapmasına izin verme
    }
}

# HTTP -> HTTPS yönlendirme
server {
    listen 80;
    server_name myapp.example.com;
    return 301 https://$host$request_uri;
}

Debug Modunu Etkinleştirme ve Detaylı Hata Analizi

Standart loglar yeterli gelmediğinde Nginx’in debug modunu açmak gerekebilir. Ancak dikkatli olun, debug modu çok fazla log üretir ve production’da disk dolumuna neden olabilir.

# Nginx'i debug modunda derlenip derlenmediğini kontrol et
nginx -V 2>&1 | grep -o with-debug

# Debug loglamayı belirli bir IP için etkinleştir (production'da güvenli yöntem)
# /etc/nginx/nginx.conf içinde:
error_log /var/log/nginx/error.log;

events {
    debug_connection 192.168.1.100;  # Sadece bu IP için debug log
}

Hata logunu aktif olarak analiz etmek için birkaç pratik komut:

# Son 1 saatteki kritik hataları say
awk -v date="$(date -d '1 hour ago' '+%Y/%m/%d %H')" 
    '$0 >= date && /[error]/' /var/log/nginx/error.log | 
    grep -oP '[error] K.+' | sort | uniq -c | sort -rn | head -20

# Upstream bağlantı hatalarını filtrele
grep "upstream timed out|connect() failed|upstream prematurely closed" 
    /var/log/nginx/error.log | 
    tail -50

# Belirli bir backend için hata sayısını bul
grep "upstream:" /var/log/nginx/error.log | 
    grep -oP 'upstream: K[^,]+' | 
    sort | uniq -c | sort -rn

WebSocket Proxy Sorunları

WebSocket bağlantılarını proxy’lemek özel yapılandırma gerektirir. En yaygın hata, 101 Switching Protocols yerine 400 Bad Request almaktır.

# WebSocket proxy yapılandırması
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

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

    location /ws/ {
        proxy_pass http://websocket_backend;
        
        proxy_http_version 1.1;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host       $host;
        
        # WebSocket bağlantıları uzun sürebilir
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
        
        # Buffering'i kapat - gerçek zamanlı iletişim için
        proxy_buffering off;
    }
}

WebSocket bağlantısını test etmek için:

# wscat ile WebSocket bağlantısını test et
npm install -g wscat
wscat -c ws://ws.example.com/ws/

# curl ile WebSocket handshake'i kontrol et
curl -v -N 
    -H "Connection: Upgrade" 
    -H "Upgrade: websocket" 
    -H "Sec-WebSocket-Version: 13" 
    -H "Sec-WebSocket-Key: $(openssl rand -base64 16)" 
    http://ws.example.com/ws/

Gerçek Dünya Senaryosu: Mikro Servis Yönlendirmesi

Birkaç ay önce üzerinde çalıştığım bir projede, tek bir domain altında birden fazla mikro servisi proxy’lemek gerekiyordu. Her servis farklı bir path prefix’i kullanıyordu ama bazı servisler bu prefix’i kendi içlerinde beklemiyordu.

# Mikro servis proxy yapılandırması
server {
    listen 80;
    server_name api.example.com;

    # Auth servisi - prefix'i strip et
    location /auth/ {
        proxy_pass http://auth-service:3000/;  # Sondaki / önemli!
        include /etc/nginx/conf.d/proxy_params.conf;
    }

    # User servisi - prefix'i koru
    location /users {
        proxy_pass http://user-service:3001;  # Sondaki / yok
        include /etc/nginx/conf.d/proxy_params.conf;
    }

    # API Gateway - regex ile yönlendirme
    location ~ ^/v([0-9]+)/(.*)$ {
        proxy_pass http://api-gateway:8080/api/v$1/$2;
        include /etc/nginx/conf.d/proxy_params.conf;
    }
}

Bu yapılandırmada /auth/ ile /auth arasındaki fark kritiktir. proxy_pass URL’inin sonundaki / karakteri, location prefix’inin strip edilip edilmeyeceğini belirler.

Yapılandırmayı test etmek için nginx -t yetmez, gerçek istekler göndererek kontrol etmek gerekir:

# Her endpoint'i test et
for path in "/auth/login" "/users/profile" "/v2/products/list"; do
    echo -n "Testing $path: "
    curl -s -o /dev/null -w "%{http_code} -> upstream: %{url_effective}n" 
        http://api.example.com$path
done

Yapılandırma Değişikliklerini Güvenle Test Etme

Production’da değişiklik yapmadan önce yapılandırmayı doğrulamak için:

# Sözdizimi kontrolü
nginx -t

# Ayrıntılı kontrol
nginx -T | grep -A5 "server_name api.example.com"

# Reload öncesi mevcut worker process'leri kontrol et
ps aux | grep nginx

# Graceful reload (aktif bağlantıları kesmez)
systemctl reload nginx
# veya
nginx -s reload

# Reload sonrası log'u izle
tail -f /var/log/nginx/error.log

Eğer değişiklik sonrası sorun çıkarsa, eski yapılandırmaya hızlıca dönmek için:

# Yapılandırma yedekleme alışkanlığı
cp /etc/nginx/conf.d/myapp.conf /etc/nginx/conf.d/myapp.conf.bak.$(date +%Y%m%d%H%M)

# Git ile versiyon kontrolü (gerçekten öneriyorum)
cd /etc/nginx
git init
git add .
git commit -m "Initial nginx config"
# Her değişiklik öncesi commit al

Sonuç

Nginx proxy sorunlarını gidermek sistematik bir yaklaşım gerektirir. Log formatını zenginleştirmekle başlayın, çünkü doğru log olmadan karanlıkta el yordamıyla çalışırsınız. 502 ve 504 hatalarının farklı kökleri olduğunu unutmayın; biri upstream’e ulaşamamakla, diğeri zaman aşımıyla ilgilidir.

Header yönetimi, özellikle SSL termination ortamlarında sessiz sedasız sorunlara yol açabilir. X-Forwarded-Proto ve X-Real-IP header’larını her zaman kontrol edin. WebSocket ve uzun süreli bağlantılar için buffer ve timeout değerlerini varsayılan bırakmayın.

En önemli tavsiyem şu: /etc/nginx dizininizi mutlaka Git ile versiyon kontrolüne alın. Bir değişiklik her şeyi bozduğunda, git diff ile neyi değiştirdiğinizi görmek priceless bir şeydir. Ve asla production’da test etmeyin, bir staging ortamı kurmak zaman kaybı değil zaman kazancıdır.

Bir yanıt yazın

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