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.

Bir yanıt yazın

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