API Koruması: Nginx Rate Limiting Yapılandırması
Modern web uygulamalarında API’ler artık sadece bir bileşen değil, uygulamanın kalbi haline geldi. Ve her kalp gibi, korunması gerekiyor. Brute force saldırıları, DDoS denemeleri, scraping botları… Bunların hepsi API’lerinizi hedef alıyor ve Nginx’in rate limiting özelliği bu saldırılara karşı ilk savunma hattınız olabilir. Bu yazıda, production ortamında gerçekten işe yarayan Nginx rate limiting yapılandırmalarını adım adım ele alacağız.
Rate Limiting Neden Bu Kadar Önemli?
Geçen ay bir müşterimin API’si dakikada 50.000 istek aldı. Normal kapasitesi? 500 istek. Sunucu çöktü, veritabanı kilitlendi, on binlerce kullanıcı etkilendi. Basit bir rate limiting kuralı bu felaketi önleyebilirdi.
Rate limiting, belirli bir zaman diliminde bir istemcinin yapabileceği istek sayısını sınırlandırır. Bu sayede:
- Brute force saldırılarını engellersiniz (özellikle login endpoint’leri için kritik)
- DDoS saldırılarının etkisini azaltırsınız
- API maliyetlerinizi kontrol altında tutarsınız (özellikle ücretli upstream servisler kullanıyorsanız)
- Adil kullanım politikası uygularsınız
- Sunucu kaynaklarınızı kötü niyetli veya hatalı istemcilerden korursunuz
Nginx Rate Limiting’in Temelleri
Nginx, rate limiting için ngx_http_limit_req_module modülünü kullanır. Bu modül neredeyse tüm Nginx kurulumlarında varsayılan olarak gelir, ekstra bir kurulum gerektirmez.
Temel mantık şu: Bir zone (paylaşılan bellek bölgesi) tanımlarsınız, bu zone istemci IP’lerini ve istek sayılarını tutar. Sonra bu zone’u istediğiniz location’lara uygularsınız.
İlk önce mevcut kurulumunuzda bu modülün yüklü olup olmadığını kontrol edelim:
nginx -V 2>&1 | grep -o with-http_limit_req_module
# Çıktı: with-http_limit_req_module görünüyorsa hazırsınız
# Alternatif kontrol:
nginx -V 2>&1 | grep limit_req
Temel Rate Limiting Yapılandırması
En basit haliyle bir rate limiting kuralı şöyle görünür. Bu yapılandırmayı /etc/nginx/nginx.conf dosyasının http bloğuna ekleyeceğiz:
http {
# Zone tanımı: 10MB bellek, IP başına 10 istek/saniye
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# İsteğe bağlı: Bağlantı sayısı limiti için
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
listen 80;
server_name api.orneksite.com;
location /api/ {
# Zone'u uygula, 20 istek burst'e izin ver
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 10;
proxy_pass http://backend_servers;
}
}
}
Buradaki parametreleri açıklayalım:
- $binary_remote_addr: İstemci IP’sini binary formatta saklar, $remote_addr’dan daha az yer kaplar
- zone=api_limit:10m: “api_limit” adında 10MB’lık bir zone oluşturur (10MB yaklaşık 160.000 IP adresi tutabilir)
- rate=10r/s: Saniyede 10 istek limiti
- burst=20: Ani yoğunluklar için 20 istek kuyruğa alınabilir
- nodelay: Burst kuyruğundaki istekler geciktirilmeden işlenir
Gelişmiş Zone Stratejileri
Gerçek dünyada tek bir zone yetmez. Farklı endpoint’ler için farklı limitler gerekebilir. İşte daha kapsamlı bir yapılandırma:
http {
# Genel API limiti
limit_req_zone $binary_remote_addr zone=general_api:10m rate=30r/m;
# Login endpoint'leri için çok daha sıkı limit
limit_req_zone $binary_remote_addr zone=login_limit:5m rate=5r/m;
# Arama endpoint'leri için orta seviye
limit_req_zone $binary_remote_addr zone=search_limit:10m rate=20r/m;
# Premium kullanıcılar için ayrı zone (API key bazlı)
limit_req_zone $http_x_api_key zone=premium_limit:20m rate=100r/s;
# Bağlantı limiti zone'u
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
}
Dikkat ettiniz mi, son zone’da $http_x_api_key kullandık? Bu, IP yerine API anahtarına göre limit uygulamak demek. Aynı ofisten çıkan onlarca kullanıcı artık aynı IP limitini paylaşmak zorunda kalmaz.
Production Ortamı Yapılandırması
Şimdi gerçek bir production senaryosuna bakalım. Bir e-ticaret platformu için API gateway yapılandırması:
# /etc/nginx/conf.d/api-gateway.conf
limit_req_zone $binary_remote_addr zone=product_api:10m rate=60r/m;
limit_req_zone $binary_remote_addr zone=auth_api:5m rate=10r/m;
limit_req_zone $binary_remote_addr zone=order_api:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=search_api:10m rate=120r/m;
limit_req_zone $http_x_api_key zone=partner_api:20m rate=500r/m;
# Log format'a rate limiting bilgilerini ekle
log_format api_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'limit_req_status=$limit_req_status '
'upstream_response_time=$upstream_response_time';
server {
listen 443 ssl http2;
server_name api.orneksirket.com;
access_log /var/log/nginx/api-access.log api_log;
error_log /var/log/nginx/api-error.log warn;
# SSL yapılandırması
ssl_certificate /etc/letsencrypt/live/api.orneksirket.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.orneksirket.com/privkey.pem;
# Ürün endpoint'leri
location /api/v1/products {
limit_req zone=product_api burst=10 nodelay;
limit_req_status 429;
limit_req_log_level warn;
proxy_pass http://product_backend;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 429 hatası için özel response
error_page 429 /api-rate-limit-error.json;
}
# Auth endpoint'leri - en sıkı limit
location ~ ^/api/v1/(login|register|password-reset) {
limit_req zone=auth_api burst=3 nodelay;
limit_req_status 429;
proxy_pass http://auth_backend;
proxy_set_header X-Real-IP $remote_addr;
}
# Sipariş endpoint'leri
location /api/v1/orders {
limit_req zone=order_api burst=5;
limit_req_status 429;
proxy_pass http://order_backend;
}
# Arama - daha yüksek limit ama yine de kontrollü
location /api/v1/search {
limit_req zone=search_api burst=20 nodelay;
limit_req_status 429;
proxy_pass http://search_backend;
}
# Partner API - API key bazlı limit
location /api/v1/partner {
# API key yoksa reddet
if ($http_x_api_key = "") {
return 401 '{"error": "API key required"}';
}
limit_req zone=partner_api burst=50 nodelay;
limit_req_status 429;
proxy_pass http://partner_backend;
}
# Özel hata sayfası
location = /api-rate-limit-error.json {
internal;
default_type application/json;
return 429 '{"error": "Too Many Requests", "message": "Rate limit exceeded. Please try again later.", "retry_after": 60}';
}
}
Whitelist ve Özel IP Yönetimi
Bazı IP’leri veya aralıkları rate limiting’den muaf tutmak isteyebilirsiniz. Monitoring sistemleri, iç ağ IP’leri veya güvenilir partnerler için bu gerekli olabilir:
# /etc/nginx/conf.d/rate-limit-whitelist.conf
geo $limit {
default 1;
# Localhost - muaf
127.0.0.1 0;
# İç ağ - muaf
10.0.0.0/8 0;
192.168.0.0/16 0;
172.16.0.0/12 0;
# Güvenilir monitoring IP'leri - muaf
203.0.113.10 0;
203.0.113.11 0;
# Belirli partner IP bloğu - muaf
198.51.100.0/24 0;
}
# $limit değişkenine göre hangi zone'u kullanacağımızı belirle
map $limit $limit_key {
0 "";
1 $binary_remote_addr;
}
# Zone tanımında $limit_key kullan
limit_req_zone $limit_key zone=protected_api:10m rate=30r/m;
Bu yapıda $limit_key değeri whitelist’teki IP’ler için boş string olur ve boş string için limit uygulanmaz. Temiz bir çözüm.
Rate Limit Response Header’ları Eklemek
İstemcilerin ne kadar quota kaldığını bilmesi, iyi bir API deneyimi için önemlidir. Nginx bunu varsayılan olarak header’lara eklemez ama ekleyebiliriz:
server {
# ...
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
# Rate limit bilgilerini header'lara ekle
add_header X-RateLimit-Limit 30;
add_header X-RateLimit-Remaining $upstream_http_x_ratelimit_remaining;
add_header X-RateLimit-Reset $upstream_http_x_ratelimit_reset;
add_header Retry-After 60 always;
# 429 durumunda özel header
add_header X-RateLimit-Policy "30 requests per minute" always;
proxy_pass http://backend;
}
}
Dikkat: Nginx kendi başına kalan quota sayısını hesaplamaz. Bu bilgiyi upstream uygulamanızdan almanız veya Lua modülü ile (OpenResty) hesaplamanız gerekir. Ama en azından limit değerini ve retry-after süresini statik olarak ekleyebilirsiniz.
Log Analizi ve Monitoring
Rate limiting kurallarınızın düzgün çalışıp çalışmadığını anlamak için log analizi şart. İşte işinize yarayacak birkaç komut:
# Son 1 saatte kaç 429 hatası oluştu?
grep "429" /var/log/nginx/api-access.log |
awk '{print $4}' |
cut -d: -f2 |
sort | uniq -c | sort -rn
# En çok rate limit yiyen IP'ler
grep " 429 " /var/log/nginx/api-access.log |
awk '{print $1}' |
sort | uniq -c | sort -rn | head -20
# Belirli bir endpoint'e gelen isteklerin dağılımı
awk '//api/v1/login/ {print $1}' /var/log/nginx/api-access.log |
sort | uniq -c | sort -rn | head -10
# Rate limit loglarını gerçek zamanlı izle
tail -f /var/log/nginx/api-error.log | grep "limiting requests"
# Saatlik 429 trendi
grep " 429 " /var/log/nginx/api-access.log |
awk '{print $4}' |
cut -d: -f1,2 |
sort | uniq -c
Prometheus ve Grafana kullanıyorsanız, nginx-prometheus-exporter ile bu metrikleri otomatik olarak toplayabilirsiniz. nginx_http_requests_total metriğini status=429 ile filtreleyerek güzel bir dashboard kurabilirsiniz.
Dinamik Rate Limiting: Lua ile Gelişmiş Senaryolar
OpenResty (Nginx + Lua) kullanıyorsanız, çok daha esnek rate limiting yapabilirsiniz. Örneğin, kullanıcının plan tipine göre dinamik limit:
# OpenResty / nginx.conf
http {
lua_shared_dict rate_limit 10m;
server {
location /api/ {
access_by_lua_block {
local limit_req = require "resty.limit.req"
local api_key = ngx.req.get_headers()["X-API-Key"]
-- Plan tipine göre limit belirle
local rate = 30 -- default: 30 req/dakika
if api_key then
-- Redis'ten veya cache'ten kullanıcı planını al
local red = require "resty.redis"
local r = red:new()
r:connect("127.0.0.1", 6379)
local plan = r:get("plan:" .. api_key)
if plan == "premium" then
rate = 500
elseif plan == "business" then
rate = 100
end
end
local lim = limit_req.new("rate_limit", rate, 10)
local key = api_key or ngx.var.binary_remote_addr
local delay, err = lim:incoming(key, true)
if not delay then
if err == "rejected" then
ngx.status = 429
ngx.header["Content-Type"] = "application/json"
ngx.say('{"error": "Rate limit exceeded"}')
ngx.exit(429)
end
end
}
proxy_pass http://backend;
}
}
}
Bu senaryo, SaaS uygulamalarında farklı abonelik planları için ideal.
Sık Yapılan Hatalar ve Çözümleri
Production’da rate limiting yaparken karşılaşılan tipik sorunlar:
Reverse proxy arkasındaki gerçek IP sorunu: Eğer Nginx başka bir load balancer arkasındaysa, $remote_addr değeri proxy IP’si olur, gerçek istemci IP’si değil. Bu durumda tüm trafik sanki tek bir IP’den geliyormuş gibi görünür ve herkesi birden bloklamış olursunuz.
# Load balancer arkasında gerçek IP için
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# Artık $remote_addr gerçek IP'yi gösterir
limit_req_zone $remote_addr zone=real_ip_limit:10m rate=30r/m;
Çok agresif burst değerleri: burst değeri çok düşük olursa, normal kullanıcılar bile etkilenir. Özellikle SPA (Single Page Application) uygulamaları sayfa yüklendiğinde aynı anda 10-15 API isteği yapabilir. Bu durumu göz önünde bulundurun.
nodelay vs delay: nodelay, burst sınırındaki tüm istekleri anında kabul eder ama limiti aşınca hemen 429 döner. delay kullanırsanız, burst’teki istekleri yavaşlatarak kabul eder. Hangisinin daha iyi olduğu kullanım senaryonuza göre değişir.
Zone boyutu yetersizliği: 1MB zone yaklaşık 16.000 IP adresi tutabilir. Yüksek trafikli sitelerde zone dolunca Nginx eski kayıtları siler. Bu beklenmedik rate limit sıfırlamalarına yol açabilir. Zone boyutunuzu trafiğinize göre ayarlayın.
Test ve Doğrulama
Yapılandırmanızı production’a almadan önce test etmek kritik:
# Nginx konfigürasyonunu test et
nginx -t
# Rate limiting'i test etmek için Apache Benchmark
ab -n 100 -c 10 https://api.orneksite.com/api/v1/products
# curl ile hızlı test
for i in {1..20}; do
curl -s -o /dev/null -w "%{http_code}n"
https://api.orneksite.com/api/v1/login
sleep 0.1
done
# hey aracı ile daha gelişmiş load test
hey -n 200 -c 20 -q 5 https://api.orneksite.com/api/v1/products
# Wrk ile belirli süre test
wrk -t4 -c100 -d30s https://api.orneksite.com/api/v1/products
Test sonuçlarında 429 kodları görmeye başladığınızda rate limiting çalışıyor demektir. Test ortamında burst ve rate değerlerini gerçekçi kullanım senaryolarına göre ayarlayın.
Nginx Yapılandırmasını Yeniden Yüklemek
Rate limiting ayarlarını değiştirdikten sonra Nginx’i graceful reload etmeyi unutmayın:
# Konfigürasyonu test et
nginx -t
# Graceful reload (bağlantıları kesmeden)
nginx -s reload
# Veya systemd ile
systemctl reload nginx
# Durum kontrolü
systemctl status nginx
journalctl -u nginx -f
nginx -s reload kullandığınızda Nginx, yeni istekler için yeni yapılandırmayı kullanırken mevcut bağlantıları tamamlamasına izin verir. Production ortamı için her zaman bu yöntemi kullanın.
Sonuç
Nginx rate limiting, doğru yapılandırıldığında gerçekten güçlü bir API koruma katmanı oluşturur. Temel noktalara bakacak olursak:
- Farklı endpoint’ler için farklı zone’lar tanımlayın, herkese aynı limiti uygulamak ne çok agresif ne de çok cömert olmayı engellemez
- Login, register gibi kritik endpoint’lerde çok daha sıkı limitler uygulayın
- Whitelist mekanizmasıyla iç servisleri ve monitoring sistemlerini korumadan muaf tutun
- Log analizi yaparak rate limit kararlarınızın doğru olup olmadığını düzenli kontrol edin
- burst değerini gerçek kullanım senaryolarına göre ayarlayın, çok düşük tutmak meşru kullanıcıları etkiler
- Load balancer arkasında real_ip yapılandırmasını mutlaka yapın
Rate limiting, güvenliğin tek katmanı değil. WAF, DDoS koruma servisleri, API gateway çözümleri ve uygulama seviyesi kontroller ile birleştirildiğinde gerçekten sağlam bir koruma oluşturur. Ama bu kadar basit bir yapılandırmayla bu denli etkili bir koruma sağlamak, Nginx’i tercih etmemin en büyük nedenlerinden biri. Deneyip production’da görmek isteyip istemediğinizi düşünüyorsanız, cevabım kesinlikle evet.
