Nginx GeoIP ile Ülke Bazlı Erişim Kontrolü

Bir web sunucusu yönetirken er ya da geç şu soruyla yüzleşiyorsunuz: “Bu IP bloğu sürekli beni tarayıp duruyor, bu ülkeden hiç meşru trafiğim yok, neden hâlâ kapım açık?” Nginx’in GeoIP modülü tam bu noktada devreye giriyor. Ülke bazlı erişim kontrolü, hem güvenlik hem de içerik kısıtlama açısından son derece güçlü bir araç. Bu yazıda MaxMind GeoIP veritabanlarını Nginx ile entegre edip gerçek dünya senaryolarında nasıl kullanacağınızı adım adım ele alacağız.

GeoIP Modülü Nedir, Ne Değildir?

Önce beklentileri doğru kuralım. GeoIP, bir IP adresinin hangi ülkeye ait olduğunu tahmin eden bir coğrafi veritabanı sistemidir. “Tahmin” kelimesini kasıtlı kullandım çünkü bu eşleştirme yüzde yüz doğru değildir. VPN kullanan birini, Türkiye’den bağlanan biri olarak görebilirsiniz ya da tam tersi. Kurumsal ağlarda IP bloklarının kayıt ülkesi ile fiziksel kullanım yeri farklı olabilir.

Bununla birlikte, %90+ doğruluk oranıyla pratik bir filtreleme aracı olarak son derece kullanışlıdır. Özellikle şu senaryolarda değer üretir:

  • Sadece belirli ülkelere hizmet veren bir e-ticaret sitesi
  • KVKK veya GDPR kapsamında bölgesel erişim kısıtlaması gereken servisler
  • Sürekli tarama yapan ülkelerden gelen kötü niyetli trafiği azaltmak
  • Admin paneline sadece kendi ülkenizden erişime izin vermek

Hangi GeoIP Modülünü Kullanmalısınız?

Nginx ekosisteminde iki farklı GeoIP yaklaşımı var:

ngx_http_geoip_module: Eski MaxMind GeoIP Legacy formatını (.dat dosyaları) kullanır. MaxMind 2019’dan itibaren bu veritabanlarını güncellemeyi bıraktı. Kullanmayın.

ngx_http_geoip2_module: MaxMind’ın yeni GeoIP2 formatını (.mmdb dosyaları) kullanır. Aktif olarak güncellenir, daha doğrudur. Bu yazıda bunu kullanacağız.

Kurulum: Ubuntu/Debian

MaxMind GeoLite2 Veritabanını Edinme

MaxMind artık veritabanlarını ücretsiz indirmek için hesap açmanızı zorunlu kılıyor. maxmind.com adresinden ücretsiz hesap açıp lisans anahtarı alın. GeoLite2 veritabanları ücretsizdir.

# MaxMind resmi deposunu ekleyin
sudo add-apt-repository ppa:maxmind/ppa
sudo apt update
sudo apt install geoipupdate

# Veritabanı dizinini oluşturun
sudo mkdir -p /usr/share/GeoIP

GeoIP güncelleme aracını yapılandıralım:

sudo nano /etc/GeoIP.conf

Dosya içeriği şu şekilde olmalı (kendi hesap bilgilerinizle):

# /etc/GeoIP.conf
AccountID YOUR_ACCOUNT_ID
LicenseKey YOUR_LICENSE_KEY
EditionIDs GeoLite2-Country GeoLite2-City GeoLite2-ASN
DatabaseDirectory /usr/share/GeoIP

Veritabanlarını indirin:

sudo geoipupdate

# Dosyaların geldiğini doğrulayın
ls -la /usr/share/GeoIP/
# GeoLite2-Country.mmdb, GeoLite2-City.mmdb, GeoLite2-ASN.mmdb görmeli siniz

Otomatik güncelleme için cron ayarlayalım. MaxMind veritabanlarını her Salı ve Cuma günceller:

sudo crontab -e

# Şu satırı ekleyin (haftada iki kez güncelleme):
0 3 * * 2,5 /usr/bin/geoipupdate && systemctl reload nginx

ngx_http_geoip2_module Kurulumu

Ubuntu/Debian’da Nginx repository’sinden direkt kurabilirsiniz:

# Nginx resmi reposu ekliyse
sudo apt install libnginx-mod-http-geoip2

# Alternatif olarak libmaxminddb kütüphanesi
sudo apt install libmaxminddb0 libmaxminddb-dev mmdb-bin

Eğer Nginx’i kaynaktan derliyorsanız:

# ngx_http_geoip2_module kaynak kodunu indirin
cd /tmp
git clone https://github.com/leev/ngx_http_geoip2_module.git

# Nginx configure aşamasında ekleyin
./configure --with-compat --add-dynamic-module=/tmp/ngx_http_geoip2_module

make modules

Kurulum: CentOS/RHEL/Rocky Linux

# EPEL reposunu ekleyin
sudo dnf install epel-release

# GeoIP güncelleme aracı
sudo dnf install GeoIP GeoIP-devel maxminddb maxminddb-devel

# Nginx için GeoIP2 modülü
sudo dnf install nginx-module-geoip2

# nginx.conf'a dynamic module yükleme satırını ekleyin
echo 'load_module modules/ngx_http_geoip2_module.so;' | sudo tee -a /etc/nginx/nginx.conf

Temel Nginx GeoIP2 Yapılandırması

Modülü yükledikten sonra nginx.conf dosyasının en üstüne (events bloğundan önce) şu satırı ekleyin:

# /etc/nginx/nginx.conf
load_module modules/ngx_http_geoip2_module.so;

http {
    # GeoIP2 veritabanlarını tanımlayın
    geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
        auto_reload 5m;
        $geoip2_metadata_country_build metadata build_epoch;
        $geoip2_data_country_code default=XX source=$remote_addr country iso_code;
        $geoip2_data_country_name default=Unknown source=$remote_addr country names en;
    }

    geoip2 /usr/share/GeoIP/GeoLite2-City.mmdb {
        $geoip2_data_city_name default=Unknown source=$remote_addr city names en;
        $geoip2_data_latitude default=0 source=$remote_addr location latitude;
        $geoip2_data_longitude default=0 source=$remote_addr location longitude;
    }

    # Log formatına ülke kodu ekleyin
    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    'country=$geoip2_data_country_code';

    # ... diğer ayarlar
}

Senaryo 1: Sadece Türkiye’den Erişime İzin Verme

Türkiye’ye özel bir hizmet sunuyorsanız whitelist yaklaşımı en temiz çözüm:

# /etc/nginx/conf.d/whitelist-tr.conf

# Ülke haritası oluşturun
geo $geoip2_data_country_code $allowed_country {
    default         0;
    TR              1;
}

server {
    listen 80;
    server_name example.com;

    # Erişim kontrolü
    if ($allowed_country = 0) {
        return 403 "Bu hizmet yalnızca Türkiye'den erişilebilir.";
    }

    location / {
        root /var/www/html;
        index index.html;
    }
}

Daha okunabilir bir alternatif olarak map direktifi kullanabilirsiniz:

# http bloğu içinde
map $geoip2_data_country_code $country_access {
    default     "deny";
    TR          "allow";
    # Test için kendi ofis IP'nizi de ekleyebilirsiniz
}

server {
    listen 443 ssl;
    server_name example.com;

    location / {
        if ($country_access = "deny") {
            return 403;
        }
        proxy_pass http://backend;
    }
}

Senaryo 2: Belirli Ülkeleri Blacklist’e Almak

Sunucunuza sürekli saldıran, tarama yapan ya da içerik politikalarınız gereği erişimi engellemeniz gereken ülkeler için blacklist yaklaşımı:

# http bloğu içinde
map $geoip2_data_country_code $blocked_country {
    default 0;
    CN      1;  # Çin
    RU      1;  # Rusya
    KP      1;  # Kuzey Kore
    IR      1;  # İran
    # İhtiyacınıza göre ekleyin veya çıkarın
}

server {
    listen 80;
    server_name example.com;

    if ($blocked_country = 1) {
        return 444;  # 444: Nginx özel kodu, bağlantıyı sessizce keser
    }

    location / {
        try_files $uri $uri/ =404;
    }
}

return 444 ile return 403 arasındaki farka dikkat edin. 444, Nginx’e özgü bir durum kodu olup yanıt göndermeden bağlantıyı kapatır. Tarayıcıya hiçbir ipucu vermez, bot açısından sanki sunucu yokmuş gibi görünür. 403 ise engellendiğinizi açıkça söyler.

Senaryo 3: Admin Paneline Ülke Kısıtlaması

Bu benim en sevdiğim senaryo. WordPress, phpMyAdmin veya herhangi bir admin arayüzüne sadece kendi ülkenizden (ve belki ofis IP’nizden) erişim izni verin:

# http bloğu içinde tanımlayın
geo $admin_access {
    default         0;
    # Ofis sabit IP'si - her zaman izin ver
    203.0.113.50    1;
    # Ev IP aralığı
    198.51.100.0/24 1;
}

map $geoip2_data_country_code $admin_country {
    default 0;
    TR      1;
}

server {
    listen 443 ssl;
    server_name example.com;

    # Admin paneli koruması
    location /wp-admin {
        # Hem Türkiye'den olmali hem de ek kontrol
        set $admin_check "${admin_access}${admin_country}";

        # Sabit IP'den geliyorsa her zaman izin ver
        if ($admin_access = 1) {
            # devam et, aşağıdaki engeli atla
        }

        if ($admin_country = 0) {
            return 403 "Yetkisiz erişim.";
        }

        proxy_pass http://wordpress_backend;
    }

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

Nginx’te if direktifleri iç içe çalışmaz. Daha temiz bir çözüm için map direktiflerini birleştirin:

# Güvenilir IP listesi
geo $trusted_ip {
    default         0;
    203.0.113.50    1;
    198.51.100.0/24 1;
}

# Birleşik erişim kontrolü
map "$trusted_ip:$geoip2_data_country_code" $admin_allowed {
    default     0;
    "1:TR"      1;  # Türkiye'den güvenilir IP
    "1:DE"      1;  # Almanya ofisinden güvenilir IP
    "~^1:"      1;  # Herhangi bir güvenilir IP (ülkeden bağımsız)
}

server {
    location /admin {
        if ($admin_allowed = 0) {
            return 403;
        }
        proxy_pass http://admin_backend;
    }
}

Senaryo 4: API Rate Limiting ile GeoIP Kombinasyonu

Farklı ülkelerden gelen isteklere farklı rate limit uygulayabilirsiniz. Örneğin yurt içi kullanıcılara daha cömert, yurt dışına daha kısıtlayıcı limit:

# Ülkeye göre rate limit zone belirleme
map $geoip2_data_country_code $rate_limit_zone {
    default     "foreign";
    TR          "domestic";
}

# Rate limit zone'ları tanımlama
limit_req_zone $binary_remote_addr zone=domestic_api:10m rate=100r/m;
limit_req_zone $binary_remote_addr zone=foreign_api:10m rate=20r/m;

server {
    listen 443 ssl;
    server_name api.example.com;

    location /api/ {
        # Dinamik zone seçimi map ile
        limit_req zone=${rate_limit_zone}_api burst=10 nodelay;
        limit_req_status 429;

        proxy_pass http://api_backend;
    }
}

Burada bir püf noktası var: Nginx’te değişken ile zone adı dinamik olarak birleştirilemez. Bunun yerine şu yapıyı kullanın:

server {
    location /api/ {
        # Ülkeye göre ayrı location'lara yönlendir
        if ($geoip2_data_country_code = "TR") {
            limit_req zone=domestic_api burst=50 nodelay;
        }

        # Varsayılan (yabancı) limit
        limit_req zone=foreign_api burst=10 nodelay;

        proxy_pass http://api_backend;
    }
}

Senaryo 5: Farklı İçerik Sunumu

Aynı sunucudan farklı ülkelere farklı içerik sunmak isteyebilirsiniz. CDN olmadan basit bir yönlendirme örneği:

map $geoip2_data_country_code $locale_root {
    default     /var/www/html/en;
    TR          /var/www/html/tr;
    DE          /var/www/html/de;
    FR          /var/www/html/fr;
    AR          /var/www/html/ar;
}

map $geoip2_data_country_code $locale_redirect {
    default     "";
    TR          "/tr/";
    DE          "/de/";
}

server {
    listen 80;
    server_name example.com;

    location = / {
        if ($locale_redirect != "") {
            return 302 $locale_redirect;
        }
        try_files $uri $uri/ /en/index.html;
    }

    # Dile göre kök dizin
    location / {
        root $locale_root;
        try_files $uri $uri/ /index.html;
    }
}

Proxy Arkasındaki Sunucularda Gerçek IP Sorunu

Sunucunuz CloudFlare, AWS ALB veya herhangi bir reverse proxy arkasındaysa $remote_addr proxy’nin IP’sini gösterir, kullanıcının değil. GeoIP bu durumda tamamen işe yaramaz hale gelir.

# http bloğu içinde gerçek IP ayarları
set_real_ip_from 103.21.244.0/22;   # CloudFlare IP aralıkları
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;

# IPv6 CloudFlare 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;

real_ip_header CF-Connecting-IP;

# AWS ALB için:
# set_real_ip_from 10.0.0.0/8;
# real_ip_header X-Forwarded-For;
# real_ip_recursive on;

Bu yapılandırmayı ekledikten sonra GeoIP modülü $remote_addr yerine gerçek kullanıcı IP’sini görür.

Yapılandırmayı Test Etme

Her değişiklikten sonra şu adımları izleyin:

# Sözdizimi kontrolü
sudo nginx -t

# Başarılı çıktı:
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful

# Reload (sıfır downtime ile)
sudo systemctl reload nginx

# GeoIP sorgusunu test etme - mmdb-bin paketi gerekli
mmdblookup --file /usr/share/GeoIP/GeoLite2-Country.mmdb --ip 8.8.8.8 country iso_code

# Belirli bir IP'nin ülkesini sorgulama
mmdblookup --file /usr/share/GeoIP/GeoLite2-Country.mmdb --ip 195.175.254.2

Nginx log’larına ülke kodunu eklediyseniz canlı trafiği izleyebilirsiniz:

# Ülke koduna göre erişim sayısını say
tail -f /var/log/nginx/access.log | grep -oP 'country=K[A-Z]+'  | sort | uniq -c | sort -rn

# Engellenen ülkeleri izle
tail -f /var/log/nginx/access.log | awk '$9 == "403" {print $NF}' | sort | uniq -c

Performans Notları

GeoIP2 mmdb formatı, ikili arama ağacı yapısı sayesinde son derece hızlıdır. Tipik bir sorguda 1-2 mikrosaniye latency eklenir ki bu pratikte ihmal edilebilir seviyededir.

Bununla birlikte şu noktalara dikkat edin:

  • auto_reload 5m direktifi, veritabanı dosyası değiştiğinde Nginx’i yeniden başlatmadan güncelleme yapmanızı sağlar. Üretimde mutlaka ekleyin.
  • Çok sayıda if direktifi kullanmak yerine map kullanın, map direktifi sadece değişken kullanıldığında değerlendirilerek lazy evaluation yapar.
  • Yüksek trafikli sunucularda worker process sayısını ve worker_connections değerini GeoIP yükü de hesaba katarak ayarlayın.

Veritabanı Güncellikini Kontrol Etme

# Veritabanı derleme tarihini kontrol edin
mmdblookup --file /usr/share/GeoIP/GeoLite2-Country.mmdb --ip 8.8.8.8 | head -5

# Build epoch kontrolü - Nginx değişkenı ile
# nginx.conf'ta tanımladıysanız:
# $geoip2_metadata_country_build şeklinde log'a ekleyip kontrol edebilirsiniz

# Cron işinin çalışıp çalışmadığını doğrulayın
sudo geoipupdate -v

Yaygın Sorunlar ve Çözümleri

Modül yüklenmiyor: nginx -t çalıştırıldığında “unknown directive” hatası alıyorsanız load_module satırını kontrol edin. Dynamic module dosyasının /etc/nginx/modules/ veya /usr/lib/nginx/modules/ altında olduğunu doğrulayın.

Tüm ziyaretçiler aynı ülkeden görünüyor: Proxy arkasındaysanız gerçek IP yapılandırmasını (set_real_ip_from) doğru yaptığınızdan emin olun.

XX ülke kodu görünüyor: GeoIP veritabanında bulunamayan IP’ler için default değer olarak XX atanır. Bu çoğunlukla localhost, özel ağ adresleri veya veritabanında kaydı olmayan yeni IP bloklarıdır. Gerekirse XX için de bir kural ekleyin.

Veritabanı dosyası bulunamıyor: Nginx worker process’i dosyaya erişebilecek izinlere sahip olmalı. sudo -u www-data ls /usr/share/GeoIP/ komutuyla test edin.

Sonuç

Nginx GeoIP2 entegrasyonu, doğru yapılandırıldığında güvenlik duvarınızın en güçlü katmanlarından biri haline gelir. Tek başına bir güvenlik çözümü olmasa da diğer önlemlerle (fail2ban, ModSecurity, rate limiting) birleştiğinde gerçekten etkili bir derinlemesine savunma (defense in depth) mimarisi oluşturur.

Üretimde devreye almadan önce şunu her zaman aklınızda tutun: Agresif bir whitelist politikası meşru kullanıcıları engelleyebilir. Özellikle mobil operatörlerin uluslararası dolaşım IP’leri, kurumsal VPN’ler ve bazı CDN çıkış noktaları beklenmedik ülke kodlarıyla gelebilir. İlk aşamada engelleme yapmak yerine sadece log’layın, gerçek trafiği analiz edin, sonra kararınızı verin. Böyle yaparsanız hem güvenlik kazanırsınız hem de öfkeli bir müşteri telefonu almaktan kurtulursunuz.

Yorum yapın