Tarayıcı bir dosyayı önbelleğinde tutuyor ama tazelik süresi dolmuş. Şimdi ne olacak? İki seçenek var: dosyayı baştan indirmek ya da sunucuya "bendeki kopya hâlâ geçerli mi" diye sormak. İkinci yol neredeyse her zaman daha ucuzdur ve bunu mümkün kılan iki başlık vardır: ETag ve Last-Modified. Bu ikisi arasındaki fark küçük görünür ama yanlış yapılandırıldığında bant genişliğini boşa harcar, çoklu sunucu kurulumlarında ise önbelleği tamamen işlevsiz hâle getirir.
Bu rehberde koşullu isteğin nasıl çalıştığını, Last-Modified ile ETag'in ayrı ayrı mekaniğini, güçlü ve zayıf ETag ayrımını, ikisi birlikte gönderildiğinde hangisinin kazandığını, yük dengeli kurulumlarda ETag'in neden sorun çıkardığını ve ne zaman ikisini de kapatmanın doğru olduğunu anlatacağım. Önbellek sürelerinin nasıl belirlendiğini önce netleştirmek istersen Cache-Control başlıkları rehberi bu yazının ön hazırlığı sayılır.
Koşullu İstek Nedir ve Neden Var#
Bir kaynağın önbellekteki kopyası bayatladığında tarayıcı onu silmez. Bunun yerine sunucuya koşullu istek gönderir: "Bu dosyayı istiyorum, ama elimdeki sürüm hâlâ geçerliyse gövdeyi gönderme." Sunucu değişiklik olmadığını görürse gövdesiz bir 304 Not Modified yanıtı döner.
Kazanç somuttur. 300 KB'lık bir dosya için 304 yanıtı yalnızca birkaç yüz bayt başlıktan ibarettir. Yani gidiş-dönüş süresini ödersin ama veriyi indirmezsin. Yavaş mobil bağlantılarda bu fark belirgindir.
Koşullu isteğin iki yolu vardır ve ikisi de aynı mantığı farklı ölçütle uygular:
| Sunucunun gönderdiği | Tarayıcının geri gönderdiği | Karşılaştırma ölçütü |
|---|---|---|
Last-Modified | If-Modified-Since | Dosyanın değiştirilme zamanı |
ETag | If-None-Match | İçeriğin kimliği (parmak izi) |
Akışı kendi sunucunda görmek istersen iki komutla kanıtlayabilirsin:
# 1) Once dogrulama basliklarini al
curl -sSI https://firmaniz.com/assets/app.css | grep -iE 'etag|last-modified'
# etag: "6a3f19-1b2c"
# last-modified: Tue, 12 Aug 2026 09:14:22 GMT
# 2) Elindeki surumle kosullu istek yap: 304 bekleriz
curl -sS -o /dev/null -w '%{http_code} %{size_download} bayt\n' \
-H 'If-None-Match: "6a3f19-1b2c"' \
https://firmaniz.com/assets/app.css
# 304 0 bayt
304 ve sıfır bayt gördüysen doğrulama düzgün çalışıyor demektir. 200 ve tam dosya boyutu görüyorsan bir sorun var; sebepleri aşağıda.
Last-Modified ve If-Modified-Since#
Last-Modified, kaynağın en son ne zaman değiştiğini bildiren bir tarih başlığıdır. Statik dosyalarda web sunucusu bunu doğrudan dosya sistemindeki değişiklik zamanından üretir; dinamik sayfalarda uygulaman kendisi hesaplamak zorundadır.
Tarayıcı bir sonraki istekte bu tarihi If-Modified-Since başlığında geri yollar. Sunucu dosyanın değişiklik zamanını karşılaştırır: dosya o tarihten sonra değişmediyse 304 döner.
Bu yöntemin iki yapısal sınırı var ve ikisini de bilmen gerekir.
Birincisi çözünürlük. HTTP tarihleri saniye hassasiyetindedir. Aynı saniye içinde iki kez değişen bir dosyada ikinci değişiklik fark edilmeyebilir. Statik varlıklarda bu nadiren sorun olur, ama sık üretilen dinamik içerikte yanıltıcı 304 yanıtlarına yol açabilir.
İkincisi anlamdır. Değişiklik zamanı, içeriğin gerçekten değiştiği anlamına gelmez. Dosyayı sunucuya yeniden yüklediğinde ya da bir dağıtım aracı dosyaları kopyaladığında içerik aynı kalsa bile zaman damgası değişir. Sonuç: tarayıcı aynı içeriği baştan indirir. Bu, dağıtım sonrası "neden herkes her şeyi yeniden indiriyor" sorusunun en sık cevabıdır.
Buna karşılık Last-Modified'ın önemli bir yan faydası vardır: Cache-Control hiç göndermediğin durumlarda tarayıcılar sezgisel önbellekleme yapar ve bu hesabı Last-Modified üzerinden kurar. Yani başlık yoksa bile bir tazelik süresi tahmin edilir. Bu davranışa güvenmemeni, açık başlık yazmanı öneririm; ama varlığını bilmek beklenmedik önbellekleme davranışlarını açıklar.
ETag ve If-None-Match#
ETag (entity tag), kaynağın o anki içeriğini temsil eden bir kimliktir. Genellikle içerikten türetilen bir hash ya da dosya boyutu ile değişiklik zamanının birleşiminden üretilen bir dize olur. Tarayıcı bir sonraki istekte bunu If-None-Match başlığıyla geri yollar; sunucu üretilen yeni ETag ile karşılaştırır ve eşleşirse 304 döner.
ETag'in Last-Modified'a üstünlüğü şudur: içeriğe bakar, zamana değil. Dosyayı yeniden yükledin ama içerik aynı kaldıysa ETag değişmez ve tarayıcı 304 alır. Aynı saniye içindeki değişiklikler de doğru yakalanır.
Nginx statik dosyalar için ETag'i varsayılan olarak üretir; açıkça yönetmek istersen:
# Statik dosyalar icin ETag ve tarih dogrulamasi
etag on;
if_modified_since exact;
location /assets/ {
add_header Cache-Control "public, max-age=2592000";
}
if_modified_since exact ayarı, tarih karşılaştırmasının tam eşleşme ile yapılmasını sağlar; before seçeneği daha gevşektir ve "bu tarihten önce" mantığıyla çalışır. Doğruluk önceliğindeyse exact daha güvenlidir.
Apache tarafında ETag üretimini FileETag direktifi yönetir:
# Icerik kimligini yalnizca degisiklik zamani ve boyuttan uret
FileETag MTime Size
Tarihsel olarak Apache'nin varsayılanı dosyanın inode numarasını da içeriyordu ve bu, birden fazla sunucu kullanan kurulumlarda ciddi bir soruna yol açıyordu; bir sonraki bölümün konusu tam olarak bu.
Güçlü ve Zayıf ETag#
ETag iki biçimde gelir ve aralarındaki fark pratikte önemlidir.
Güçlü ETag tırnak içinde yazılır: "6a3f19-1b2c". Anlamı, "iki kaynak bayt bayt aynıdır" demektir. Kısmi içerik istekleri (Range ile video ilerletme, indirme devam ettirme) yalnızca güçlü ETag ile güvenle çalışır.
Zayıf ETag başında W/ taşır: W/"6a3f19-1b2c". Anlamı, "içerik anlamsal olarak eşdeğerdir ama bayt bayt aynı olmayabilir" demektir. Doğrulama için yeterlidir, kısmi istekler için değildir.
Zayıf ETag'in en yaygın ortaya çıkış sebebi sıkıştırmadır. Sunucu bir dosyayı gzip veya brotli ile sıkıştırdığında çıktı artık orijinal baytlar değildir. Nginx bu durumda güçlü ETag'i zayıfa çevirir; böylece doğrulama çalışmaya devam eder ama kısmi istek yanlışlıkla desteklenmiş gibi görünmez. Bu davranış doğrudur ve müdahale etmene gerek yoktur.
| Özellik | Güçlü ETag | Zayıf ETag |
|---|---|---|
| Yazım | "deger" | W/"deger" |
| Anlamı | Bayt bayt aynı | Anlamca eşdeğer |
304 doğrulaması | Çalışır | Çalışır |
Range istekleri | Güvenli | Kullanılmaz |
| Tipik kaynağı | Sıkıştırılmamış statik dosya | Sıkıştırılmış yanıt |
Bir de öncelik kuralı var: yanıtta hem ETag hem Last-Modified varsa ve tarayıcı ikisini birden geri gönderirse, sunucu If-None-Match değerini esas alır. Yani ETag her zaman tarihi yener. İkisini birlikte göndermek yaygındır ve zararsızdır; ETag doğruluğu, Last-Modified ise sezgisel önbelleklemeye zemin sağlar.
ETag'in doğrulama dışında ikinci bir kullanımı daha var ve API yazıyorsan işine yarayacak: eşzamanlı yazma çakışmalarını önlemek. İstemci bir kaydı okurken ETag'i alır, güncelleme isteğinde bunu If-Match başlığıyla geri gönderir. Kayıt bu arada başka biri tarafından değiştirilmişse ETag artık tutmaz ve sunucu 412 Precondition Failed döner; böylece iki kullanıcının birbirinin değişikliğini sessizce ezmesi engellenir. If-Unmodified-Since aynı işi zaman damgasıyla yapar ama saniye çözünürlüğü nedeniyle daha zayıf bir güvencedir. Bu kalıba iyimser kilitleme denir ve ETag'i yalnızca bir önbellek aracı olarak görmemen gerektiğini gösterir.
Çoklu Sunucuda ETag Tutarsızlığı#
Şimdi teşhis edilmesi en zor senaryoya gelelim. Sitene iki web sunucusu hizmet veriyor ve önlerinde bir yük dengeleyici var. Aynı dosya iki sunucuda da duruyor, içerikleri birebir aynı. Ama sunucular farklı ETag üretiyorsa ne olur?
Tarayıcı birinci sunucudan aldığı ETag ile koşullu istek yapar. İstek bu kez ikinci sunucuya düşer. İkinci sunucu kendi ETag'ini üretir, gelen değerle eşleşmez ve 304 yerine tam dosyayı gönderir. Kullanıcı her ziyarette her dosyayı yeniden indirir; önbellek var gibi görünür ama hiç işe yaramaz.
Bunun klasik sebebi, ETag üretimine dosyanın inode numarasının dahil edilmesidir. Inode, dosyanın o disk üzerindeki kimliğidir ve her sunucuda farklıdır. İçerik aynı olsa bile ETag değişir. Çözüm ETag'i inode'dan arındırmaktır:
# Coklu sunucuda tutarli ETag: inode kullanma
FileETag MTime Size
Yine de iki sunucudaki dosyaların değişiklik zamanları dağıtım sırasında farklılaşabilir. Bu durumda MTime de tutarsızlık üretir. En sağlam çözüm, dağıtımda dosya zaman damgalarını korumak (rsync -a gibi araçlar bunu yapar) ya da ETag'i tamamen kapatıp doğrulamayı Last-Modified üzerinden yürütmektir.
# Yuk dengeli kurulumda ETag'i kapatip tarih dogrulamasina birak
etag off;
if_modified_since before;
Doğrusunu söylemek gerekirse, en iyi çözüm doğrulamayı tamamen gereksiz kılmaktır. Statik varlıklarda dosya adına içerik hash'i koyup uzun ömürlü bir başlık verirsen, tarayıcı hiç koşullu istek yapmaz:
location ~* "-[a-f0-9]{8,}\.(js|css)$" {
add_header Cache-Control "public, max-age=31536000, immutable";
}
Bu yaklaşımda ETag tartışması tamamen ortadan kalkar, çünkü gidiş-dönüş hiç yapılmaz. Aynı fikrin tarayıcı tarafındaki karşılığını tarayıcı önbelleği nasıl çalışır yazısında bulabilirsin.
Sık Yapılan Hatalar#
En yaygın hata, "ETag güvenlik açığı" iddiasına dayanarak onu kapatmaktır. Eski Apache varsayılanının inode sızdırdığı doğruydu, ama modern yapılandırmalarda ETag zaten inode içermez. ETag'i kapatmanın gerçek gerekçesi güvenlik değil, çoklu sunucu tutarsızlığıdır.
İkinci hata, dinamik yanıtlarda ETag'i her istekte yeniden hesaplayarak sunucuyu yormaktır. Büyük bir JSON yanıtının hash'ini her seferinde hesaplamak, gönderilen baytlardan tasarruf ederken CPU harcar. İçeriğin bir sürüm numarası varsa ETag'i ondan türetmek çok daha ucuzdur.
Üçüncü hata, 304 yanıtına gövde ya da gereksiz başlık koymaktır. 304 gövdesiz olmalıdır; bazı ara katmanlar buna uymayan yanıtlarda beklenmedik davranır. Uygulama katmanında elle 304 üretiyorsan gövdenin boş olduğundan emin ol.
Dördüncü hata, doğrulamanın ücretsiz olduğunu sanmaktır. 304 gövdeyi indirtmez ama gidiş-dönüş süresini yine ödetir. Mobilde bu, dosya başına yüz milisaniyeler demektir. On varlık için on ayrı doğrulama, ölçülebilir bir gecikmedir. Bu yüzden hedef, doğrulamayı hızlandırmak değil, gereksiz kılmaktır.
Beşinci hata, ölçümü tarayıcıda yapıp sonucu yanlış okumaktır. Sert yenileme (Ctrl+F5) doğrulamayı da atlatır ve her şeyi 200 olarak gösterir. Gerçek davranışı görmek için normal gezinme yap ya da yukarıdaki curl testini kullan.
Sıkça Sorulan Sorular#
ETag mi Last-Modified mi kullanmalıyım#
Tek sunucu kullanıyorsan ikisini birden göndermek en iyisidir: ETag doğruluğu sağlar, Last-Modified ise başlık eksikliğinde sezgisel önbelleklemeye zemin verir. Yük dengeli çoklu sunucu kurulumunda ETag tutarsızlık üretiyorsa onu kapatıp Last-Modified ile devam edebilirsin. En iyi senaryo ise hash'li dosya adları kullanıp doğrulamayı tamamen gereksiz kılmaktır.
ETag'i kapatmak siteme zarar verir mi#
Doğrudan zarar vermez; doğrulama Last-Modified üzerinden yürümeye devam eder. Kaybettiğin şey, içeriği değişmediği hâlde zaman damgası değişen dosyalarda 304 alabilme imkânıdır. Yani bazı durumlarda gereksiz yere tam indirme olur. Buna karşılık çoklu sunucuda ETag tutarsızsa kapatmak, zaten hiç çalışmayan bir mekanizmayı temizlemekten ibarettir.
304 yanıtı hız kazandırır mı#
Evet, ama sınırlı biçimde. Gövde indirilmediği için bant genişliğinden ciddi tasarruf edersin; buna karşılık ağ gidiş-dönüşünü yine ödersin. Yavaş bağlantılarda büyük dosyalar için kazanç belirgindir. Ancak onlarca küçük varlığın her biri için ayrı 304 yapılıyorsa toplam gecikme hâlâ hissedilir; bu durumda uzun ömürlü başlıklarla doğrulamayı büsbütün ortadan kaldırmak daha iyidir.
Sıkıştırma neden ETag'imi değiştiriyor#
Sunucu içeriği gzip veya brotli ile sıkıştırdığında gönderilen baytlar orijinalinden farklı olur. Güçlü ETag "bayt bayt aynı" iddiasında bulunduğu için bu iddia artık geçerli değildir; sunucular bu yüzden ETag'i W/ önekiyle zayıf hâle getirir. Bu doğru ve beklenen davranıştır, doğrulamayı bozmaz ve müdahale etmen gerekmez.
Dinamik sayfalarda ETag kullanmalı mıyım#
Yanıt herkese aynıysa ve içerikten ucuza bir kimlik türetebiliyorsan evet. Örneğin bir ürün kaydının güncelleme sürümünü ETag olarak kullanmak hem ucuzdur hem doğrudur. Buna karşılık her istekte büyük bir yanıtın tamamının hash'ini hesaplamak CPU açısından pahalıdır; tasarruf ettiğin bant genişliğinden fazlasını işlemcide harcayabilirsin.
Tarayıcı neden hiç koşullu istek göndermiyor#
Muhtemelen kaynağın tazelik süresi hâlâ dolmamıştır; taze bir kopya doğrudan kullanılır, sunucuya hiç sorulmaz. Bir diğer ihtimal immutable direktifi kullanmandır: bu direktif, tazelik süresi boyunca yenilemede bile doğrulama yapılmamasını söyler. Üçüncü ihtimal, yanıtın no-store ile gelmesidir; o zaman saklanacak bir kopya olmadığı için doğrulanacak bir şey de yoktur.
Kapanış#
ETag ve Last-Modified aynı işi iki farklı ölçütle yapar: biri içeriğin kimliğine, diğeri değişiklik zamanına bakar. İkisi de bayat bir kopyayı yeniden indirmeden kullanmanı sağlar ve doğru kurulduğunda ciddi bant genişliği tasarrufu getirir. Aklında tutman gereken dört alışkanlık şu: ETag üretimine inode gibi sunucuya özgü değerler karıştırma, çoklu sunucuda tutarlılığı test etmeden ETag'e güvenme, 304'ün gövdeyi değil turu ortadan kaldırdığını unutma, ve statik varlıklarda hash'li dosya adı kullanarak doğrulamayı büsbütün gereksiz kıl.
Bu ayarları hayata geçirmek istersen paylaşımlı hosting tarafında htaccess önbellek ve sıkıştırma yazımız, kendi sunucun için Nginx FastCGI cache rehberimiz yol gösterir. Hızlı bir zemin arıyorsan web hosting ve WordPress hosting paketlerimize, tam kontrol ve kendi sunucu yapılandırman için VDS seçeneklerimize bakabilirsin. Yapılandırmayı bize bırakmak istersen sunucu yönetimi hizmetimiz bu başlıkları senin yerine doğru kurar.