Nginx ile Header Manipülasyonu ve Güvenlik: Kapsamlı Rehber

Nginx’i reverse proxy olarak kullandığın anda, header yönetimi artık sadece “güzel olur” kategorisinden çıkıp kritik bir güvenlik meselesi haline geliyor. Yanlış yapılandırılmış headerlar; bilgi sızıntısına, CSRF açıklarına, clickjacking saldırılarına ve hatta direkt sunucu ele geçirmeye zemin hazırlayabilir. Bu yazıda, gerçek dünya senaryoları üzerinden Nginx’te header manipülasyonunu doğru yapmanın yollarını ele alacağız.

Temel Kavramlar: Request vs Response Header Manipülasyonu

Nginx’te iki yönlü header işlemi yapıyoruz:

  • Upstream’e giden (proxy_set_header): Client’tan gelen isteği backend’e iletirken header ekleyip değiştiriyoruz.
  • Client’a dönen (add_header / more_set_headers): Backend’den gelen yanıtı client’a iletmeden önce header ekliyoruz.

Bu iki yönü karıştırmak çok yaygın bir hata. Özellikle add_header direktifinin sadece response’a etki ettiğini, proxy_set_header‘ın ise upstream’e giden isteği etkilediğini net olarak kafana oturtman gerekiyor.

Bir de header silme işi var ki, bu özellikle güvenlik açısından çok kritik. Nginx’te varsayılan olarak bazı headerlar backend’e iletilirken bazıları saklanır. Hangi headerın nereye gittiğini bilmeden yapılandırma yapmak büyük riskler barındırıyor.

Neden Header Güvenliği Bu Kadar Önemli?

Gerçek bir senaryo düşünelim: E-ticaret platformunuz var, önünde Nginx reverse proxy çalışıyor, arkada bir Python/Node.js API. Müşteri siparişi veriyor, ödeme işlemi gerçekleşiyor. Bu noktada;

  • X-Forwarded-For hatalı yapılandırılmışsa, rate limiting bypass edilebilir
  • Backend sunucunuzun versiyonu Server headerında görünüyorsa, CVE taraması yapılabilir
  • X-Powered-By görünüyorsa, framework versiyonuna göre exploit denenebilir
  • HSTS eksikse, SSL stripping saldırısına kapı aralanmış olur

Bunların hepsi önlenebilir sorunlar ve hepsinin çözümü Nginx konfigürasyonunda saklı.

Bilgi Sızıntısını Önleme: Gereksiz Headerları Kaldırma

İlk yapılacak iş, backend’in kimliğini ele veren headerları temizlemek. Varsayılan Nginx kurulumunda Server: nginx/1.18.0 gibi bir şey görürsünüz. Bu, saldırgana hangi versiyon açıklarını deneyeceğini söylüyor.

# /etc/nginx/nginx.conf - http bloğuna ekle
http {
    # Nginx versiyon bilgisini gizle
    server_tokens off;

    # Proxy edilen backend başlıklarını temizle
    proxy_hide_header X-Powered-By;
    proxy_hide_header X-AspNet-Version;
    proxy_hide_header X-AspNetMvc-Version;
    proxy_hide_header X-Generator;
    proxy_hide_header X-Drupal-Cache;

    # Backend server bilgisini gizle
    more_set_headers 'Server: WebServer';
}

proxy_hide_header direktifi, backend’den gelen response’taki belirtilen headerı client’a iletmez. more_set_headers ise headers-more-nginx-module modülünü gerektirir ve mevcut headerları değiştirip overwrite edebilir. Nginx’in built-in add_header direktifi aynı isimde header zaten varsa ikincisini ekler, overwrite etmez. Bu kritik bir fark.

Ubuntu/Debian’da headers-more modülünü yüklemek için:

# Ubuntu 20.04+ için
sudo apt install libnginx-mod-http-headers-more-filter

# Nginx'i yeniden başlat
sudo systemctl restart nginx

# Config'e modülü yükle (eğer otomatik yüklenmiyorsa)
# /etc/nginx/nginx.conf başına ekle:
load_module modules/ngx_http_headers_more_filter_module.so;

X-Forwarded-For ve Gerçek IP Yönetimi

Bu konu, özellikle rate limiting ve IP bazlı güvenlik kontrolleri açısından kritik. Yanlış yapılandırma, hem güvenlik açığı hem de backend tarafında hatalı loglama yaratır.

# /etc/nginx/conf.d/proxy_params.conf
# Güvenilir proxy IP aralıklarını 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 bloklarını ekleyin
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;

# Gerçek IP'yi hangi headerdan al
real_ip_header X-Forwarded-For;
real_ip_recursive on;

Burada real_ip_recursive on çok önemli. Bu direktif olmadan, bir saldırgan X-Forwarded-For: 127.0.0.1, attacker_real_ip göndererek sahte IP geçirebilir. recursive on ile Nginx, güvenilir olmayan son IP’yi alıyor.

Backend’e iletilen headerları da doğru set etmemiz gerekiyor:

# /etc/nginx/conf.d/upstream_headers.conf
# Location veya server bloğu içinde kullan
location /api/ {
    # Önceki X-Forwarded-For değerini temizle, client IP'yi yaz
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Port $server_port;

    # Host header'ını doğru ilet
    proxy_set_header Host $host;

    # Connection header temizliği
    proxy_set_header Connection "";
    proxy_http_version 1.1;

    proxy_pass http://backend_api;
}

proxy_set_header Connection "" ve proxy_http_version 1.1 kombinasyonu, HTTP keepalive connection’larını düzgün yönetmek için gerekli. Aksi halde connection header’ı “upgrade” veya “close” şeklinde iletilip backend’de sorun çıkarabilir.

Güvenlik Headerları: HTTPS Siteleri İçin Zorunlu Minimum Set

Güvenlik headerları konusunda çoğu kişi yüzeysel geçiyor. Ben burada her birinin ne işe yaradığını ve yanlış değerlerin ne tür sorunlara yol açtığını açıklayacağım.

# /etc/nginx/conf.d/security_headers.conf
# Bu dosyayı http bloğuna include et veya doğrudan server bloğuna ekle

server {
    listen 443 ssl;
    server_name example.com;

    # SSL yapılandırması
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # HSTS - Tarayıcıya "bu siteye sadece HTTPS'le bağlan" de
    # max-age=31536000 = 1 yıl
    # includeSubDomains: alt domainleri de kapsa
    # preload: HSTS preload listesine eklenebilir
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # Clickjacking koruması - iframe içinde açılmayı engelle
    add_header X-Frame-Options "SAMEORIGIN" always;

    # MIME type sniffing'i engelle
    add_header X-Content-Type-Options "nosniff" always;

    # XSS filtresi (eski tarayıcılar için)
    add_header X-XSS-Protection "1; mode=block" always;

    # Referrer Policy - kaynaktan çok bilgi gitmesin
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Permissions Policy - tarayıcı API'lerini kısıtla
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=(self), payment=()" always;
}

always parametresi kritik. Bunu koymadan, Nginx sadece 200 ve 304 response’larına header ekler. 404, 500 gibi hata response’larına eklemez. Güvenlik headerlarının hata sayfalarında da görünmesi gerektiğinden always şart.

Content Security Policy Yapılandırması

CSP ayrı bir başlık hak ediyor çünkü yanlış yapılandırılmış CSP ya siteyi kırar ya da işe yaramaz.

# Temel bir web uygulaması için CSP örneği
# Önce report-only modda test edin!
add_header Content-Security-Policy-Report-Only "
    default-src 'self';
    script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net;
    style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
    font-src 'self' https://fonts.gstatic.com;
    img-src 'self' data: https:;
    connect-src 'self' https://api.example.com;
    frame-ancestors 'none';
    base-uri 'self';
    form-action 'self';
    upgrade-insecure-requests;
    report-uri /csp-report;
" always;

# Test başarılı olduktan sonra:
# add_header Content-Security-Policy "..." always;

Tek satırda yazmak zorunda değilsiniz, ama Nginx config’de multi-line string için dikkatli olun. Benim tercihim tüm CSP’yi tek satırda tutmak, okunabilirlik için bir değişkene atamak:

# nginx.conf http bloğuna veya ayrı bir map bloğuna
geo $csp_policy {
    default "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';";
}

server {
    add_header Content-Security-Policy $csp_policy always;
}

CORS Headerları: API Gateway Senaryoları

Nginx’i API gateway olarak kullandığınızda CORS yönetimi en çok acı çekilen konulardan biri. Yanlış CORS yapılandırması hem güvenlik açığı hem de frontend developer’ların kabusu.

# /etc/nginx/conf.d/cors.conf
# Güvenli CORS yapılandırması - wildcard * kullanma!

map $http_origin $cors_origin {
    default "";
    "https://app.example.com" "https://app.example.com";
    "https://admin.example.com" "https://admin.example.com";
    "https://staging.example.com" "https://staging.example.com";
}

server {
    location /api/ {
        # CORS preflight isteğini handle et
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin $cors_origin always;
            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, PATCH, OPTIONS" always;
            add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-API-Key" always;
            add_header Access-Control-Max-Age 3600 always;
            add_header Content-Length 0;
            add_header Content-Type text/plain;
            return 204;
        }

        # Normal istekler için
        add_header Access-Control-Allow-Origin $cors_origin always;
        add_header Access-Control-Allow-Credentials true always;
        add_header Vary "Origin" always;

        proxy_pass http://backend_api;
    }
}

Burada birkaç önemli nokta var:

  • map bloğu ile whitelist yaklaşımı, dinamik origin kontrolü için en temiz yol. Wildcard * kullanmak, credentials ile birlikte çalışmaz ve güvenlik açığı yaratır.
  • Vary: Origin header’ı, CDN’lerin ve tarayıcıların farklı origin’ler için ayrı cache tutmasını sağlar. Bunu eklemezsek farklı origin’ler arasında cache karışıklığı yaşanır.
  • Access-Control-Allow-Credentials: true koyduğunuzda, origin kesinlikle wildcard olamaz.

Hassas Headerları Backend’e İletmeme

İstemciden gelen bazı headerların backend’e ulaşmaması gerekir. Özellikle internal sistemlerde kullanılan headerlar.

# Tehlikeli veya gereksiz headerları temizle
location /api/ {
    # Client'ın X-Real-IP set etmesini engelle (IP spoofing)
    proxy_set_header X-Real-IP $remote_addr;

    # Internal header'ları sıfırla
    proxy_set_header X-Internal-User "";
    proxy_set_header X-Admin-Override "";
    proxy_set_header X-Debug-Mode "";

    # Authorization header'ını sadece belirli path'lerde ilet
    proxy_set_header Authorization $http_authorization;

    proxy_pass http://backend;
}

# Admin endpoint için farklı header politikası
location /api/admin/ {
    # Sadece belirli IP'lerden gelenleri kabul et
    allow 10.0.0.0/8;
    deny all;

    # Internal admin flag ekle
    proxy_set_header X-Internal-Admin "true";
    proxy_set_header X-Admin-IP $remote_addr;

    proxy_pass http://backend;
}

Rate Limiting ile Header Entegrasyonu

Rate limiting, header bilgilerine göre farklı davranmalı. API key varsa farklı limit, yoksa farklı limit gibi.

# /etc/nginx/conf.d/rate_limit.conf

# API key bazlı rate limit zone'ları
limit_req_zone $http_x_api_key zone=api_key:10m rate=100r/s;
limit_req_zone $binary_remote_addr zone=ip_based:10m rate=10r/s;
limit_req_zone $http_x_api_key$binary_remote_addr zone=combined:10m rate=50r/s;

server {
    location /api/ {
        # API key varsa daha yüksek limit
        if ($http_x_api_key) {
            limit_req zone=api_key burst=200 nodelay;
        }

        # Yoksa IP bazlı kısıt
        limit_req zone=ip_based burst=20 nodelay;

        # Rate limit header'larını client'a bildir
        add_header X-RateLimit-Limit 100 always;
        add_header X-RateLimit-Remaining $limit_req_status always;
        add_header Retry-After 60 always;

        proxy_pass http://backend;
    }
}

Gerçek Dünya Senaryosu: Microservice Ortamında Header Routing

Microservice mimarisinde Nginx’i API gateway olarak kullandığınızda, header bazlı routing çok işe yarıyor.

# /etc/nginx/conf.d/api_gateway.conf

upstream service_v1 {
    server api-v1:8080;
    keepalive 32;
}

upstream service_v2 {
    server api-v2:8080;
    keepalive 32;
}

upstream service_beta {
    server api-beta:8080;
    keepalive 32;
}

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

    # API versiyonuna göre routing
    location /api/ {
        # Header bazlı versiyon seçimi
        set $backend service_v1;

        if ($http_x_api_version = "v2") {
            set $backend service_v2;
        }

        # Beta kullanıcıları için ayrı cluster
        if ($http_x_beta_user = "true") {
            set $backend service_beta;
        }

        # Backend'e hangi versiyonu istediğini ilet
        proxy_set_header X-Routed-Version $backend;
        proxy_set_header X-Gateway-Time $time_iso8601;
        proxy_set_header X-Request-ID $request_id;

        # Correlation ID yoksa oluştur
        proxy_set_header X-Correlation-ID $http_x_correlation_id;

        proxy_pass http://$backend;

        # Response'a routing bilgisi ekle (debug için)
        add_header X-Served-By $upstream_addr always;
        add_header X-Response-Time $upstream_response_time always;
    }
}

$request_id değişkeni, Nginx 1.11.0+ ile geldi ve her request için unique ID üretiyor. Distributed tracing için bu headerı hem backend’e iletmek hem de client’a response olarak göndermek çok değerli.

Header Güvenlik Testi

Yapılandırmayı tamamladıktan sonra test etmek şart.

# Temel güvenlik header kontrolü
curl -I https://example.com

# Spesifik header testi
curl -s -o /dev/null -D - https://example.com | grep -i "strict-transport|x-frame|x-content|content-security"

# CORS testi
curl -H "Origin: https://attacker.com" 
     -H "Access-Control-Request-Method: GET" 
     -X OPTIONS 
     https://api.example.com/api/users 
     -I

# X-Forwarded-For spoofing testi
curl -H "X-Forwarded-For: 127.0.0.1" 
     https://api.example.com/api/sensitive 
     -v

# Rate limit header kontrolü
for i in {1..15}; do
    curl -s -o /dev/null -w "%{http_code} " https://api.example.com/api/test
done
echo ""

# Versiyon bilgisi sızıntı kontrolü
curl -I https://example.com 2>&1 | grep -i "server:|x-powered-by:|x-aspnet"

Bu testleri CI/CD pipeline’ınıza da ekleyebilirsiniz. Her deployment sonrası otomatik header güvenlik kontrolü çok mantıklı bir pratik.

Nginx Konfigürasyonunu Modüler Tutma

Tüm header yapılandırmalarını tek dosyada tutmak yerine, include’larla modüler yönetmek çok daha sürdürülebilir.

# /etc/nginx/snippets/security_headers.conf
# Tüm site genelinde güvenlik headerları

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
proxy_hide_header X-Powered-By;
proxy_hide_header Server;
# /etc/nginx/snippets/proxy_headers.conf
# Upstream'e giden standart headerlar

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Request-ID $request_id;
proxy_http_version 1.1;
proxy_set_header Connection "";
# /etc/nginx/sites-available/example.com
server {
    listen 443 ssl;
    server_name example.com;

    include snippets/security_headers.conf;

    location /api/ {
        include snippets/proxy_headers.conf;
        proxy_pass http://backend;
    }
}

Bu yapı ile güvenlik header’larını tek yerden yönetip tüm sitelere uygulayabilirsiniz. Bir güvenlik güncellemesi gerektiğinde tek dosyayı değiştirmek yeterli.

Sık Yapılan Hatalar

  • add_header direktifini iç içe bloklarda kullanmak: Nginx’te bir bloğa add_header eklediğinizde, parent bloktan gelen add_header direktifleri çalışmaz hale gelir. Tüm headerları ya snippet ile ya da aynı blok seviyesinde tanımlamanız gerekir.
  • HSTS’yi HTTP’de de aktifleştirmek: HSTS sadece HTTPS üzerinde anlam taşır. HTTP server bloğuna HSTS eklemeyin.
  • CSP’yi test etmeden production’a almak: Her zaman önce Content-Security-Policy-Report-Only ile başlayın, sorun yoksa gerçek header’a geçin.
  • proxy_hide_header‘ı sadece location bloğuna koymak: Bu direktif http bloğunda tanımlanırsa global olarak çalışır, server veya location bloğunda sadece o scope için geçerli olur. Genellikle http bloğunda tanımlamak daha mantıklıdır.

Sonuç

Nginx’te header güvenliği, tek seferlik yapılandırıp unutulan bir şey değil, düzenli gözden geçirilmesi gereken bir alan. Üç ayda bir curl -I ile kendi sitenizi kontrol edin, yeni ortaya çıkan güvenlik header’larını takip edin, CSP politikanızı uygulama değiştikçe güncelleyin.

En kritik adımlar şöyle özetlenebilir: Versiyon bilgisini gizle, gereksiz headerları temizle, güvenlik headerlarını ekle, CORS’u whitelist ile yönet, X-Forwarded-For’u doğru yapılandır. Bu beş adımı uygulayan bir Nginx kurulumu, security scanner’lardan geçerli not alır ve olası saldırı yüzeyini ciddi ölçüde azaltır.

Konfigürasyonunuzu yazdıktan sonra [securityheaders.com](https://securityheaders.com) ve Mozilla Observatory üzerinden test etmeyi de ihmal etmeyin. Bu araçlar, gözden kaçırdığınız noktaları hızlıca gösteriyor.

Bir yanıt yazın

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