Nginx ile Reverse Proxy ve Upstream Backend Yönetimi

Modern web mimarilerinde tek bir sunucunun tüm yükü taşıması artık neredeyse imkansız. Yüksek trafikli uygulamalar, mikroservis yapıları ve yüksek erişilebilirlik gereksinimleri bizi kaçınılmaz olarak reverse proxy ve load balancing kavramlarıyla yüz yüze getiriyor. Nginx, bu noktada sadece bir web sunucusu olmaktan çıkıp güçlü bir trafik yöneticisine dönüşüyor. Bu yazıda, Nginx’in reverse proxy özelliklerini ve upstream backend yönetimini gerçek dünya senaryolarıyla derinlemesine inceleyeceğiz.

Reverse Proxy Nedir ve Neden Nginx?

Reverse proxy, istemciden gelen istekleri alıp arka plandaki sunuculara ileten bir aracı katmandır. İstemci, arkada kaç sunucu olduğunu ya da hangi teknolojinin çalıştığını bilmez. Bu yapı size birkaç kritik avantaj sağlar: güvenlik katmanı, SSL terminasyonu, load balancing ve caching.

Nginx’in bu işte öne çıkmasının temel nedeni event-driven mimarisidir. Apache’nin her istek için yeni bir thread veya process oluşturmasının aksine, Nginx asenkron yapısıyla binlerce eş zamanlı bağlantıyı minimal kaynak kullanımıyla yönetebilir. Bir Node.js uygulamasının önüne, bir Python/Django uygulamasının önüne ya da birden fazla uygulama sunucusunun önüne koyduğunuzda farkı hemen hissediyorsunuz.

Temel Reverse Proxy Yapılandırması

En sade haliyle bir reverse proxy kurulumu şöyle görünür. Diyelim ki port 3000’de çalışan bir Node.js uygulamanız var ve bunu dış dünyaya 80 ve 443 üzerinden açmak istiyorsunuz.

server {
    listen 80;
    server_name uygulama.orneksite.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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_cache_bypass $http_upgrade;
    }
}

Burada birkaç kritik header ayarı var. proxy_set_header X-Real-IP: İstemcinin gerçek IP adresini backend’e iletir. proxy_set_header X-Forwarded-For: Proxy zincirindeki tüm IP adreslerini içerir. proxy_http_version 1.1: WebSocket desteği için gereklidir. Bu header’ları eksik bıraktığınızda uygulamanız tüm isteklerin 127.0.0.1’den geldiğini düşünür, log analizleri ve rate limiting çalışmaz.

Upstream Bloğu ile Backend Yönetimi

Birden fazla backend sunucunuz olduğunda upstream direktifi devreye giriyor. Bu blok, Nginx’e hangi sunuculara istek göndereceğini ve nasıl dağıtacağını söyler.

upstream nodejs_cluster {
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;
    server 192.168.1.12:3000;
}

server {
    listen 80;
    server_name uygulama.orneksite.com;

    location / {
        proxy_pass http://nodejs_cluster;
        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_connect_timeout 10s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
}

Varsayılan yük dağılım algoritması round-robin‘dir. Yani istekler sırayla her sunucuya gönderilir. Bu basit ama çoğu senaryo için yeterlidir.

Load Balancing Algoritmaları

Nginx, farklı ihtiyaçlar için birkaç farklı algoritma sunar.

Round Robin (Varsayılan): İstekler sunucular arasında sırayla dağıtılır. Tüm sunucuların benzer kapasitede olduğu durumlarda idealdir.

Least Connections: Aktif bağlantısı en az olan sunucuya istek gönderir. Uzun süren işlemlerin olduğu durumlarda daha adil bir dağılım sağlar.

IP Hash: Aynı istemcinin isteklerini her zaman aynı sunucuya yönlendirir. Session tabanlı uygulamalar için kritiktir.

Weighted Round Robin: Farklı kapasitedeki sunucular için ağırlık tanımlanır.

upstream backend_pool {
    # Least connections algoritması
    least_conn;

    # Ağırlıklı dağılım: app1 isteklerin %60'ını, app2 %40'ını alır
    server 192.168.1.10:8080 weight=3;
    server 192.168.1.11:8080 weight=2;

    # Yedek sunucu - diğerleri çökerse devreye girer
    server 192.168.1.12:8080 backup;

    # Geçici olarak devre dışı
    server 192.168.1.13:8080 down;
}

weight=N: Sunucunun alacağı trafik payını belirler. backup: Diğer tüm sunucular kullanılamaz olduğunda devreye girer. down: Sunucuyu pool’dan çıkarır ama config’den silmez.

Health Check ve Failover Yapılandırması

Production ortamında bir backend sunucusu çökerse Nginx’in bunu otomatik fark edip trafiği sağlıklı sunuculara yönlendirmesi gerekir. Nginx open-source sürümünde pasif health check mevcuttur.

upstream api_servers {
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
}

max_fails=3: Belirtilen süre içinde 3 başarısız istek olursa sunucu devre dışı sayılır. fail_timeout=30s: Bu parametreyi çift işlev görür; hem başarısız isteklerin sayıldığı zaman dilimini hem de sunucunun kaç saniye devre dışı kalacağını belirler.

Nginx Plus kullanıyorsanız aktif health check için şu yapılandırmayı kullanabilirsiniz, ancak açık kaynak sürümde ngx_http_upstream_hc_module için Nginx’i kaynak koddan derlemeniz gerekir:

upstream app_backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;

    keepalive 32;
}

server {
    listen 80;
    server_name api.orneksite.com;

    location / {
        proxy_pass http://app_backend;
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
        proxy_next_upstream_tries 3;
        proxy_next_upstream_timeout 10s;
    }
}

proxy_next_upstream: Hangi durumlarda bir sonraki upstream sunucusuna geçileceğini tanımlar. proxy_next_upstream_tries: Maksimum deneme sayısı. Bu yapı sayesinde bir backend 500 döndürdüğünde Nginx otomatik olarak diğer sunucuya geçer, kullanıcı hiçbir şey fark etmez.

Gerçek Dünya Senaryosu: Mikroservis Mimarisi

Diyelim ki bir e-ticaret platformu yönetiyorsunuz. Kullanıcı servisi, ürün servisi, sipariş servisi ve ödeme servisi ayrı ayrı çalışıyor. Nginx’i bu servislerin önüne bir API gateway olarak konumlandırabilirsiniz.

# /etc/nginx/conf.d/eticaret.conf

upstream kullanici_servisi {
    server 10.0.1.10:8001;
    server 10.0.1.11:8001;
    keepalive 16;
}

upstream urun_servisi {
    least_conn;
    server 10.0.2.10:8002 weight=2;
    server 10.0.2.11:8002 weight=2;
    server 10.0.2.12:8002 weight=1;
    keepalive 16;
}

upstream siparis_servisi {
    ip_hash;
    server 10.0.3.10:8003;
    server 10.0.3.11:8003;
}

upstream odeme_servisi {
    server 10.0.4.10:8004;
    server 10.0.4.11:8004 backup;
    keepalive 8;
}

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

    ssl_certificate /etc/ssl/certs/eticaret.crt;
    ssl_certificate_key /etc/ssl/private/eticaret.key;

    # Kullanıcı servisi
    location /api/v1/users/ {
        proxy_pass http://kullanici_servisi;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_read_timeout 15s;
    }

    # Ürün servisi - daha yüksek timeout, büyük kataloglar için
    location /api/v1/products/ {
        proxy_pass http://urun_servisi;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_read_timeout 30s;
        proxy_cache urun_cache;
        proxy_cache_valid 200 5m;
    }

    # Sipariş servisi - session sticky gerekli
    location /api/v1/orders/ {
        proxy_pass http://siparis_servisi;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_read_timeout 60s;
    }

    # Ödeme servisi - en kritik, kısa timeout ve tek yedek
    location /api/v1/payment/ {
        proxy_pass http://odeme_servisi;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_connect_timeout 5s;
        proxy_read_timeout 45s;
        proxy_next_upstream error timeout http_502 http_503;
    }
}

Bu yapıda her servisin ihtiyacına göre farklı algoritmalar ve timeout değerleri kullandık. Sipariş servisi ip_hash kullandığı için sepet tutarlılığı sağlanmış. Ödeme servisi için ikinci sunucu sadece backup olarak işaretlenmiş, yani aktif load balancing yok, tek bir sunucu tüm trafiği alıyor.

Keepalive Bağlantıları ve Performans Optimizasyonu

Her istek için yeni bir TCP bağlantısı açmak maliyetlidir. keepalive direktifi ile Nginx, upstream sunucularla kalıcı bağlantı havuzu tutar.

upstream django_app {
    server 127.0.0.1:8000;
    server 127.0.0.1:8001;
    keepalive 64;
    keepalive_requests 1000;
    keepalive_timeout 60s;
}

server {
    listen 80;
    server_name www.orneksite.com;

    location / {
        proxy_pass http://django_app;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;

        # Buffer ayarları
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
        proxy_busy_buffers_size 8k;

        # Gzip sıkıştırma
        proxy_set_header Accept-Encoding "";
    }

    gzip on;
    gzip_proxied any;
    gzip_types text/plain application/json application/javascript text/css;
    gzip_min_length 1000;
}

Keepalive kullanırken proxy_http_version 1.1 ve proxy_set_header Connection "" ikilisini mutlaka eklemelisiniz. HTTP/1.0 varsayılan olarak her istekte bağlantıyı kapatır, bu direktifler HTTP/1.1 persistent connection davranışını etkinleştirir.

proxy_buffering: Backend’den gelen yanıtı tampona alır, ağır istemcilerin backend worker’larını meşgul etmesini engeller. proxy_buffer_size: İlk yanıt satırı ve header’lar için tampon boyutu. proxy_buffers: Yanıt gövdesi için tampon sayısı ve boyutu.

SSL Terminasyonu ve Güvenlik Yapılandırması

SSL’i backend sunucular yerine Nginx’te sonlandırmak yaygın ve mantıklı bir yaklaşımdır. Backend’e giden trafik iç ağda plain HTTP ile gider, sertifika yönetimi merkezileşir.

server {
    listen 443 ssl http2;
    server_name secure.orneksite.com;

    ssl_certificate /etc/letsencrypt/live/orneksite.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/orneksite.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # HSTS
    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;

    location / {
        proxy_pass http://uygulama_backend;
        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 https;

        # İstemci sertifika bilgisini backend'e ilet
        proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
    }
}

# HTTP'yi HTTPS'e yönlendir
server {
    listen 80;
    server_name secure.orneksite.com;
    return 301 https://$server_name$request_uri;
}

Upstream Durumunu İzleme ve Debug

Production’da bir şeyler ters gittiğinde hızlı teşhis için birkaç araç kullanabilirsiniz. Nginx’in yerleşik stub_status modülü temel metrikleri sunar.

# /etc/nginx/conf.d/monitoring.conf
server {
    listen 8080;
    server_name localhost;

    # Sadece yerel erişim
    allow 127.0.0.1;
    allow 10.0.0.0/8;
    deny all;

    location /nginx_status {
        stub_status on;
        access_log off;
    }
}

Bu endpoint’ten aldığınız çıktı şu bilgileri verir: aktif bağlantı sayısı, toplam kabul edilen bağlantılar, işlenen istekler. Prometheus ile bu metrikleri toplamak için nginx-prometheus-exporter kullanabilirsiniz.

Upstream sorunlarını anlık takip etmek için logları yapılandırın:

# /etc/nginx/nginx.conf içinde
log_format upstream_log '$remote_addr - $remote_user [$time_local] '
                        '"$request" $status $body_bytes_sent '
                        '"$http_referer" "$http_user_agent" '
                        'upstream: $upstream_addr '
                        'upstream_status: $upstream_status '
                        'upstream_time: $upstream_response_time '
                        'request_time: $request_time';

access_log /var/log/nginx/upstream.log upstream_log;

Bu log formatıyla hangi backend’in yavaş yanıt verdiğini, hangi sunucunun kaç hata döndürdüğünü kolayca analiz edebilirsiniz.

# Yavaş upstream'leri bul (2 saniyeden uzun yanıtlar)
awk '$NF > 2.0' /var/log/nginx/upstream.log | awk '{print $17}' | sort | uniq -c | sort -rn

# Upstream sunucu başına hata sayısı
grep "upstream_status: 5" /var/log/nginx/upstream.log | 
  awk '{for(i=1;i<=NF;i++) if($i=="upstream:") print $(i+1)}' | 
  sort | uniq -c | sort -rn

Dinamik Upstream Yönetimi

Nginx’i yeniden başlatmadan upstream havuzunu değiştirmek istediğinizde nginx -s reload komutunu kullanabilirsiniz. Bu işlem sıfır kesinti ile çalışır; mevcut bağlantılar tamamlanırken yeni yapılandırma devreye girer.

# Upstream'e yeni sunucu ekledikten sonra
sudo nginx -t && sudo nginx -s reload

# Config testini ayrıca çalıştır
sudo nginx -t -c /etc/nginx/nginx.conf

Daha gelişmiş dinamik upstream yönetimi için lua-nginx-module veya Nginx Plus’ın upstream API’si kullanılabilir. Açık kaynak dünyasında ise OpenResty tercih edilir. Bunlar olmadan bile basit bir script ile otomasyon sağlanabilir:

#!/bin/bash
# upstream_toggle.sh - Bir sunucuyu upstream'den geçici olarak çıkar

NGINX_CONF="/etc/nginx/conf.d/upstream.conf"
SERVER=$1
ACTION=$2

if [ "$ACTION" == "disable" ]; then
    sed -i "s/server $SERVER;/server $SERVER down;/" $NGINX_CONF
elif [ "$ACTION" == "enable" ]; then
    sed -i "s/server $SERVER down;/server $SERVER;/" $NGINX_CONF
fi

nginx -t && nginx -s reload
echo "Upstream $SERVER $ACTION yapildi ve reload tamamlandi."

Rate Limiting ile Aşırı Yük Koruması

Upstream sunucuları kötü niyetli ya da aşırı trafike karşı korumak için rate limiting kullanın:

# limit_req_zone global context'te tanımlanır (http bloğu içinde)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;

server {
    listen 80;
    server_name api.orneksite.com;

    location /api/ {
        limit_req zone=api_limit burst=20 nodelay;
        limit_req_status 429;

        proxy_pass http://api_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /api/v1/login {
        limit_req zone=login_limit burst=3 nodelay;
        limit_req_status 429;

        proxy_pass http://auth_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

rate=10r/s: Saniyede 10 istek sınırı. burst=20: Anlık spike’lara izin verilen ekstra istek sayısı. nodelay: Burst kapasitesi dahilindeki istekler geciktirilmeden işlenir. Bu yapı backend sunucularınızı DDoS saldırılarına ve scraper’lara karşı önemli ölçüde korur.

Sık Karşılaşılan Sorunlar ve Çözümleri

502 Bad Gateway: Backend sunucu çalışmıyor ya da yanlış port. proxy_pass adresini ve backend servisin durumunu kontrol edin. Firewall kuralları da buna neden olabilir.

504 Gateway Timeout: Backend çok yavaş yanıt veriyor. proxy_read_timeout değerini artırın veya backend performansını iyileştirin.

upstream sent invalid header: Backend yanlış HTTP yanıtı gönderiyor. proxy_ignore_headers veya proxy_pass_header ile başlık sorunlarını aşabilirsiniz.

“upstream timed out (110: Connection timed out)” hatasını görüyorsanız şu kontrolleri yapın:

# Backend erişilebilirliğini test et
curl -v http://192.168.1.10:8080/health

# Nginx worker proseslerini kontrol et
ps aux | grep nginx

# Aktif upstream bağlantılarını görüntüle
ss -tnp | grep nginx

Sonuç

Nginx ile reverse proxy ve upstream yönetimi, modern web altyapısının belkemiğini oluşturuyor. Temel proxy_pass direktifinden başlayıp gelişmiş load balancing algoritmalarına, health check mekanizmalarına ve mikroservis gateway yapılandırmalarına kadar Nginx size ihtiyaç duyduğunuz her aracı sunuyor.

Bu yazıda anlattıklarımı özetlersek: upstream bloğu ile backend havuzunuzu tanımlayın, trafiğin niteliğine göre doğru load balancing algoritmasını seçin, max_fails ve proxy_next_upstream ile otomatik failover sağlayın, keepalive bağlantıları ile performansı artırın ve detaylı log formatlarıyla sorunu hızlı teşhis edin.

Production’a almadan önce her zaman nginx -t ile config testini yapın, kademeli reload kullanın ve upstream sunucularınızın durumunu izleme sistemlerinize entegre edin. Bir Nginx reload işlemi saniyeler içinde tamamlansa da kötü bir yapılandırma tüm servisi çökertebilir. Staging ortamında test etmek her zaman en iyi pratiktir.

Yorum yapın