Nginx 502 Bad Gateway Hatası: Kaynağı ve Çözümü
Sunucuya SSH bağlandığında tarayıcıda o soğuk beyaz sayfayı görüyorsun: 502 Bad Gateway. Nginx bir şeyler söylemeye çalışıyor ama ne söylediğini anlamak bazen gerçek bir dedektiflik işi. Bu yazıda 502 hatasını kökünden çözmeyi, log okumayı ve production ortamında panik yapmadan nasıl davranman gerektiğini konuşacağız.
502 Bad Gateway Nedir, Ne Değildir?
Önce şunu netleştirelim: 502 hatası Nginx’in kendisinden kaynaklanmıyor. Nginx proxy olarak çalışırken arka taraftaki servisten (upstream) geçersiz veya hiç yanıt alamadığında bu hatayı üretiyor. Yani Nginx ayakta, çalışıyor, istekleri alıyor ama PHP-FPM, Gunicorn, Node.js, uWSGI veya herhangi bir backend servisi ya cevap vermiyor ya da bozuk bir cevap dönüyor.
Bunu anlamak önemli çünkü ilk refleks “Nginx’i yeniden başlatayım” oluyor. Yanlış teşhis, yanlış tedavi. Asıl hasta çoğunlukla backend.
Yaygın upstream servisler şunlar:
- PHP-FPM (WordPress, Laravel gibi PHP uygulamaları)
- Gunicorn / uWSGI (Django, Flask)
- Node.js / Express
- Puma / Unicorn (Ruby on Rails)
- Başka bir Nginx veya Apache (reverse proxy zinciri)
İlk Adım: Logları Okumak
Panik yapmadan önce loglara bak. Nginx’in error log’u sana neredeyse her zaman sorunu işaret eder.
# Gerçek zamanlı error log takibi
tail -f /var/log/nginx/error.log
# Son 100 satır
tail -100 /var/log/nginx/error.log
# Sadece upstream hatalarını filtrele
grep "upstream" /var/log/nginx/error.log | tail -50
# Bugünkü 502 hatalarını say
grep "$(date '+%Y/%m/%d')" /var/log/nginx/error.log | grep "502" | wc -l
Tipik bir 502 log satırı şöyle görünür:
2024/01/15 14:23:45 [error] 1234#1234: *567 connect() failed (111: Connection refused)
while connecting to upstream, client: 192.168.1.100, server: example.com,
request: "GET /api/users HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000",
host: "example.com"
Bu satırdan şunları anlıyoruz: Connection refused yani 9000 portunda kimse dinlemiyor. PHP-FPM çalışmıyor veya yanlış portta dinliyor.
Bir diğer yaygın mesaj:
[error] connect() to unix:/var/run/php/php8.1-fpm.sock failed (2: No such file or directory)
Bu da socket dosyasının olmadığını söylüyor. PHP-FPM ya çalışmıyor ya da farklı bir socket path kullanıyor.
Senaryo 1: PHP-FPM Çökmüş veya Çalışmıyor
Production’da WordPress sitesi yönetiyorsun. Sabah 08:00’de müşteri arıyor, site 502 veriyor. İlk kontrol:
# PHP-FPM durumunu kontrol et
systemctl status php8.1-fpm
# Çalışmıyorsa başlat
systemctl start php8.1-fpm
# Journal loglarına bak (son 50 satır)
journalctl -u php8.1-fpm -n 50 --no-pager
# PHP-FPM process var mı?
ps aux | grep php-fpm
PHP-FPM çalışıyor görünüyor ama hata devam ediyorsa socket/port uyumsuzluğu var demektir. Nginx config’ini ve PHP-FPM pool config’ini karşılaştır:
# Nginx upstream tanımına bak
grep -r "fastcgi_pass|proxy_pass" /etc/nginx/sites-enabled/
# PHP-FPM pool config'ini kontrol et
cat /etc/php/8.1/fpm/pool.d/www.conf | grep "listen ="
Nginx’te fastcgi_pass 127.0.0.1:9000 yazıyorsa ama PHP-FPM listen = /run/php/php8.1-fpm.sock kullanıyorsa 502 kaçınılmaz. İkisini eşitlemen lazım. Socket kullanmak genellikle daha performanslı:
# /etc/nginx/sites-available/example.com
server {
listen 80;
server_name example.com;
root /var/www/html;
index index.php;
location ~ .php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
Değişiklik yaptıktan sonra:
# Nginx config syntax kontrolü
nginx -t
# Nginx'i yeniden yükle (reload, restart değil!)
systemctl reload nginx
Senaryo 2: Upstream Timeout Problemi
Django uygulaması yönetiyorsun. Bazı istekler 502 veriyor, bazıları veriyor. Bu intermittent (aralıklı) sorun daha sinsi. Log’a bakıyorsun:
[error] upstream timed out (110: Connection timed out) while reading response header
from upstream, upstream: "http://127.0.0.1:8000"
Gunicorn ayakta ama bazı istekler zaman aşımına uğruyor. Birkaç olası sebep var:
1. Worker sayısı yetersiz:
# Gunicorn kaç worker ile çalışıyor?
ps aux | grep gunicorn | grep -v grep
# Önerilen formül: (2 x CPU sayısı) + 1
nproc --all
2. Worker timeout çok düşük:
Gunicorn’un default timeout’u 30 saniye. Ağır database sorguları veya external API çağrıları bunu aşabilir. Gunicorn servis dosyasını güncelle:
# /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn Django Application
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/var/www/myapp/venv/bin/gunicorn
--workers 5
--timeout 120
--bind 127.0.0.1:8000
--access-logfile /var/log/gunicorn/access.log
--error-logfile /var/log/gunicorn/error.log
myapp.wsgi:application
[Install]
WantedBy=multi-user.target
3. Nginx tarafında da timeout ayarları gerekiyor:
upstream django_app {
server 127.0.0.1:8000;
keepalive 32;
}
server {
location / {
proxy_pass http://django_app;
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 ayarları
proxy_connect_timeout 60s;
proxy_read_timeout 120s;
proxy_send_timeout 60s;
# Buffer ayarları
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 16k;
}
}
Senaryo 3: Upstream’de Tüm Workerlar Meşgul
Trafik ani yükseldi, Node.js uygulaması 502 veriyor. Access log’a bakıyorsun:
# Son 1 dakikadaki istek sayısı
awk -v d="$(date -d '1 minute ago' '+%d/%b/%Y:%H:%M')" '$4 > "["d' /var/log/nginx/access.log | wc -l
# 502 hatası veren URL'leri say
grep " 502 " /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20
Node.js event loop’u tıkanmış olabilir. Önce process durumuna bak:
# Node.js process'lerinin CPU ve bellek kullanımı
ps aux | grep node | grep -v grep
# Açık dosya ve bağlantı sayısı
lsof -p $(pgrep -f "node") | wc -l
# Sistem genelinde bağlantı durumu
ss -s
netstat -an | grep :3000 | grep ESTABLISHED | wc -l
Nginx tarafında upstream için max_fails ve fail_timeout ayarlarını yapılandır. Ayrıca birden fazla worker instance çalıştırıyorsan load balancing konfigürasyonu:
upstream nodejs_cluster {
least_conn; # En az bağlantıya sahip worker'a yönlendir
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3002 max_fails=3 fail_timeout=30s;
keepalive 64;
}
Senaryo 4: Unix Socket İzin Problemi
Bu en can sıkıcı senaryolardan biri. Her şey doğru görünüyor ama Nginx socket’e erişemiyor.
# Socket dosyasının varlığını ve izinlerini kontrol et
ls -la /run/php/php8.1-fpm.sock
# Çıktı örneği:
# srw-rw---- 1 www-data www-data 0 Jan 15 14:00 /run/php/php8.1-fpm.sock
# Nginx hangi kullanıcı ile çalışıyor?
ps aux | grep "nginx: worker" | awk '{print $1}' | head -1
# Genellikle www-data veya nginx kullanıcısı
Eğer Nginx nginx kullanıcısı ile çalışıyorsa ve PHP-FPM socket’i sadece www-data grubuna açıksa sorun çıkar. PHP-FPM pool config’ini düzenle:
; /etc/php/8.1/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data
listen = /run/php/php8.1-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
; Nginx kullanıcısını gruba ekle veya mode'u değiştir
# Nginx kullanıcısını www-data grubuna ekle
usermod -a -G www-data nginx
# PHP-FPM'i yeniden başlat
systemctl restart php8.1-fpm
# Nginx'i yeniden yükle
systemctl reload nginx
Upstream Health Check ve Monitoring
Sorunları önceden yakalamak için basit bir health check scripti işe yarar:
#!/bin/bash
# /usr/local/bin/check_upstream.sh
UPSTREAM_URL="http://127.0.0.1:8000/health"
NGINX_ERROR_LOG="/var/log/nginx/error.log"
ALERT_EMAIL="[email protected]"
# HTTP status kontrolü
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 "$UPSTREAM_URL")
if [ "$HTTP_STATUS" != "200" ]; then
echo "UYARI: Upstream $UPSTREAM_URL yanıt vermiyor! Status: $HTTP_STATUS"
# Son 502 hatalarını al
RECENT_ERRORS=$(grep "502|upstream" "$NGINX_ERROR_LOG" | tail -10)
echo "Son hatalar:"
echo "$RECENT_ERRORS"
# Mail gönder (mailutils kurulu olmalı)
echo -e "Upstream hata!nStatus: $HTTP_STATUSnnSon loglar:n$RECENT_ERRORS" |
mail -s "502 Bad Gateway Alarmı - $(hostname)" "$ALERT_EMAIL"
fi
# Script'i çalıştırılabilir yap
chmod +x /usr/local/bin/check_upstream.sh
# Cron'a ekle (her 2 dakikada bir kontrol)
echo "*/2 * * * * root /usr/local/bin/check_upstream.sh" >> /etc/cron.d/upstream-check
Nginx Upstream Hata Sayfalarını Özelleştir
Production’da çıplak 502 sayfası göstermek profesyonel değil. Kullanıcıya anlamlı bir hata sayfası sun, arka tarafta ise servisi yeniden başlat:
server {
listen 80;
server_name example.com;
# Özel hata sayfaları
error_page 502 503 504 /maintenance.html;
location = /maintenance.html {
root /var/www/error-pages;
internal;
}
# Upstream geçici hata verirse statik dosyaları sun
location / {
proxy_pass http://backend;
proxy_intercept_errors on;
# Upstream çökmüşse önbellekten sun (stale cache)
proxy_cache_use_stale error timeout http_502 http_503;
}
}
Gelişmiş Log Analizi
Sistematik sorun tespiti için log analizi şablonu:
# Belirli bir zaman aralığındaki 502 hatalarını analiz et
# Örnek: Son 1 saatteki hatalar
START_TIME=$(date -d '1 hour ago' '+%d/%b/%Y:%H:%M:%S')
echo "=== 502 Hata Analizi ==="
echo "Toplam 502 sayısı:"
grep " 502 " /var/log/nginx/access.log | wc -l
echo ""
echo "En çok 502 veren IP'ler:"
grep " 502 " /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
echo ""
echo "En çok 502 veren URL'ler:"
grep " 502 " /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -10
echo ""
echo "502 hatalarının saatlik dağılımı:"
grep " 502 " /var/log/nginx/access.log |
awk '{print $4}' | cut -d: -f2 | sort | uniq -c
echo ""
echo "Nginx error log'undaki upstream mesajları:"
grep "upstream" /var/log/nginx/error.log |
grep -oP 'connect() failed.*?upstream' | sort | uniq -c | sort -rn
Hızlı Teşhis Kontrol Listesi
Bir 502 hatası aldığında sırayla bunları kontrol et:
- Backend servis çalışıyor mu?
systemctl status php-fpm / gunicorn / node - Doğru port/socket’i dinliyor mu?
ss -tlnp | grep 9000veyass -tlnp | grep 8000 - Socket dosyası mevcut mu?
ls -la /run/php/*.sock - İzin problemi var mı?
ls -laile owner ve group’u kontrol et - Nginx config doğru mu?
nginx -t - Sistem kaynakları tükenmiş mi?
free -h,df -h,uptime - Ulimit değerleri yeterli mi?
ulimit -naçık dosya limiti - Upstream logları ne diyor?
/var/log/gunicorn/error.logveyajournalctl -u php-fpm
# Tek komutla hızlı sistem durumu özeti
echo "=== Sistem Durumu ===" &&
echo "Uptime: $(uptime)" &&
echo "Bellek: $(free -h | grep Mem)" &&
echo "Disk: $(df -h / | tail -1)" &&
echo "PHP-FPM: $(systemctl is-active php8.1-fpm)" &&
echo "Nginx: $(systemctl is-active nginx)" &&
echo "Açık bağlantılar: $(ss -s | grep estab)"
Önemli Bir Not: Reload vs Restart
Nginx konfigürasyon değişikliklerinde restart yerine reload kullan. restart mevcut bağlantıları keser, reload ise mevcut bağlantıları tamamladıktan sonra yeni config ile çalışmaya devam eder:
# YANLIŞ - mevcut bağlantıları keser
systemctl restart nginx
# DOĞRU - graceful reload
systemctl reload nginx
# veya
nginx -s reload
Backend servislerinde de aynı mantık geçerli. Gunicorn için HUP sinyali graceful restart sağlar:
# Gunicorn graceful restart
kill -HUP $(cat /var/run/gunicorn.pid)
# Systemd ile
systemctl reload gunicorn
Sonuç
502 Bad Gateway hatası ilk gördüğünde stres yaratıyor ama aslında oldukça sistematik bir şekilde çözülebilir. Temel kural şu: Nginx’e değil, upstream’e bak. Log dosyaları neredeyse her zaman sana doğru yönü gösteriyor.
En yaygın senaryoları özetlersek: PHP-FPM, Gunicorn veya başka bir backend servis çökmüş, port/socket uyumsuzluğu var, timeout değerleri düşük, izin problemi mevcut veya sistem kaynakları tükenmiş. Bu beş kategoriyi sistematik olarak kontrol ettiğinde çözümü çok hızlı bulursun.
Production ortamında proaktif olmak her zaman reaktif olmaktan iyidir. Upstream health check, anlamlı hata sayfaları ve log monitoring kurarak 502 hatalarını kullanıcılar size haber vermeden tespit edebilirsin. Bir sonraki 502 geldiğinde, log aç, upstream’i kontrol et, sorunu bul. Hepsi bu.
