← blog · 19 Ekim 2026

HTTP Önbelleğini Doğru Kurmak: Cache-Control, ETag ve Paylaşımlı Önbellekte Veri Sızıntısı

no-cache ile no-store farkı, yanıt türüne göre doğru başlık, çalışmayan 304'ler ve CDN'de kişiye özel yanıtın başkasına verilmesine yol açan hatalar.

Önbellek başlıkları çoğu projede bir kez yazılır ve unutulur. Sonra iki şikâyetten biri gelir: ya dağıtımdan sonra kullanıcılar eski arayüzü görmeye devam eder, ya da daha kötüsü, bir kullanıcının hesap sayfası başka bir kullanıcıya gösterilir. İkisinin de kökü aynıdır: HTTP önbelleğinin kuralları sezgiyle değil, başlıklarla işler ve başlık yoksa tarayıcı ile ara katmanlar kendi tahminlerini yapar. Bu yazıda yanıt türüne göre doğru başlığı seçmeyi, doğrulama mekanizmasını ve paylaşımlı önbellekte veri sızdıran hataları ele alıyoruz.

İki tür önbellek, iki ayrı risk

Tarayıcının önbelleği özel önbellektir: yalnızca o kullanıcının yanıtlarını saklar. CDN, ters vekil ya da kurumsal vekil ise paylaşımlı önbellektir: bir kullanıcı için saklanan yanıtı başka kullanıcılara da verebilir. Tarayıcıda yapılan hata bayat içerik üretir. Paylaşımlı önbellekte yapılan hata ise veri sızıntısı üretir.

Bu yüzden ilk soru "ne kadar süre saklansın" değil, "bu yanıt kimin" olmalıdır. Kişiye özel her yanıt private ya da no-store almalıdır. public ve s-maxage yalnızca herkese aynı olan yanıtlar içindir.

Yönergelerin gerçek anlamı

En çok karıştırılan çift no-cache ile no-store. no-cache "saklama" demek değildir; "sakla ama her kullanımdan önce sunucuya sor" demektir. no-store ise yanıtın hiçbir yere yazılmamasını ister. Banka ekstresi gibi diske düşmemesi gereken içerik için no-store, sık değişen ama doğrulanabilen HTML için no-cache doğru seçimdir.

max-age hem özel hem paylaşımlı önbelleğe süre verir. s-maxage yalnızca paylaşımlı önbellekler için geçerlidir ve onlarda max-age'i ezer. Böylece tarayıcıya kısa, CDN'e uzun süre verebilirsiniz; CDN'i dağıtımda temizleyebiliyorsanız bu çok işe yarar, tarayıcıyı ise temizleyemezsiniz.

stale-while-revalidate süresi dolan yanıtın arka planda yenilenirken bir süre daha verilmesine izin verir. stale-if-error ise kaynak sunucu hata döndüğünde bayat yanıtla idare etmeyi sağlar. Her iki yönergeyi her istemci ve her CDN desteklemez; kullanmadan önce kendi sağlayıcınızın belgesine bakın.

Yanıt türüne göre önerilen başlıklar

Pratikte üç sınıf yeterlidir.

Parmak izli statik dosyalar (adında içerik özeti olan app.3f9a1c.js gibi): içerik değişince ad değişir, eski ad asla başka bir içeriğe işaret etmez. Bunlara en uzun süreyi verin:

Cache-Control: public, max-age=31536000, immutable

immutable, tarayıcının sayfa yenilemede bile bu dosya için doğrulama isteği göndermemesini sağlar.

HTML ve giriş noktası dosyaları: parmak izli dosyaların adlarını taşıyan belge budur. Bu belge uzun süre önbellekte kalırsa yeni sürüm hiç yüklenmez. Bunlar için:

Cache-Control: no-cache
ETag: "5d8c72a5"

Her istekte kısa bir doğrulama yapılır, değişmediyse sunucu gövdesiz 304 Not Modified döner.

Kişiye özel API yanıtları ve oturum gerektiren sayfalar:

Cache-Control: private, no-cache

ya da hassas veriyse no-store.

Bir nginx yapılandırmasında bu ayrım şöyle görünür:

location /assets/ {
    add_header Cache-Control "public, max-age=31536000, immutable" always;
}

location / {
    add_header Cache-Control "no-cache" always;
}

Doğrulama: ETag ve Last-Modified

Süre dolduktan sonra önbellek yanıtı çöpe atmaz, sunucuya If-None-Match (ETag için) ya da If-Modified-Since (Last-Modified için) ile sorar. Doğru kurulduğunda bu, büyük bir gövdeyi tekrar göndermek yerine birkaç yüz baytlık bir 304 demektir.

Kendi uç noktanızı şöyle sınayabilirsiniz:

curl -sI https://ornek.com/ | grep -i -E 'etag|cache-control|age'
curl -s -o /dev/null -w '%{http_code}\n' \
  -H 'If-None-Match: "5d8c72a5"' https://ornek.com/

İkinci komut 304 dönmüyorsa doğrulama çalışmıyor demektir.

Tuzaklar

Başlık yokluğu "önbelleğe alma" anlamına gelmez. Cache-Control göndermeyen ama Last-Modified gönderen bir yanıtı tarayıcılar sezgisel olarak önbelleğe alabilir; HTTP belirtimi buna izin verir ve yaygın uygulama, dosyanın yaşının yaklaşık onda biri kadar süredir. Bir ay önce değişmiş bir dosya böylece birkaç gün sorgusuz kullanılır. Her yanıtta açık bir Cache-Control yazın.

nginx'te add_header kalıtımı. Bir location bloğunda tek bir add_header yazdığınız anda üst seviyede tanımlı bütün add_header satırları o blokta geçersiz olur. Güvenlik başlıklarınız sessizce kaybolabilir. Ayrıca always olmadan bu başlıklar yalnızca başarılı yanıt kodlarına eklenir; hata sayfaları başlıksız kalır.

Birden fazla sunucuda farklı ETag. ETag'i dosya sistemi meta verisinden (inode, değiştirilme zamanı) üreten sunucular, aynı dosya için her makinede farklı değer üretebilir. Yük dengeleyici istekleri dağıttıkça doğrulama hep başarısız olur ve 304 hiç görülmez. ETag'i içerik özetinden üretin ya da dağıtım sürecinde değiştirilme zamanlarını sabitleyin. Sıkıştırma da ETag'i etkiler: bazı sunucular gövdeyi sıkıştırırken güçlü ETag'i zayıf (W/ önekli) hâle çevirir, bu normaldir.

Vary ile önbelleği ya bölmek ya da öldürmek. Vary: Accept-Encoding gereklidir ve zararsızdır. Vary: Cookie ise neredeyse her isteği ayrı bir önbellek girdisi yapar ve isabet oranı sıfıra iner. Kişiye özel yanıtı Vary ile paylaşımlı önbellekte "güvenli" saklamaya çalışmak yerine private işaretleyin.

Önbellek anahtarına girmeyen başlıklar. Uygulama yanıtı X-Forwarded-Host gibi bir başlığa göre üretiyor, ama CDN bu başlığı önbellek anahtarına katmıyorsa, saldırgan zehirli bir değerle bir istek gönderip üretilen yanıtı herkese servis ettirebilir. Buna önbellek zehirleme denir. Kural basittir: yanıtı değiştiren her girdi ya önbellek anahtarında olmalı ya da uygulama onu yok saymalıdır.

Yol karışıklığıyla gelen sızıntı. Bazı CDN'ler uzantıya bakarak önbelleğe alır: .css ile biten her şey statik sayılır. Uygulama /hesap/profil/x.css isteğini sessizce /hesap/profil olarak işliyorsa, kurbanın kişisel sayfası bir CSS dosyası gibi önbelleğe girer ve herkese açılır. Uzantı tabanlı kurallar yerine kaynak sunucunun Cache-Control başlığına uyan bir yapılandırma tercih edin ve bilinmeyen yolları 404 ile reddedin.

Hata yanıtlarının önbelleğe girmesi. Kısa bir kesinti sırasında dönen 404 ya da 502, uzun bir süreyle önbelleğe girerse kesinti bitse de kullanıcılar hatayı görmeye devam eder. Hata kodları için ayrı ve çok kısa süreler tanımlayın.

Set-Cookie taşıyan yanıtlar. Bir CDN'in çerez taşıyan yanıtı önbelleğe alıp almayacağı sağlayıcıdan sağlayıcıya değişir. Birinin oturum çerezi önbelleğe girip başkasına verilirse sonuç hesap ele geçirmedir. Çerez basan her yanıta private ya da no-store koyun, sağlayıcının varsayılanına güvenmeyin.

Temizleme mi, sürümlü ad mı

Dağıtımdan sonra CDN'i temizlemek (purge) çalışır, ama tarayıcı önbelleğine ulaşmaz ve temizleme isteği başarısız olduğunda kimse fark etmez. Sürümlü dosya adı ise önbelleği hiç temizlemek zorunda bırakmaz: yeni sürüm yeni bir addır. Bu yüzden statik dosyalarda tercih açıkça parmak izli addır. Temizlemeyi yalnızca HTML gibi adı sabit kalan, s-maxage ile CDN'de tutulan belgeler için kullanın.

Ne zaman önbelleklemeyi seçmemeli

Yanıt kişiye özelse ve üretilmesi ucuzsa paylaşımlı önbelleğe almaya çalışmayın; kazanç küçük, sızıntı riski büyüktür. Veri her saniye değişiyor ve bayat değer yanlış karara yol açıyorsa (stok, bakiye, fiyat), no-store ile başlayın ve gerçekten ölçülen bir yük sorunu çıkarsa uygulama içinde kontrol ettiğiniz bir önbellekle çözün. HTTP önbelleği, herkese aynı olan ve biraz bayatlığı tolere eden içerik için tasarlanmıştır; bu sınırın dışında kullanıldığında sorun çözmez, sorun saklar.

Dağıtım kontrol listenize tek bir satır ekleyin: her yanıt sınıfı için curl -sI ile Cache-Control, ETag ve Age başlıklarına bakın. Beklediğiniz değeri görmüyorsanız önbellek sizin yerinize karar veriyor demektir.