Nginx proxy_cache ile Önbellekleme Yapılandırması

Web sunucunuzun her istek geldiğinde aynı içeriği tekrar tekrar üretmesi ciddi bir kaynak israfıdır. Özellikle yüksek trafikli sitelerde bu durum hem sunucu yükünü artırır hem de kullanıcı deneyimini olumsuz etkiler. Nginx’in yerleşik proxy_cache modülü, arka uç sunuculardan gelen yanıtları disk veya bellekte saklayarak sonraki isteklerde doğrudan önbellekten servis yapmanızı sağlar. Bu yazıda, gerçek dünya senaryolarıyla Nginx önbellekleme yapılandırmasını baştan sona ele alacağız.

proxy_cache Nedir ve Neden Kullanmalısınız?

Nginx’i bir reverse proxy olarak kullandığınızda, her istek arka uç uygulamanıza (Node.js, PHP-FPM, bir başka web sunucusu, vs.) iletilir. Bu arka uç sunucular yanıtı üretip geri döner. Ancak aynı içerik defalarca isteniyorsa her seferinde bu döngüyü yaşamak gereksizdir.

proxy_cache modülü bu noktada devreye girer. Arka uçtan gelen yanıtı bir kez alır, belirlediğiniz konuma kaydeder ve sonraki aynı isteklerde arka uca hiç dokunmadan önbellekten yanıt verir. Sonuç olarak:

  • Arka uç yükü dramatik biçimde düşer, uygulama sunucunuz nefes alır
  • Yanıt süreleri azalır, önbellekten dönen yanıtlar milisaniyeler içinde gelir
  • Bant genişliği tasarrufu sağlanır, aynı veri tekrar tekrar transfer edilmez
  • Ani trafik artışlarına karşı tampon görevi görür, slashdot etkisi gibi senaryolarda sitenizi ayakta tutar

Önce Ortamı Hazırlayalım

Yapılandırmaya geçmeden önce sisteminizde Nginx’in proxy_cache desteğiyle derlendiğini doğrulayın.

nginx -V 2>&1 | grep -o with-http_proxy_module
# Çıktı: with-http_proxy_module görünmüyorsa modül dahil değildir
# Çoğu dağıtımın paket yöneticisinden gelen Nginx bu modülü varsayılan olarak içerir

nginx -V 2>&1 | tr -- - 'n' | grep proxy

Önbellek verilerini tutacak bir dizin oluşturun ve Nginx kullanıcısına gerekli izinleri verin:

mkdir -p /var/cache/nginx/proxy_cache
chown -R nginx:nginx /var/cache/nginx
chmod -R 755 /var/cache/nginx

# Dizinin varlığını ve izinleri doğrulayın
ls -la /var/cache/nginx/

Temel proxy_cache Yapılandırması

Nginx’te önbellekleme iki aşamada tanımlanır. Önce http bloğunda önbellek alanını (proxy_cache_path) bildirirsiniz, ardından server veya location bloklarında bu önbelleği aktif edersiniz.

Önce /etc/nginx/nginx.conf dosyanızdaki http bloğuna önbellek alanı tanımını ekleyin:

# /etc/nginx/nginx.conf içinde http {} bloğuna ekleyin

http {
    # Önbellek alanı tanımı
    proxy_cache_path /var/cache/nginx/proxy_cache
                     levels=1:2
                     keys_zone=MAIN_CACHE:10m
                     max_size=1g
                     inactive=60m
                     use_temp_path=off;

    # Diğer http direktifleri...
    include /etc/nginx/conf.d/*.conf;
}

Bu satırı parça parça inceleyelim:

  • /var/cache/nginx/proxy_cache: Önbellek dosyalarının saklanacağı dizin
  • levels=1:2: Dizin hiyerarşisi derinliği. Çok sayıda dosya tek dizinde birikmesin diye alt dizinler oluşturur
  • keys_zone=MAIN_CACHE:10m: Önbellek anahtarlarının tutulacağı paylaşımlı bellek bölgesinin adı ve boyutu. 10MB yaklaşık 80.000 anahtarı tutabilir
  • max_size=1g: Önbelleğin maksimum disk alanı. Bu sınıra ulaşıldığında eski veriler temizlenir
  • inactive=60m: 60 dakika içinde hiç erişilmeyen önbellek girdisi silinir
  • use_temp_path=off: Geçici dosyaları ayrı dizine yazmak yerine doğrudan önbellek dizinine yazar, I/O maliyetini azaltır

Sanal Sunucu Yapılandırması

Şimdi bir web uygulamasını önbellekleyecek site yapılandırmasını oluşturalım. Örneğimizde Node.js uygulaması 3000 portunda çalışıyor:

# /etc/nginx/conf.d/myapp.conf

upstream backend_app {
    server 127.0.0.1:3000;
    keepalive 32;
}

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

    # Önbellek anahtarı için kullanılacak değişken
    proxy_cache_key "$scheme$request_method$host$request_uri";

    location / {
        proxy_pass http://backend_app;
        proxy_cache MAIN_CACHE;

        # Hangi HTTP durum kodlarının önbellekleneceği ve süreleri
        proxy_cache_valid 200 301 302 10m;
        proxy_cache_valid 404 1m;
        proxy_cache_valid any 30s;

        # Önbellek bypass koşulları
        proxy_cache_bypass $http_pragma $http_authorization;
        proxy_no_cache $http_pragma $http_authorization;

        # Arka uç bilgilerini ilet
        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;

        # Önbellek durumunu response header'a ekle (debug için)
        add_header X-Cache-Status $upstream_cache_status;
        add_header X-Cache-Date $upstream_http_date;
    }

    # Statik dosyalar için ayrı önbellek yok, doğrudan servis
    location ~* .(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {
        proxy_pass http://backend_app;
        proxy_cache MAIN_CACHE;
        proxy_cache_valid 200 7d;
        expires 7d;
        add_header Cache-Control "public, no-transform";
        add_header X-Cache-Status $upstream_cache_status;
    }
}

Yapılandırmayı test edip uygulayın:

nginx -t && nginx -s reload

upstream_cache_status ile Önbelleği İzleme

Yapılandırmanızın çalışıp çalışmadığını doğrulamak için X-Cache-Status header’ına bakmanız yeterlidir. curl ile kolayca kontrol edebilirsiniz:

# İlk istek - MISS bekliyoruz (önbellekte yok)
curl -I http://example.com/

# İkinci istek - HIT bekliyoruz (önbellekten geldi)
curl -I http://example.com/

# Çıktıda şunu arayın:
# X-Cache-Status: HIT   -> önbellekten geldi
# X-Cache-Status: MISS  -> arka uçtan geldi, önbelleğe alındı
# X-Cache-Status: BYPASS -> önbellek atlandı
# X-Cache-Status: EXPIRED -> önbellek süresi dolmuş, yenileniyor
# X-Cache-Status: STALE -> arka uç yanıt vermedi, eski önbellek sunuldu
# X-Cache-Status: UPDATING -> önbellek güncelleniyor, geçici eski veri sunuldu

Gerçek Dünya Senaryosu: E-ticaret Sitesi Yapılandırması

E-ticaret sitelerinde her sayfayı aynı şekilde önbelleğe alamazsınız. Ürün listeleme sayfaları önbelleğe alınabilirken, sepet ve hesap sayfaları kullanıcıya özel olduğu için önbelleğe alınmamalıdır.

# /etc/nginx/conf.d/ecommerce.conf

proxy_cache_path /var/cache/nginx/ecommerce
                 levels=1:2
                 keys_zone=ECOM_CACHE:20m
                 max_size=5g
                 inactive=2h
                 use_temp_path=off;

# Önbellek bypass için değişkenler
map $request_uri $bypass_cache {
    default                 0;
    ~*/cart*               1;
    ~*/checkout*           1;
    ~*/account*            1;
    ~*/admin*              1;
    ~*/wp-admin*           1;
}

map $http_cookie $has_session {
    default                 0;
    ~*PHPSESSID            1;
    ~*wordpress_logged_in  1;
    ~*woocommerce_cart     1;
}

server {
    listen 443 ssl http2;
    server_name shop.example.com;

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

    # Oturum açmış kullanıcılar veya özel sayfalar için önbellek bypass
    set $no_cache 0;

    if ($bypass_cache = 1) {
        set $no_cache 1;
    }

    if ($has_session = 1) {
        set $no_cache 1;
    }

    if ($request_method = POST) {
        set $no_cache 1;
    }

    location / {
        proxy_pass http://woocommerce_backend;
        proxy_cache ECOM_CACHE;
        proxy_cache_key "$scheme$host$request_uri$is_args$args";
        proxy_cache_valid 200 15m;
        proxy_cache_valid 404 1m;
        proxy_cache_bypass $no_cache;
        proxy_no_cache $no_cache;

        # Önbellek eski veriyle çalışmaya devam etsin, arka uç meşgulse
        proxy_cache_use_stale error timeout updating
                              http_500 http_502 http_503 http_504;

        # Arka uç meşgulken sadece bir istek geçsin, diğerleri beklesin
        proxy_cache_lock on;
        proxy_cache_lock_timeout 5s;

        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;

        add_header X-Cache-Status $upstream_cache_status always;
    }
}

Bu yapılandırmada dikkat edilmesi gereken önemli direktifler şunlardır:

  • proxy_cache_use_stale: Arka uç hata verdiğinde veya güncelleme yaparken eski önbellek verisini sunmaya devam eder. Kullanıcılar 502 hatası görmek yerine biraz eski veriyi görür, bu genellikle çok daha iyi bir deneyimdir
  • proxy_cache_lock: Önbellekte olmayan bir URL için aynı anda onlarca istek gelirse, sadece bir tanesi arka uca iletilir, diğerleri yanıtı bekler. “Thundering herd” problemini çözer
  • proxy_cache_lock_timeout: Kilidi bekleyecek maksimum süre

Önbellek Temizleme (Cache Purge)

İçeriğinizi güncellediğinizde önbelleği temizlemeniz gerekir. Nginx açık kaynak sürümünde ngx_cache_purge modülü ayrıca derlenmesi gereken bir modüldür. Ancak manuel temizleme yöntemi her zaman çalışır:

# Tüm önbelleği temizle
find /var/cache/nginx/proxy_cache -type f -delete
nginx -s reload

# Belirli bir URL'nin önbelleğini temizle
# Önce önbellek anahtarının MD5 hash'ini bul
echo -n "GET http://example.com/urun/kamera-123" | md5sum
# Çıktı: a1b2c3d4e5f6...

# Bu hash'e karşılık gelen dosyayı sil (levels=1:2 için)
# Son karakter -> ilk dizin, son 3. ve 4. karakter -> ikinci dizin
find /var/cache/nginx/proxy_cache -name "a1b2c3d4e5f6*" -delete

Daha pratik bir yaklaşım için Nginx’e özel bir purge endpoint tanımlayabilirsiniz. Bu endpoint’i yalnızca dahili ağdan erişilebilir yapın:

# Purge endpoint - sadece dahili IP'lerden erişilebilir
geo $purge_allowed {
    default 0;
    127.0.0.1 1;
    10.0.0.0/8 1;
    192.168.0.0/16 1;
}

map $request_method $purge_method {
    PURGE $purge_allowed;
    default 0;
}

server {
    location / {
        proxy_pass http://backend_app;
        proxy_cache MAIN_CACHE;
        proxy_cache_purge $purge_method;  # ngx_cache_purge modülü gerektirir
    }
}

ngx_cache_purge modülü olmadan basit bir shell scripti ile çözüm:

#!/bin/bash
# /usr/local/bin/nginx-purge.sh

CACHE_DIR="/var/cache/nginx/proxy_cache"
URL=$1

if [ -z "$URL" ]; then
    echo "Kullanim: $0 <url>"
    echo "Ornek: $0 'http://example.com/urun/kamera-123'"
    exit 1
fi

# Önbellek anahtarı formatı yapılandırmanıza göre değişir
CACHE_KEY="GET${URL}"
HASH=$(echo -n "$CACHE_KEY" | md5sum | awk '{print $1}')

# levels=1:2 için dosya yolu hesapla
LEVEL1="${HASH: -1}"
LEVEL2="${HASH: -3:2}"
CACHE_FILE="${CACHE_DIR}/${LEVEL1}/${LEVEL2}/${HASH}"

if [ -f "$CACHE_FILE" ]; then
    rm -f "$CACHE_FILE"
    echo "Önbellek temizlendi: $URL"
    echo "Dosya: $CACHE_FILE"
else
    echo "Önbellekte bulunamadı: $URL"
fi

Performans Optimizasyonları

Önbellek Boyutu ve Bellek Ayarları

Büyük ölçekli ortamlar için önbellek boyutunu ve bellek ayarlarını dikkatli yapılandırın:

# /etc/nginx/nginx.conf - http bloğu içinde

# Birden fazla önbellek bölgesi tanımlayabilirsiniz
proxy_cache_path /var/cache/nginx/static_cache
                 levels=1:2
                 keys_zone=STATIC_CACHE:50m
                 max_size=20g
                 inactive=7d
                 use_temp_path=off;

proxy_cache_path /var/cache/nginx/api_cache
                 levels=1:2
                 keys_zone=API_CACHE:5m
                 max_size=500m
                 inactive=5m
                 use_temp_path=off;

# Proxy buffer ayarları - büyük yanıtlar için
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
proxy_temp_file_write_size 8k;

# Bağlantı zaman aşımları
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 30s;

Koşullu Önbellekleme ile Akıllı TTL

Her içerik türü için farklı önbellek süreleri tanımlamak performansı önemli ölçüde artırır:

# API yanıtları için kısa TTL
location /api/ {
    proxy_pass http://api_backend;
    proxy_cache API_CACHE;
    proxy_cache_key "$host$request_uri$http_accept$http_accept_language";
    proxy_cache_valid 200 30s;
    proxy_cache_valid 404 10s;

    # Arka uç header'larını dikkate al
    proxy_cache_revalidate on;

    # Minimum istek sayısı dolmadan önbelleğe alma
    proxy_cache_min_uses 3;

    add_header X-Cache-Status $upstream_cache_status;
    add_header Cache-Control "no-store, no-cache";
}

# Statik assets için uzun TTL
location /static/ {
    proxy_pass http://static_backend;
    proxy_cache STATIC_CACHE;
    proxy_cache_key "$host$request_uri";
    proxy_cache_valid 200 7d;
    proxy_cache_valid 301 1d;

    # Etag ve Last-Modified header'larını kullanarak yeniden doğrula
    proxy_cache_revalidate on;

    expires 7d;
    add_header Cache-Control "public, immutable";
    add_header X-Cache-Status $upstream_cache_status;
}
  • proxy_cache_revalidate: Önbellek süresi dolduğunda içeriği tamamen yeniden almak yerine If-Modified-Since başlığıyla kontrol eder, bant genişliği tasarrufu sağlar
  • proxy_cache_min_uses: Bir URL bu kadar kez istenmedikçe önbelleğe alınmaz. Tek seferlik isteklerin önbelleği doldurmesini engeller

Önbellek İzleme ve Sorun Giderme

Önbellek sağlığını izlemek için log formatınızı zenginleştirin:

# /etc/nginx/nginx.conf - http bloğu içinde
log_format cache_log '$remote_addr - $remote_user [$time_local] '
                     '"$request" $status $body_bytes_sent '
                     '"$http_referer" "$http_user_agent" '
                     'cache_status=$upstream_cache_status '
                     'cache_time=$upstream_response_time '
                     'host=$host';

server {
    access_log /var/log/nginx/cache_access.log cache_log;
}

Önbellek performansını analiz etmek için pratik komutlar:

# HIT oranını hesapla
awk '{print $14}' /var/log/nginx/cache_access.log | 
    sort | uniq -c | sort -rn

# Örnek çıktı:
# 8543 cache_status=HIT
# 1234 cache_status=MISS
# 89   cache_status=BYPASS
# 45   cache_status=EXPIRED

# HIT yüzdesi
TOTAL=$(awk '{print $14}' /var/log/nginx/cache_access.log | wc -l)
HITS=$(awk '{print $14}' /var/log/nginx/cache_access.log | grep -c HIT)
echo "HIT orani: $(echo "scale=2; $HITS * 100 / $TOTAL" | bc)%"

# Önbellek dizininin boyutunu kontrol et
du -sh /var/cache/nginx/proxy_cache/
du -sh /var/cache/nginx/proxy_cache/* | sort -h | tail -20

# Önbellekte kaç dosya var
find /var/cache/nginx/proxy_cache -type f | wc -l

Yaygın Hatalar ve Çözümleri

Sorun: Önbellek hiç dolmuyor, sürekli MISS alıyorum

Arka uç uygulamanız Cache-Control: no-cache veya Cache-Control: private header’ı gönderiyorsa Nginx bu yanıtları önbelleğe almaz. Bunu geçersiz kılmak için:

location / {
    proxy_pass http://backend;
    proxy_cache MAIN_CACHE;

    # Arka ucun Cache-Control header'ını gizle
    proxy_hide_header Cache-Control;
    proxy_hide_header Pragma;

    # Kendi önbellek kuralını uygula
    proxy_cache_valid 200 10m;

    add_header X-Cache-Status $upstream_cache_status;
}

Sorun: Set-Cookie header’ı olan yanıtlar önbelleğe alınmıyor

Nginx varsayılan olarak Set-Cookie header’ı içeren yanıtları önbelleğe almaz:

location / {
    proxy_pass http://backend;
    proxy_cache MAIN_CACHE;

    # Set-Cookie header'ını gizle, önbelleğe alınmasını sağla
    proxy_hide_header Set-Cookie;
    proxy_ignore_headers Set-Cookie;

    proxy_cache_valid 200 10m;
}

Sorun: Önbellek dosyaları oluşturuluyor ama bir sonraki istekte MISS alıyorum

proxy_cache_key değişkenini kontrol edin. İstekler arasında farklılık varsa (örneğin query string parametreleri farklı sırada geliyorsa) Nginx bunları farklı önbellek girdileri olarak değerlendirir:

# Sorunlu: query string sırası değişince farklı anahtar oluşur
proxy_cache_key "$scheme$host$request_uri";

# Çözüm: parametreleri normalize et veya belirli parametreleri dahil et
proxy_cache_key "$scheme$host$uri$arg_page$arg_category";

Sonuç

Nginx proxy_cache, doğru yapılandırıldığında web uygulamalarınızın performansını katlarına çıkarabilecek güçlü bir araçtır. Temel ilkeler şöyle özetlenebilir: önbellek alanını sisteminizin kapasitesine göre boyutlandırın, kullanıcıya özel içerikleri kesinlikle önbellekten çıkarın, proxy_cache_use_stale ile yüksek erişilebilirliği artırın ve proxy_cache_lock ile ani trafik artışlarına karşı kendinizi koruyun.

X-Cache-Status header’ını her zaman etkin tutun; bu header size önbelleğin ne kadar verimli çalıştığını anında gösterir. Hedefleyeceğiniz HIT oranı uygulamanızın doğasına göre değişir ama %70’in üzeri genel olarak iyi bir işarettir.

Yapılandırmanızı üretim ortamına almadan önce mutlaka yük testleri yapın, farklı URL desenleri için önbellek davranışını doğrulayın ve log formatınıza önbellek bilgilerini ekleyerek uzun vadeli izleme altyapısını kurun. Kademeli bir yaklaşımla, önce statik içeriklerden başlayıp sonra dinamik içeriklere geçerek ilerlemenizi tavsiye ederim.

Yorum yapın