Nginx real_ip Modülü ile CDN Arkasında Gerçek IP Tespiti

CDN arkasına aldığınız bir web sunucusunda kullanıcı IP adreslerini loglamaya çalıştığınızda, karşınıza hep aynı IP çıkmaya başlar: CDN’in edge sunucusu. Güvenlik loglarınız anlamsızlaşır, rate limiting çalışmaz, coğrafi kısıtlamalar işe yaramaz. İşte bu noktada Nginx’in ngx_http_realip_module modülü devreye giriyor.

Bu yazıda Cloudflare, AWS CloudFront ve benzeri CDN’lerin arkasında çalışan Nginx sunucularında gerçek istemci IP’sini nasıl doğru şekilde tespit edeceğinizi, güvenli bir şekilde yapılandıracağınızı ve yaygın tuzaklardan nasıl kaçınacağınızı anlatacağım.

Sorun: CDN Arkasında IP Adresleri

Bir istemci CDN üzerinden web sunucunuza ulaştığında, TCP bağlantısını aslında CDN’in edge sunucusu kurar. Nginx’in $remote_addr değişkeni bu edge sunucusunun IP adresini gösterir, gerçek istemciyi değil. Ancak CDN’ler gerçek istemci IP’sini genellikle bir HTTP başlığı içinde iletir.

En yaygın başlıklar şunlardır:

  • X-Forwarded-For: Birden fazla proxy geçişinde zincir şeklinde IP’leri listeler
  • X-Real-IP: Tek bir IP değeri taşır, bazı CDN ve proxy’ler kullanır
  • CF-Connecting-IP: Cloudflare’e özgü başlık, doğrudan gerçek IP’yi taşır
  • True-Client-IP: Cloudflare Enterprise ve bazı diğer CDN’lerin kullandığı başlık

X-Forwarded-For başlığının yapısı şu şekildedir:

X-Forwarded-For: <istemci_ip>, <proxy1_ip>, <proxy2_ip>

En soldaki değer orijinal istemci IP’sidir. Ancak bu başlık istemci tarafından manipüle edilebileceği için doğrudan güvenilmez. ngx_http_realip_module tam da bu güven sorununu çözmek için tasarlanmıştır.

ngx_http_realip_module Nedir?

Bu modül Nginx’e şunu söyler: “Belirli IP adreslerinden gelen bağlantılarda $remote_addr değerini bir HTTP başlığındaki IP ile değiştir.” Böylece Nginx’in tüm diğer modülleri, log formatları ve uygulama katmanınız gerçek istemci IP’sini görür.

Modülün temel direktifleri:

  • set_real_ip_from: Hangi IP adreslerinden gelen isteklerde başlık içindeki IP’nin güvenilir sayılacağını belirtir. CIDR notasyonu desteklenir.
  • real_ip_header: Gerçek IP’nin okunacağı HTTP başlığını belirtir. Varsayılan değeri X-Real-IP‘dir.
  • real_ip_recursive: off olduğunda belirtilen başlıktaki son IP alınır. on olduğunda güvenilir IP listesinde olmayan ilk IP geriye doğru taranarak bulunur.

Modülün Kurulu Olduğunu Kontrol Etme

Çoğu dağıtımda bu modül varsayılan olarak gelir ancak teyit etmek iyidir:

nginx -V 2>&1 | grep -o with-http_realip_module

Çıktıda with-http_realip_module görüyorsanız hazırsınız demektir. Ubuntu/Debian’da nginx-full paketi bu modülü içerir, nginx-light içermeyebilir:

# Ubuntu'da mevcut Nginx paketlerini karşılaştırmak için
apt-cache show nginx-full | grep Depends
apt-cache show nginx-light | grep Depends

Temel Yapılandırma

En basit senaryo ile başlayalım. Nginx sunucunuzun önünde tek bir güvenilir proxy var ve bu proxy X-Forwarded-For başlığı gönderiyor:

http {
    # Güvenilir proxy IP'si
    set_real_ip_from 203.0.113.10;

    # Gerçek IP hangi başlıktan alınacak
    real_ip_header X-Forwarded-For;

    # Birden fazla proxy varsa gerçek istemciyi bulmak için
    real_ip_recursive on;

    server {
        listen 80;
        server_name example.com;

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

        location / {
            proxy_pass http://backend;
        }
    }
}

Bu yapılandırmadan sonra $remote_addr değişkeni artık gerçek istemci IP’sini taşır. Log formatınızda, rate limiting kurallarınızda, allow/deny direktiflerinde her yerde gerçek IP görünür.

Cloudflare Yapılandırması

Cloudflare, tüm dünyada dağılmış edge sunucularına sahip. Bu sunucuların IP aralıkları zaman zaman değişebiliyor. Cloudflare’in güncel IP listesini kullanmanız gerekiyor.

http {
    # Cloudflare IPv4 aralıkları (2024 güncel listesi)
    set_real_ip_from 103.21.244.0/22;
    set_real_ip_from 103.22.200.0/22;
    set_real_ip_from 103.31.4.0/22;
    set_real_ip_from 104.16.0.0/13;
    set_real_ip_from 104.24.0.0/14;
    set_real_ip_from 108.162.192.0/18;
    set_real_ip_from 131.0.72.0/22;
    set_real_ip_from 141.101.64.0/18;
    set_real_ip_from 162.158.0.0/15;
    set_real_ip_from 172.64.0.0/13;
    set_real_ip_from 173.245.48.0/20;
    set_real_ip_from 188.114.96.0/20;
    set_real_ip_from 190.93.240.0/20;
    set_real_ip_from 197.234.240.0/22;
    set_real_ip_from 198.41.128.0/17;

    # Cloudflare IPv6 aralıkları
    set_real_ip_from 2400:cb00::/32;
    set_real_ip_from 2606:4700::/32;
    set_real_ip_from 2803:f800::/32;
    set_real_ip_from 2405:b500::/32;
    set_real_ip_from 2405:8100::/32;
    set_real_ip_from 2a06:98c0::/29;
    set_real_ip_from 2c0f:f248::/32;

    # Cloudflare CF-Connecting-IP başlığını kullan
    real_ip_header CF-Connecting-IP;

    server {
        listen 443 ssl;
        server_name example.com;
        # ... diğer yapılandırmalar
    }
}

Cloudflare için CF-Connecting-IP başlığını X-Forwarded-For‘a tercih etmenizi öneririm. Bu başlık Cloudflare tarafından her zaman tek ve doğru istemci IP’sini içerir, manipüle edilmesi daha zordur.

Cloudflare IP Listesini Otomatik Güncelleme

Cloudflare IP’lerini elle takip etmek zahmetli. Bunun için bir cron job yazabiliriz:

#!/bin/bash
# /usr/local/bin/update-cloudflare-ips.sh

CF_IPS_FILE="/etc/nginx/conf.d/cloudflare-ips.conf"
TEMP_FILE=$(mktemp)

echo "# Cloudflare IP listesi - Son güncelleme: $(date)" > "$TEMP_FILE"

# IPv4 listesini çek
while IFS= read -r ip; do
    echo "set_real_ip_from $ip;" >> "$TEMP_FILE"
done < <(curl -s https://www.cloudflare.com/ips-v4)

# IPv6 listesini çek
while IFS= read -r ip; do
    echo "set_real_ip_from $ip;" >> "$TEMP_FILE"
done < <(curl -s https://www.cloudflare.com/ips-v6)

echo "real_ip_header CF-Connecting-IP;" >> "$TEMP_FILE"

# Değişiklik var mı kontrol et
if ! diff -q "$CF_IPS_FILE" "$TEMP_FILE" > /dev/null 2>&1; then
    mv "$TEMP_FILE" "$CF_IPS_FILE"
    nginx -t && systemctl reload nginx
    echo "Cloudflare IP listesi güncellendi ve Nginx yeniden yüklendi."
else
    rm "$TEMP_FILE"
    echo "Değişiklik yok, güncelleme gerekmedi."
fi
# Script'i çalıştırılabilir yapın
chmod +x /usr/local/bin/update-cloudflare-ips.sh

# Haftada bir çalışacak şekilde cron'a ekleyin
echo "0 3 * * 0 root /usr/local/bin/update-cloudflare-ips.sh >> /var/log/cf-ip-update.log 2>&1" 
    >> /etc/cron.d/cloudflare-ip-update

AWS CloudFront Yapılandırması

CloudFront biraz farklı çalışır. AWS’nin IP aralıkları büyük ve değişkendir. CloudFront X-Forwarded-For başlığını kullanır:

http {
    # CloudFront'un kullandığı başlık
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;

    # CloudFront IP'lerini AWS'nin JSON listesinden alabilirsiniz
    # Aşağıdaki script bunu otomatikleştirir
    include /etc/nginx/conf.d/cloudfront-ips.conf;

    server {
        listen 80;
        server_name example.com;

        # CloudFront'un gerçek IP'yi doğru iletip iletmediğini test etmek için
        location /ip-check {
            return 200 "Remote: $remote_addrnForwarded: $http_x_forwarded_forn";
            add_header Content-Type text/plain;
        }
    }
}

CloudFront IP listesini çeken script:

#!/bin/bash
# CloudFront IP listesini AWS'den çekip Nginx formatına dönüştür

OUTPUT_FILE="/etc/nginx/conf.d/cloudfront-ips.conf"

curl -s https://ip-ranges.amazonaws.com/ip-ranges.json | 
    python3 -c "
import json, sys
data = json.load(sys.stdin)
for prefix in data['prefixes']:
    if prefix.get('service') == 'CLOUDFRONT':
        print(f'set_real_ip_from {prefix["ip_prefix"]};')
for prefix in data['ipv6_prefixes']:
    if prefix.get('service') == 'CLOUDFRONT':
        print(f'set_real_ip_from {prefix["ipv6_prefix"]};')
" > "$OUTPUT_FILE"

nginx -t && systemctl reload nginx

real_ip_recursive Direktifini Anlamak

Bu direktif kafaları en çok karıştıran kısım. Somut bir örnekle açıklayalım.

Diyelim ki isteğin X-Forwarded-For başlığı şu şekilde:

X-Forwarded-For: 1.2.3.4, 10.0.0.1, 203.0.113.5

Güvenilir IP listesinde 10.0.0.1 ve 203.0.113.5 var.

real_ip_recursive off (varsayılan) ile: En sağdaki IP olan 203.0.113.5 $remote_addr olur. Ama bu güvenilir bir proxy, gerçek kullanıcı değil.

real_ip_recursive on ile: Nginx sağdan sola tarar. 203.0.113.5 güvenilir, atla. 10.0.0.1 güvenilir, atla. 1.2.3.4 güvenilir değil, bu gerçek istemci. Sonuç 1.2.3.4 olur.

CDN arkasındaki çoğu senaryoda real_ip_recursive on kullanmanız gerekir.

Güvenlik: IP Sahteciliğine Karşı Önlemler

Burada kritik bir güvenlik konusuna değinmem gerekiyor. set_real_ip_from direktifini dikkatli kullanmazsanız ciddi güvenlik açıkları oluşturabilirsiniz.

Senaryo: Diyelim ki CDN olmadan direkt internet’e açık bir Nginx sunucunuz var ama yine de real_ip_header X-Forwarded-For yapılandırdınız ve set_real_ip_from 0.0.0.0/0 gibi anlamsız bir şey yazdınız. Artık herhangi bir istemci HTTP isteğine X-Forwarded-For: 127.0.0.1 başlığı ekleyerek IP tabanlı kısıtlamalarınızı bypass edebilir.

Güvenli yapılandırma kuralları:

  • Yalnızca gerçekten güvendiğiniz proxy/CDN IP aralıklarını set_real_ip_from ile belirtin
  • CDN kullanıyorsanız, sunucunuzun sadece CDN IP’lerinden erişilebilir olduğundan emin olun (firewall ile)
  • 0.0.0.0/0 asla kullanmayın

Firewall tarafında Cloudflare kullanıyorsanız, sunucunuza yalnızca Cloudflare IP’lerinden erişime izin verebilirsiniz:

# Önce mevcut kuralları temizle (dikkatli!)
# Sadece Cloudflare'den HTTP/HTTPS erişimine izin ver
for ip in $(curl -s https://www.cloudflare.com/ips-v4); do
    ufw allow from "$ip" to any port 80,443 proto tcp
done

# SSH için kendi IP'nizi eklemeyi unutmayın!

Log Formatı ve Monitoring

Gerçek IP tespitini doğru yaptıktan sonra log formatınızı da güncellemek isteyebilirsiniz. Hem orijinal bağlantı IP’sini hem de gerçek istemci IP’sini loglamak debug süreçleri için faydalıdır:

http {
    # Özel log formatı - hem gerçek IP hem bağlantı IP'si
    log_format cdn_aware '$remote_addr [$http_x_forwarded_for] - $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';

    server {
        access_log /var/log/nginx/access.log cdn_aware;
    }
}

real_ip_module devreye girdikten sonra $remote_addr gerçek istemci IP’si olur. $http_x_forwarded_for ise başlıktaki ham zinciri gösterir. Bu ikisini birlikte loglarsanız hem analitik hem de debug için tam bilgiye sahip olursunuz.

Rate Limiting ile Entegrasyon

Rate limiting Nginx’in en çok kullanılan özelliklerinden biri. CDN arkasında ngx_http_realip_module olmadan tüm rate limiting kurallarınız CDN IP’lerine uygulanır ve anlamsız hale gelir:

http {
    # CDN IP listesi ve real IP modülü yapılandırması
    set_real_ip_from 103.21.244.0/22;
    # ... diğer Cloudflare aralıkları
    real_ip_header CF-Connecting-IP;

    # Rate limiting zone'u $remote_addr'a göre tanımla
    # real_ip_module devrede olduğundan bu artık gerçek istemci IP'si
    limit_req_zone $remote_addr zone=api_limit:10m rate=10r/s;
    limit_req_zone $remote_addr zone=login_limit:10m rate=5r/m;

    server {
        location /api/ {
            limit_req zone=api_limit burst=20 nodelay;
            proxy_pass http://api_backend;
        }

        location /auth/login {
            limit_req zone=login_limit burst=3;
            proxy_pass http://auth_backend;
        }
    }
}

Yapılandırmayı Test Etme

Yapılandırmanın doğru çalışıp çalışmadığını test etmek için birkaç yöntem:

# Test endpoint'i - geçici olarak ekleyin, production'da kaldırın
location /debug-ip {
    # Sadece dahili ağdan erişime izin verin
    allow 10.0.0.0/8;
    deny all;

    return 200 "remote_addr: $remote_addrnx_forwarded_for: $http_x_forwarded_forn";
    add_header Content-Type text/plain;
}

Komut satırından test:

# CDN'i simüle ederek test edin
# Güvenilir proxy IP'sinden geliyormuş gibi header ekleyin
curl -H "X-Forwarded-For: 1.2.3.4" 
     -H "CF-Connecting-IP: 1.2.3.4" 
     http://your-server/debug-ip

# Nginx loglarını gerçek zamanlı takip edin
tail -f /var/log/nginx/access.log | grep -v "healthcheck"

# Belirli bir IP'nin loglarını filtreleyin
grep "1.2.3.4" /var/log/nginx/access.log | tail -20

Gerçek Dünya Senaryosu: E-ticaret Sitesi

Bir e-ticaret müşterisinde karşılaştığım tipik bir sorunu paylaşayım. Cloudflare arkasında çalışan bir Magento kurulumuydu. Müşteri şikayeti: “Aynı IP’den çok fazla istek geliyor diye meşru kullanıcılar engelleniyor.”

Sebebi basitti: WAF kuralları $remote_addr‘a bakıyordu ve bu hep Cloudflare’in birkaç edge IP’sinden biri oluyordu. Bütün Türkiye’deki kullanıcılar aynı Cloudflare edge’i kullandığında hepsi aynı IP gibi görünüyordu.

Çözüm:

http {
    # Cloudflare IP'leri
    include /etc/nginx/conf.d/cloudflare-ips.conf;
    real_ip_header CF-Connecting-IP;

    # GeoIP modülüyle birlikte çalışıyor çünkü $remote_addr artık gerçek IP
    geoip_country /etc/nginx/geoip/GeoIP.dat;

    # Türkiye ve Avrupa dışından gelen istekleri yavaşlat
    map $geoip_country_code $request_rate {
        default     "suspicious";
        TR          "normal";
        DE          "normal";
        NL          "normal";
        # ... diğer ülkeler
    }

    limit_req_zone $remote_addr zone=normal_zone:10m rate=30r/s;
    limit_req_zone $remote_addr zone=suspicious_zone:10m rate=3r/s;

    server {
        location / {
            limit_req zone=${request_rate}_zone burst=10;
            proxy_pass http://magento_backend;
        }
    }
}

Bu yapılandırmadan sonra hem gerçek IP’ye dayalı rate limiting çalışmaya başladı hem de coğrafi kısıtlamalar anlamlı hale geldi.

Yaygın Hatalar ve Çözümleri

Hata 1: X-Forwarded-For zincirinde yanlış IP seçimi

real_ip_recursive off ile CDN IP’si $remote_addr oluyor. Çözüm: real_ip_recursive on kullanın veya CDN’in özel başlığını (CF-Connecting-IP gibi) tercih edin.

Hata 2: IPv6 aralıklarını unutmak

Modern CDN’ler IPv6 kullanır. set_real_ip_from direktiflerinizde hem IPv4 hem IPv6 aralıklarını eklemeyi unutmayın.

Hata 3: Modülün http bloğu yerine server bloğuna eklenmesi

set_real_ip_from direktifi http, server ve location bloklarında çalışır. Ancak tutarlılık için http bloğunda tanımlamak en iyisidir, böylece tüm sanal hostlara uygulanır.

Hata 4: Nginx reload yapmadan test etmek

# Her değişiklikten sonra önce syntax kontrolü
nginx -t

# Sonra graceful reload
systemctl reload nginx
# veya
nginx -s reload

Sonuç

ngx_http_realip_module küçük ama kritik bir yapılandırma. CDN arkasında doğru şekilde yapılandırılmadan rate limiting, güvenlik kuralları, analitik ve loglama sistemleriniz güvenilmez hale gelir.

Özetlemek gerekirse yapmanız gerekenler sırasıyla şunlardır: CDN’inizin kullandığı başlığı ve IP aralıklarını belirleyin, set_real_ip_from ile güvenilir kaynaklarınızı tanımlayın, real_ip_header ile doğru başlığı seçin, birden fazla proxy katmanı varsa real_ip_recursive on kullanın ve son olarak firewall ile sunucunuza yalnızca CDN IP’lerinden erişildiğinden emin olun.

CDN IP listelerinin zaman zaman değiştiğini unutmayın. Cloudflare için yazdığım otomatik güncelleme script’ini mutlaka kullanın. Bir gece aniden loglarınızın bozulmasından daha sinir bozucu bir şey yoktur ve sebebini bulmak saatler alabilir.

Son olarak, bu yapılandırmaları production’a almadan önce bir test ortamında /debug-ip gibi bir endpoint ile her şeyin doğru çalıştığını teyit edin. Gerçek istemci IP’sini görüyorsanız hazırsınız demektir.

Yorum yapın