Nginx Rate Limiting Kaynaklı 429 Hatası Nasıl Çözülür?
Sunucuna bir bak, loglar 429’larla dolup taşıyor. Kullanıcılar “neden site çalışmıyor” diye yazıyor, siz de nginx’in rate limiting kurallarıyla boğuşuyorsunuz. Bu senaryo her sysadmin’in bir noktada yaşadığı klasik bir durum. 429 Too Many Requests hatası kötü bir şey değil aslında, aksine sistemin kendini koruduğunun işareti. Ama yanlış ayarlanmışsa meşru kullanıcıları da blokluyor, bu da ciddi bir sorun haline geliyor. Gelin bu hatayı kökten anlayalım, logları nasıl okuyacağımızı öğrenelim ve gerçek dünya senaryolarıyla çözümleri ele alalım.
429 Hatası Nedir ve Nginx Bunu Neden Üretir
HTTP 429 status kodu, RFC 6585 ile standartlaşmış bir yanıt kodudur ve “istemci çok fazla istek gönderdi” anlamına gelir. Nginx bu kodu kendi rate limiting modülü olan ngx_http_limit_req_module aracılığıyla üretir. Temel mantık şu: belirli bir zaman diliminde belirli bir kaynaktan gelen istek sayısı tanımlı eşiği aşarsa, fazla istekler ya kuyruğa alınır (delay) ya da direkt reddedilir (reject) ve istemciye 429 döner.
Nginx’in rate limiting mekanizması “leaky bucket” (sızdıran kova) algoritmasını kullanır. Kovaya su (istek) dolarken, kovadan sabit bir hızda su sızar (işlenir). Kova taşarsa, yeni gelen su (istek) dökülür yani reddedilir. Bu mekanizma DDoS saldırılarına, bot trafiğine ve kötü yazılmış API istemcilerine karşı ilk savunma hattıdır.
Log Dosyalarını Okumak ve 429’ları Tespit Etmek
Sorun gidermeye her zaman loglardan başlayın. Nginx’in varsayılan log dosyaları genellikle şu konumlardadır:
- /var/log/nginx/access.log: Tüm HTTP isteklerinin kaydı
- /var/log/nginx/error.log: Rate limiting kısıtlamaları dahil hata kayıtları
429 hatalarını access.log’dan hızlıca çekmek için:
# Kaç adet 429 var, genel sayım
grep ' 429 ' /var/log/nginx/access.log | wc -l
# Son 100 429 hatasını görüntüle
grep ' 429 ' /var/log/nginx/access.log | tail -100
# IP adreslerine göre grupla ve sırala
grep ' 429 ' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20
# Belirli bir zaman aralığındaki 429'lar (örnek: son 1 saat)
awk -v d="$(date -d '1 hour ago' '+%d/%b/%Y:%H')" '$4 > "["d {print}' /var/log/nginx/access.log | grep ' 429 '
Error.log’da rate limiting kayıtları biraz farklı görünür. Nginx burada “limiting requests” ifadesini kullanır:
# Error log'dan rate limiting kayıtlarını çek
grep "limiting requests" /var/log/nginx/error.log | tail -50
# Zone bazında grupla
grep "limiting requests" /var/log/nginx/error.log | grep -oP 'zone "[^"]+"' | sort | uniq -c | sort -rn
# Gerçek zamanlı izleme
tail -f /var/log/nginx/error.log | grep --line-buffered "limiting"
Tipik bir error.log satırı şöyle görünür:
2024/01/15 14:23:45 [error] 12345#12345: *98765 limiting requests, excess: 5.432 by zone "api_zone", client: 192.168.1.100, server: api.example.com, request: "POST /api/v1/login HTTP/1.1", host: "api.example.com"
Bu satırı okuyalım: excess: 5.432 değeri kovadaki fazlalık miktarını gösterir. zone "api_zone" hangi rate limit zone’unun tetiklendiğini belirtir. client: 192.168.1.100 ise kısıtlanan IP adresidir.
Nginx Rate Limiting Konfigürasyonu Anlamak
Sorunun köküne inmek için konfigürasyonu iyi anlamak gerekiyor. Tipik bir rate limiting ayarı şöyle görünür:
http {
# Zone tanımı: isim, anahtar, bellek boyutu, hız limiti
limit_req_zone $binary_remote_addr zone=genel:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=api_zone:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=login_zone:1m rate=1r/m;
# Log seviyesi ayarı (varsayılan error, warn yapılabilir)
limit_req_log_level warn;
# Varsayılan 503 yerine 429 dön
limit_req_status 429;
server {
listen 80;
server_name api.example.com;
location /api/ {
limit_req zone=api_zone burst=20 nodelay;
proxy_pass http://backend;
}
location /api/v1/login {
limit_req zone=login_zone burst=5;
proxy_pass http://backend;
}
location / {
limit_req zone=genel burst=50 nodelay;
proxy_pass http://backend;
}
}
}
Buradaki kritik parametreleri açıklayalım:
- rate=10r/s: Saniyede 10 istek kabul edilir
- rate=1r/m: Dakikada 1 istek kabul edilir
- burst=20: Kova kapasitesi 20 istektir, yani anlık spike’larda 20 istek kuyruğa alınabilir
- nodelay: Burst kapsamındaki istekler geciktirilmeden işlenir, ama burst dolunca direkt reddedilir
- zone=login_zone:1m:
1mbellek boyutudur, yaklaşık 16.000 IP adresi tutabilir
nodelay olmadan burst davranışı farklıdır: burst=20 ise 20 istek kuyruğa alınır ve bunlar rate’e göre gecikmeyle işlenir. nodelay ile kuyruktaki istekler hemen işlenir ama kova yine de dolar.
Gerçek Dünya Senaryosu 1: API Gateway Arkasındaki Yük Dengeleyici Sorunu
En sık karşılaşılan sorunlardan biri şudur: tüm trafiğin tek bir IP’den geliyor gibi görünmesi. Bir load balancer veya reverse proxy arkasında çalışıyorsanız ve X-Forwarded-For header’ını düzgün okumuyorsanız, nginx herkesi aynı IP’den geliyor sanır ve meşru kullanıcıları bloklarsınız.
Bu durumu tespit etmek için:
# Tüm 429'ların tek bir IP'den gelip gelmediğini kontrol et
grep ' 429 ' /var/log/nginx/access.log | awk '{print $1}' | sort -u
# Eğer sadece 1-2 IP varsa ve bunlar load balancer IP'leri ise sorun budur
# Load balancer IP'lerini /etc/nginx/nginx.conf'ta kontrol et
grep -r "real_ip|X-Forwarded" /etc/nginx/
Çözüm olarak gerçek IP’yi okuyacak şekilde konfigürasyonu güncelleyin:
http {
# Güvenilir proxy IP'leri tanımla
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
# Cloudflare kullanıyorsanız onların IP aralıklarını da ekleyin
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
# Hangi header'dan okuyacağını belirt
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# Artık rate limiting gerçek IP'ye göre çalışır
limit_req_zone $binary_remote_addr zone=api_zone:10m rate=100r/s;
}
Gerçek Dünya Senaryosu 2: Mobil Uygulama Login Sorunu
Bir e-ticaret şirketinde login endpoint’i dakikada 1 istek ile sınırlanmıştı (rate=1r/m). Güvenlik açısından mantıklı. Ama mobil uygulama bir güncelleme sonrasında her açılışta token yenileme isteği atıyordu ve kullanıcılar sürekli 429 alıyordu. Sorunu tespit edip çözmek için şu adımları izledik:
# Login endpoint'ine gelen isteklerin dağılımı
grep '"POST /api/v1/login' /var/log/nginx/access.log |
awk '{print $1, $4}' |
sed 's/[//' |
awk -F: '{print $1":"$2}' |
sort | uniq -c | sort -rn | head -20
# Aynı kullanıcıdan saniyede kaç istek geliyor, User-Agent'a göre grupla
grep '"POST /api/v1/login' /var/log/nginx/access.log |
grep ' 429 ' |
awk -F'"' '{print $6}' |
sort | uniq -c | sort -rn
Çözüm olarak login ve token refresh endpoint’leri için farklı zone’lar tanımladık:
http {
# Login denemesi için sıkı limit
limit_req_zone $binary_remote_addr zone=login_strict:1m rate=5r/m;
# Token yenileme için daha gevşek limit
limit_req_zone $binary_remote_addr zone=token_refresh:10m rate=1r/s;
# Genel API limiti
limit_req_zone $binary_remote_addr zone=api_general:10m rate=30r/s;
server {
location /api/v1/login {
limit_req zone=login_strict burst=3 nodelay;
limit_req_status 429;
add_header Retry-After 60;
proxy_pass http://backend;
}
location /api/v1/token/refresh {
limit_req zone=token_refresh burst=10 nodelay;
proxy_pass http://backend;
}
}
}
Retry-After header’ı eklemek önemlidir, istemci uygulamanın ne zaman tekrar deneyeceğini bilmesini sağlar.
Rate Limit Zone Bellek Kullanımını İzlemek
Zone’lar dolduğunda nginx eski kayıtları silmeye başlar, bu da beklenmedik davranışlara yol açabilir. Zone sağlığını izlemek için nginx’in status modülünü kullanabilirsiniz:
# nginx.conf veya ayrı bir server bloğu
server {
listen 8080;
server_name localhost;
location /nginx_status {
stub_status;
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
}
# Status bilgisini çek
curl -s http://localhost:8080/nginx_status
# Shared memory zone kullanımını görmek için (nginx Plus veya özel derleme gerekebilir)
# Alternatif olarak sistem belleğini izle
cat /proc/$(pgrep -f "nginx: master")/status | grep VmRSS
Zone boyutunu hesaplamak için kaba bir formül: her IP kaydı yaklaşık 64 byte tutar. 10m zone = 10 1024 1024 / 64 = yaklaşık 163.000 IP adresi. Bu genellikle yeterlidir ama yüksek trafikli sitelerde artırılması gerekebilir.
Whitelist ve Özel Kurallar
Bazı IP’leri veya koşulları rate limiting’den muaf tutmak sık ihtiyaç duyulan bir durumdur. Dahili monitoring araçları, güvenilir partner API’leri veya kendi ofis IP’niz bu kapsama girebilir:
http {
# Geo modülü ile whitelist
geo $limit {
default 1;
10.0.0.0/8 0;
172.16.0.0/12 0;
192.168.0.0/16 0;
203.0.113.42 0; # Güvenilir partner IP
}
# Whitelist'tekilere boş string, diğerlerine IP döndür
map $limit $limit_key {
0 "";
1 $binary_remote_addr;
}
# Limit key olarak $limit_key kullan
# Boş string için nginx rate limiting uygulamaz
limit_req_zone $limit_key zone=api_zone:10m rate=30r/s;
server {
location /api/ {
limit_req zone=api_zone burst=50 nodelay;
proxy_pass http://backend;
}
}
}
Bu yaklaşım zarif çünkü whitelist’teki IP’ler için $limit_key boş string olur ve nginx boş key için rate limiting yapmaz.
Konfigürasyon Değişikliği Sonrası Test ve Doğrulama
Konfigürasyonu değiştirdikten sonra mutlaka test edin:
# Syntax kontrolü
nginx -t
# Konfigürasyonu yeniden yükle (downtime yok)
nginx -s reload
# veya
systemctl reload nginx
# Rate limiting'i test etmek için basit bir döngü
for i in $(seq 1 20); do
curl -s -o /dev/null -w "%{http_code}n" http://api.example.com/api/v1/test
done
# Daha sofistike test, paralel istekler
seq 1 50 | xargs -P 10 -I{} curl -s -o /dev/null -w "%{http_code}n" http://api.example.com/api/
# Apache Bench ile yük testi
ab -n 1000 -c 50 http://api.example.com/api/v1/test
# 429 sayısını görmek için:
ab -n 1000 -c 50 http://api.example.com/api/v1/test | grep -E "Non-2xx|Complete"
Log Formatını Zenginleştirmek
Varsayılan log formatı bazen yeterli bilgi vermez. Rate limiting sorunlarını daha iyi analiz etmek için log formatını zenginleştirin:
http {
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time '
'uct="$upstream_connect_time" '
'uht="$upstream_header_time" '
'urt="$upstream_response_time" '
'limit_req_status=$limit_req_status '
'x_forwarded_for="$http_x_forwarded_for"';
access_log /var/log/nginx/access.log detailed;
server {
# Zone bazında ayrı log dosyası
location /api/ {
limit_req zone=api_zone burst=20 nodelay;
access_log /var/log/nginx/api_access.log detailed;
proxy_pass http://backend;
}
}
}
$limit_req_status değişkeni nginx 1.17.6+ sürümlerde mevcuttur ve PASSED, DELAYED, REJECTED, DELAYED_DRY_RUN, REJECTED_DRY_RUN değerlerini alır. Bu değişken sayesinde hangi isteklerin kısıtlandığını log’da net şekilde görebilirsiniz.
Dry Run Modu ile Güvenli Test
Yeni rate limiting kurallarını canlı ortamda test etmek risklidir. Nginx’in dry_run özelliği kısıtlama yapmadan sadece log’a yazar:
http {
limit_req_zone $binary_remote_addr zone=yeni_kural:10m rate=50r/s;
server {
location /api/ {
# dry_run: kısıtlamaz, sadece loglar
limit_req zone=yeni_kural burst=100 nodelay;
limit_req_dry_run on;
proxy_pass http://backend;
}
}
}
# Dry run modunda kaç istek kısıtlanırdı?
grep "DRY_RUN" /var/log/nginx/error.log | wc -l
# Hangi IP'ler kısıtlanırdı?
grep "DRY_RUN" /var/log/nginx/error.log | grep -oP 'client: [0-9.]+' | sort | uniq -c | sort -rn
Prometheus ve Grafana ile İzleme
Üretim ortamında 429 hatalarını anlık izlemek için nginx-prometheus-exporter kullanabilirsiniz:
# nginx-prometheus-exporter kurulumu (Docker ile)
docker run -d
--name nginx-exporter
-p 9113:9113
nginx/nginx-prometheus-exporter:latest
--nginx.scrape-uri=http://localhost:8080/nginx_status
# Systemd ile çalıştırmak için
cat > /etc/systemd/system/nginx-exporter.service << 'EOF'
[Unit]
Description=Nginx Prometheus Exporter
After=nginx.service
[Service]
ExecStart=/usr/local/bin/nginx-prometheus-exporter
--nginx.scrape-uri=http://127.0.0.1:8080/nginx_status
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now nginx-exporter
Prometheus’ta 429 oranını izlemek için örnek alert kuralı:
# /etc/prometheus/rules/nginx.yml
cat > /etc/prometheus/rules/nginx_429.yml << 'EOF'
groups:
- name: nginx_rate_limiting
rules:
- alert: HighRateLimitingRate
expr: rate(nginx_http_requests_total{status="429"}[5m]) > 10
for: 2m
labels:
severity: warning
annotations:
summary: "Nginx 429 orani yuksek"
description: "Son 5 dakikada saniyede {{ $value }} adet 429 hatasi"
EOF
Sık Yapılan Hatalar ve Çözümleri
Hata 1: Tüm trafiği tek zone’la yönetmek
Her endpoint’in farklı trafik karakteristiği vardır. Login, API, static asset ve webhook’lar için ayrı zone’lar tanımlayın.
Hata 2: burst değerini çok düşük tutmak
Modern uygulamalar aynı anda paralel istek gönderir (JavaScript’teki Promise.all gibi). burst değeri bu peak’leri absorbe edecek kadar yüksek olmalıdır.
Hata 3: limit_req_log_level’ı error yerine warn yapmamak
- limit_req_log_level error: Çok fazla log, error.log’u kirletir
- limit_req_log_level warn: Daha temiz, hala izlenebilir
- limit_req_log_level info: Sadece bilgi amaçlı, production’da çok gürültülü
Hata 4: Retry-After header’ı göndermemek
İyi yazılmış istemciler bu header’a bakarak ne zaman tekrar deneyeceğini bilir ve gereksiz 429 almaktan kaçınır.
Hata 5: IPv6’yı unutmak
$binary_remote_addr hem IPv4 hem IPv6 için çalışır ama IPv6 subnet’leri için ek kural gerekebilir:
# IPv6 için $http_x_forwarded_for veya custom map kullanmak gerekebilir
map $remote_addr $rate_limit_key {
~^::ffff:(.*)$ $1; # IPv4-mapped IPv6 adreslerini normalize et
default $binary_remote_addr;
}
limit_req_zone $rate_limit_key zone=api_zone:10m rate=30r/s;
Sonuç
Nginx rate limiting kaynaklı 429 hataları, doğru araçlarla sistematik şekilde ele alındığında çözülmesi zor değil. Önemli olan şu sırayı takip etmek: önce logları analiz et, 429’ların hangi zone’dan geldiğini anla, gerçek IP sorununu çöz, ardından iş gereksinimlerine göre limit değerlerini ayarla.
Rate limiting’i tamamen kaldırmak hiçbir zaman doğru çözüm değildir. Aksine, kötü ayarlanmış bir rate limiting kuralını düzeltmek, doğru zone’lar oluşturmak ve whitelist mekanizmalarını kullanmak hem güvenliği korur hem de meşru trafiğin önünü açar.
Canlı ortama geçmeden önce mutlaka dry_run modunu kullanın, değişiklikleri staging’de test edin ve Prometheus gibi bir izleme altyapısıyla 429 oranlarını sürekli takip edin. Bir sysadmin olarak en kötü senaryo, rate limiting’in ne zaman ve neden tetiklendiğinden habersiz olmaktır.
