Nginx 504 Gateway Timeout: Upstream Sorunlarını Tespit ve Çözme

Gece 2’de telefonun çalıyor, monitoring alarmları üst üste geliyor ve loglarına baktığında her yerde aynı şeyi görüyorsun: 504 Gateway Timeout. Bu hata, Nginx’in arkasındaki upstream sunucudan (PHP-FPM, Gunicorn, uWSGI, başka bir servis) zamanında yanıt alamaması durumunda ortaya çıkar. Sorun basit gibi görünse de kök nedeni bulmak bazen gerçek bir dedektiflik işine dönüşebilir. Bu yazıda 504 hatalarını sistematik olarak nasıl çözeceğini, log analizinden timeout ayarlarına, upstream health check’ten uygulama profiling’e kadar her şeyi ele alacağız.

504 Gateway Timeout Nedir, Neden Olur?

Nginx bir reverse proxy olarak çalıştığında, istemciden gelen isteği upstream’e (backend sunucusuna) iletir ve yanıt bekler. Eğer bu bekleme süresi proxy_read_timeout değerini aşarsa, Nginx istemciye 504 döner. Bu kadar basit.

Ama sorunun kaynağı hiç de basit değil. 504 almanın başlıca nedenleri şunlar:

  • Yavaş veritabanı sorguları: En yaygın sebep. Bir sorgu indekssiz tam tablo taraması yapıyordur.
  • Upstream servisinin meşgul olması: PHP-FPM worker’ları dolmuş, yeni istek kabul edemiyordur.
  • Ağ sorunları: Nginx ile upstream arasında paket kaybı veya gecikme vardır.
  • Uygulama kodundaki bloklamalar: Senkron HTTP çağrıları, dosya kilitleri, mutex beklemeleri.
  • Memory/CPU baskısı: Sunucu kaynak yetersizliğinden dolayı yanıt üretemiyordur.
  • DNS çözümleme sorunları: Upstream hostname kullanıyorsan DNS gecikmesi devreye girebilir.

Önce Logları Okumayı Öğren

Her şeyden önce, Nginx error loguna bakman gerekiyor. Default lokasyon /var/log/nginx/error.log olsa da production ortamında virtual host bazlı ayrı log dosyaları kullanmak çok daha mantıklı.

# Son 100 satırı real-time takip et
tail -f /var/log/nginx/error.log | grep "upstream timed out"

# Belirli bir zaman aralığındaki 504'leri say
grep "upstream timed out" /var/log/nginx/error.log | grep "2024/01/15" | wc -l

# Hangi upstream'den geldiğini gör
grep "upstream timed out" /var/log/nginx/error.log | awk '{print $NF}' | sort | uniq -c | sort -rn

Access log’da ise şöyle bir şey görürsün:

# 504 dönen istekleri filtrele ve response time ile birlikte listele
awk '$9 == 504 {print $7, $10}' /var/log/nginx/access.log | sort -k2 -rn | head -20

Bu komut sana hangi endpoint’lerin en uzun sürdüğünü ve 504 verdiğini gösterir. Eğer access log formatında $request_time ve $upstream_response_time yoksa, önce bunu eklemen gerekiyor.

# /etc/nginx/nginx.conf veya virtual host config
log_format detailed '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    'rt=$request_time urt=$upstream_response_time '
                    'uct=$upstream_connect_time uht=$upstream_header_time';

access_log /var/log/nginx/access.log detailed;

Bu format ile artık her isteğin ne kadar sürdüğünü, upstream’in yanıt vermesinin ne kadar aldığını görebilirsin. urt (upstream response time) ile rt (toplam request time) arasındaki fark, Nginx’in kendi overhead’ini gösterir.

Timeout Değerlerini Anlamak ve Ayarlamak

Nginx’te upstream ile ilgili birden fazla timeout değeri var ve bunları karıştırmak çok kolay.

  • proxy_connect_timeout: Nginx’in upstream’e TCP bağlantısı kurması için maksimum süre. Default 60 saniye. Genellikle 5-10 saniye yeterli.
  • proxy_send_timeout: Nginx’in upstream’e istek göndermesi için maksimum süre. Default 60 saniye.
  • proxy_read_timeout: Upstream’den yanıt okumak için maksimum süre. Bu en kritik olanı. Default 60 saniye.
  • fastcgi_read_timeout: FastCGI (PHP-FPM) için aynı şey.
  • uwsgi_read_timeout: uWSGI için.
# /etc/nginx/conf.d/myapp.conf
upstream backend {
    server 127.0.0.1:9000;
    keepalive 32;
}

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

    location / {
        proxy_pass http://backend;
        
        proxy_connect_timeout 10s;
        proxy_send_timeout 30s;
        proxy_read_timeout 120s;
        
        # Upstream'e bağlanamadığında veya 502/504 alındığında bir sonraki sunucuya geç
        proxy_next_upstream error timeout http_502 http_504;
        proxy_next_upstream_tries 3;
        proxy_next_upstream_timeout 30s;
    }
    
    # Yavaş endpoint'ler için özel timeout (örneğin rapor oluşturma)
    location /api/reports {
        proxy_pass http://backend;
        proxy_read_timeout 300s;
    }
}

Önemli bir uyarı: Timeout değerlerini körü körüne artırmak çözüm değil. Eğer bir endpoint gerçekten 5 dakika sürüyorsa, asıl sorunu çözmek yerine timeout’u artırmak kullanıcıyı 5 dakika bekletmek demek. Timeout ayarları geçici bir çözüm olabilir, ama kök nedenin üzerini örtmemeli.

PHP-FPM ile Yaşanan 504 Sorunları

PHP-FPM kullanan ortamlarda 504’ün en yaygın sebebi worker havuzunun dolması. PHP-FPM, aynı anda işleyebileceği istek sayısını pm.max_children ile sınırlar.

# PHP-FPM status sayfasını kontrol et (önce nginx config'de aktif etmen lazım)
curl http://127.0.0.1/fpm-status

# PHP-FPM log'larına bak
tail -f /var/log/php8.1-fpm.log

# Kaç tane PHP-FPM worker çalışıyor
ps aux | grep php-fpm | grep -v grep | wc -l

PHP-FPM havuzu dolduğunda yeni istekler kuyruğa girer. Kuyruk da dolunca istekler reddedilir ve Nginx 504 alır. Bu durumda şu log mesajını görürsün:

[pool www] server reached pm.max_children setting (50), consider raising it
# /etc/php/8.1/fpm/pool.d/www.conf

[www]
pm = dynamic
pm.max_children = 100
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30
pm.max_requests = 500

; Her isteğin max kaç saniye sürebileceği
request_terminate_timeout = 120

; Yavaş istekleri logla (5 saniyeden uzun sürenler)
slowlog = /var/log/php8.1-fpm-slow.log
request_slowlog_timeout = 5s

request_slowlog_timeout ayarını mutlaka aktif et. Slow log, hangi PHP fonksiyonlarının ne kadar sürdüğünü stack trace ile birlikte gösterir. Bu, darboğazı bulmak için altın değerinde bir bilgi.

# Slow log'u analiz et
cat /var/log/php8.1-fpm-slow.log | grep "script_filename" | sort | uniq -c | sort -rn | head -10

Nginx Upstream Health Check ve Upstream Bloğu Optimizasyonu

Birden fazla upstream sunucusu varsa veya upstream arasında sıkıntı varsa şunları yapabilirsin:

upstream backend_pool {
    # Bağlantı sayısına göre yük dağılımı
    least_conn;
    
    server 10.0.0.1:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 10.0.0.2:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 10.0.0.3:8080 backup;
    
    # TCP keepalive - bağlantıları tekrar kullan
    keepalive 64;
    keepalive_requests 1000;
    keepalive_time 1h;
}

server {
    location / {
        proxy_pass http://backend_pool;
        
        # HTTP 1.1 keepalive için zorunlu
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        
        # Upstream headers
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

max_fails ve fail_timeout değerleri kritik. Bir upstream max_fails sayısında başarısız olursa, Nginx onu fail_timeout süresince devre dışı bırakır. Bu sayede bozuk bir upstream’e istek gitmeye devam etmez.

Veritabanı Kaynaklı 504’ler

Gerçek dünyada karşılaştığım 504 vakalarının yaklaşık %60’ının sebebi yavaş veritabanı sorgularıdır. Uygulama server’ı işlemek için veritabanını bekler, veritabanı yavaş kalır, timeout aşılır.

# MySQL/MariaDB - şu an çalışan uzun sorguları gör
mysql -e "SHOW PROCESSLIST;" | awk '$6 > 10 {print $0}'

# Slow query log aktif mi kontrol et
mysql -e "SHOW VARIABLES LIKE 'slow_query%';"
mysql -e "SHOW VARIABLES LIKE 'long_query_time';"

# Slow query logunu analiz et
mysqldumpslow -s t -t 20 /var/log/mysql/mysql-slow.log

# PostgreSQL için uzun çalışan sorgular
psql -c "SELECT pid, now() - query_start AS duration, query FROM pg_stat_activity WHERE state = 'active' AND now() - query_start > interval '5 seconds' ORDER BY duration DESC;"

Veritabanı bağlantı havuzu da önemli. Eğer uygulaman her istek için yeni bir veritabanı bağlantısı açıyorsa, bu hem yavaş hem de kaynak israfı. PgBouncer (PostgreSQL) veya ProxySQL (MySQL) gibi connection pooler kullanmayı düşünmelisin.

# PgBouncer durumunu kontrol et
psql -p 6432 pgbouncer -c "SHOW POOLS;"
psql -p 6432 pgbouncer -c "SHOW STATS;"

Gerçek Dünya Senaryosu: E-ticaret Sitesinde Sabah 504 Patlaması

Birkaç ay önce bir e-ticaret sitesinde her sabah 09:00-09:30 arasında 504 patlaması yaşanıyordu. Gece boyunca hiç sorun yok, tam iş saatinin başında kriz. Log analizine baktım:

# Saatlik 504 dağılımı
awk '$9 == 504 {split($4,a,":"); print a[2]}' /var/log/nginx/access.log | sort | uniq -c

Sonuç net: 09 saatinde 847 adet 504, diğer saatlerde 0 ile 15 arasında.

Slow log’a baktım, tüm yavaş istekler /dashboard ve /reports endpoint’lerinden geliyordu. Uygulama koduna baktım: Her sabah 09:00’da bir cron job tetikleniyordu ve bu cron job, raporlama için dev bir JOIN sorgusu çalıştırıyordu. Bu sorgu çalışırken veritabanı diğer isteklere yavaş yanıt veriyordu.

Çözüm üç adımda geldi:

# 1. Sorguya indeks ekle
mysql -e "CREATE INDEX idx_orders_created_at ON orders(created_at, status);"

# 2. Cron zamanını trafiğin düşük olduğu saate taşı
crontab -e
# 0 9 * * * /app/reports/generate.sh
# Yerine:
# 0 3 * * * /app/reports/generate.sh

# 3. Raporlama sorgularını read replica'ya yönlendir

Hem Nginx timeout’u ayarlamak hem de kök nedeni çözmek gerekti. Sadece timeout’u artırsaydık, cron çalışırken tüm kullanıcılar 2-3 dakika bekleyecekti.

Sistem Kaynaklarını Kontrol Et

504 bazen tamamen sistem kaynağı sorunudur. Upstream sunucusu CPU veya memory baskısı altındaysa yanıt üretemez.

# CPU yükü
top -b -n1 | head -20
vmstat 1 10

# Memory kullanımı
free -h
cat /proc/meminfo | grep -E "MemTotal|MemFree|MemAvailable|SwapTotal|SwapFree"

# I/O beklemesi (wa değeri yüksekse disk I/O sorunu var)
iostat -x 1 5

# Hangi process en çok kaynak kullanıyor
ps aux --sort=-%cpu | head -15
ps aux --sort=-%mem | head -15

# Dosya tanımlayıcı limiti kontrol et
ulimit -n
cat /proc/$(pgrep nginx | head -1)/limits | grep "open files"

# Aktif TCP bağlantı durumları
ss -s
netstat -an | awk '{print $6}' | sort | uniq -c | sort -rn

Eğer wa (I/O wait) yüksekse, muhtemelen disk okuma/yazma darboğazı var. SSD’ye geçmek veya veritabanı buffer’larını artırmak çözüm olabilir.

# Nginx worker process limitleri
# /etc/nginx/nginx.conf
worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

Upstream Bağlantısını Doğrudan Test Et

Nginx’i bypass ederek upstream’e doğrudan bağlanmak sorunun nerede olduğunu netleştirir.

# HTTP upstream'e doğrudan bağlan
curl -v --max-time 10 http://127.0.0.1:8080/api/health

# Yanıt süresi ile birlikte test et
curl -o /dev/null -s -w "Connect: %{time_connect}snTTFB: %{time_starttransfer}snTotal: %{time_total}sn" http://127.0.0.1:8080/

# Belirli endpoint'i test et
time curl -o /dev/null -s http://127.0.0.1:8080/api/users

# TCP bağlantısını test et
nc -zv 127.0.0.1 8080
telnet 127.0.0.1 8080

Eğer direkt bağlantıda yanıt hızlıysa, sorun Nginx konfigürasyonunda veya Nginx ile upstream arasındaki ağda. Eğer direkt bağlantıda da yavaşsa, sorun tamamen upstream uygulamasında veya veritabanında.

Monitoring ve Alerting Kurulumu

504 sorunlarını proaktif olarak yakalamak için monitoring şart.

# Prometheus + Nginx Exporter için Nginx status sayfası
# /etc/nginx/conf.d/status.conf
server {
    listen 127.0.0.1:8090;
    
    location /nginx_status {
        stub_status on;
        access_log off;
        allow 127.0.0.1;
        deny all;
    }
}

Basit bir bash script ile kendi alerting’ini de yapabilirsin:

#!/bin/bash
# /usr/local/bin/check_504.sh

THRESHOLD=10
LOG_FILE="/var/log/nginx/access.log"
ALERT_EMAIL="[email protected]"

# Son 5 dakikadaki 504 sayısı
RECENT_504=$(awk -v d="$(date -d '5 minutes ago' '+%d/%b/%Y:%H:%M')" 
    '$4 >= "["d && $9 == "504"' "$LOG_FILE" | wc -l)

if [ "$RECENT_504" -gt "$THRESHOLD" ]; then
    echo "ALERT: Son 5 dakikada $RECENT_504 adet 504 hatası!" | 
        mail -s "504 Gateway Timeout Alert" "$ALERT_EMAIL"
    
    # Aynı zamanda logla
    echo "$(date): 504 count=$RECENT_504" >> /var/log/504_alerts.log
fi
# Crontab'a ekle - her 5 dakikada çalışsın
*/5 * * * * /usr/local/bin/check_504.sh

Nginx Konfigürasyon Debugging Araçları

# Nginx konfigürasyonunu test et
nginx -t
nginx -T | grep -A5 "upstream"

# Nginx'i reload et (yeniden başlatmaya gerek yok)
nginx -s reload

# Aktif bağlantıları ve upstream durumunu kontrol et (Nginx Plus veya third-party module gerekir)
# Open source Nginx için:
curl http://127.0.0.1:8090/nginx_status

# Hangi Nginx modülleri yüklü
nginx -V 2>&1 | tr -- '-' 'n' | grep module

# Debug log ile upstream sorununu detaylı izle (production'da dikkatli kullan!)
# nginx.conf içine:
# error_log /var/log/nginx/error.log debug;

debug log level’ı production’da çok fazla yazı üretir, disk dolar. Sadece sorun anında kısa süreliğine kullan ve hemen warn veya error‘a geri dön.

Uzun Süren İşlemler İçin Alternatif Yaklaşımlar

Bazı işlemler gerçekten uzun sürer: rapor oluşturma, büyük dosya işleme, toplu e-posta gönderimi gibi. Bu tür işlemleri senkron HTTP isteklerinde tutmak yanlış.

Doğru yaklaşım asenkron işleme:

  • İstek gelir, bir queue’ya (Redis, RabbitMQ, SQS) job eklenir, hemen 202 Accepted döner.
  • Background worker job’ı alır, işler.
  • İstemci polling veya WebSocket ile sonucu takip eder.

Bu mimari değişikliği gerektiriyor ama 504 sorunlarını kökten çözer. Celery (Python), Sidekiq (Ruby), Bull (Node.js) gibi kütüphaneler bu iş için yaygın kullanılır.

Sonuç

504 Gateway Timeout, yüzey görüntüsü basit ama kök nedeni bulmak çok katmanlı bir sorun. Sistematik yaklaşım şu sırayla işler:

  • Önce log analizi ile sorunu doğrula ve hangi endpoint’lerin etkilendiğini bul.
  • Upstream’i bypass ederek Nginx’in mi yoksa uygulamanın mı yavaş olduğunu belirle.
  • Sistem kaynaklarını (CPU, memory, I/O) kontrol et.
  • PHP-FPM veya uygulama server loglarına bak.
  • Veritabanı slow query loglarını incele.
  • Geçici çözüm olarak timeout’ları ayarla ama bunu kalıcı çözüm sanma.
  • Kök nedeni bul ve düzelt.

Timeout değerlerini artırmak her zaman cazip gelir, anlık krizi çözer gibi görünür. Ama bu, “ateşlemesi geç olan silahı daha uzun bekliyorum” demek gibi. Asıl iş, neden geciktiğini bulmak. Monitoring kurarak sorunları proaktif yakalamak ve gelecekte aynı sabah 2 krizini yaşamamak ise ayrı bir kazanım.

Bir yanıt yazın

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