Site Hızı & Performans

    Cache-Control Başlıkları Rehberi

    Cache-Control direktiflerinin gerçek anlamları ve içerik tipine göre doğru başlık seçimi.

    9 dk okuma Güncellendi: 25 Ağustos 2026

    Bir sayfayı güncelledin ama ziyaretçiler eski hâlini görmeye devam ediyor. Ya da tam tersi: her ziyarette aynı 400 KB'lık CSS dosyası baştan indiriliyor ve sunucun gereksiz yere yoruluyor. Bu iki problemin de tek bir kaynağı var, o da Cache-Control başlıkları. Bu başlık, sunucunun tarayıcıya ve aradaki tüm önbelleklere "bu cevabı ne kadar saklayabilirsin ve saklayabilir misin" diye söylediği tek cümledir. Yanlış yazıldığında ya güncellemeler yayılmaz ya da önbellekleme hiç devreye girmez.

    Bu rehberde Cache-Control'ün tam olarak neyi kontrol ettiğini, her direktifin gerçek anlamını (özellikle sürekli karıştırılan no-cache ile no-store ayrımını), tarayıcı ile CDN'e ayrı ayrı süre vermeyi, doğrulama ve bayat sunum direktiflerini, Nginx ve Apache'de nasıl uygulanacağını ve içerik tipine göre hazır bir başlık tablosunu bulacaksın.

    Cache-Control Neyi Kontrol Eder#

    Cache-Control bir HTTP yanıt başlığıdır ve iki temel soruya cevap verir: bu yanıt saklanabilir mi, saklanabiliyorsa ne kadar süre "taze" sayılır. Bu iki kavramı ayrı düşünmen gerekir çünkü çoğu karışıklık buradan çıkar.

    Saklanabilirlik, yanıtın diske ya da belleğe yazılıp yazılamayacağıdır. Tazelik, saklanan kopyanın sunucuya sormadan doğrudan kullanılabileceği süredir. Tazelik süresi dolduğunda kopya silinmez; "bayat" (stale) hâle gelir ve kullanılmadan önce sunucuya doğrulatılması gerekir. Doğrulama genellikle çok ucuzdur: sunucu değişiklik yoksa gövdesiz bir 304 Not Modified döner. Bu mekaniği ETag ve Last-Modified farkı yazısında ayrıntısıyla ele aldım.

    Başlığı görmenin en pratik yolu komut satırıdır:

    # Bir kaynagin onbellek basliklarini gor
    curl -sSI https://firmaniz.com/assets/app.css \
      | grep -iE 'cache-control|expires|etag|last-modified|age|vary'
    
    # Ornek cikti:
    # cache-control: public, max-age=31536000, immutable
    # etag: "a1b2c3d4"
    # age: 51240
    

    Bir de karıştırılan iki eski başlık var. Expires, HTTP/1.0 döneminden kalma ve mutlak bir tarih verir; Cache-Control: max-age varsa modern tüm istemciler onu dikkate alır ve Expires yok sayılır. Pragma: no-cache ise bir istek başlığıdır ve bugün yalnızca çok eski istemciler için anlamlıdır. Yeni yapılandırmalarda ikisini de yazmana gerek yoktur; tek başına Cache-Control yeterlidir.

    Bir de isteğin kendi tarafında gönderilen direktifler var; bunları sunucu değil tarayıcı ya da bir ara istemci üretir. Kullanıcı sayfayı sert yenilediğinde tarayıcı Cache-Control: no-cache içeren bir istek yollar ve aradaki tüm önbelleklerden taze kopya ister; bu yüzden sert yenilemeyle yaptığın test gerçek kullanıcı deneyimini yansıtmaz. only-if-cached direktifi ise yalnızca önbellekteki kopyanın kullanılmasını, ağa hiç çıkılmamasını söyler ve çevrimdışı senaryolarda service worker kodları tarafından kullanılır. Sunucu tarafında bu direktifleri genellikle yönetmezsin, ama log'larında beklenmedik bir tam indirme dalgası gördüğünde sebebin istek tarafında olabileceğini bilmek teşhis süreni belirgin biçimde kısaltır.

    Direktifler Tek Tek#

    Direktifler virgülle ayrılarak birlikte yazılır. Sık kullanılanların anlamları şöyle:

    DirektifAnlamıNe zaman kullanılır
    publicPaylaşımlı önbellekler de saklayabilirHerkese aynı olan içerik
    privateYalnızca tarayıcı saklayabilirKişiye özel HTML
    no-storeHiçbir yere yazılmasınÖdeme, panel, hassas veri
    no-cacheSakla ama her kullanımdan önce doğrulatSık değişen ama büyük içerik
    max-age=NN saniye taze sayılırHer yerde
    s-maxage=NPaylaşımlı önbellekler için süreCDN'e ayrı süre vermek
    must-revalidateBayat kopya asla sunulmasınDoğruluğun kritik olduğu içerik
    immutableTazelik süresince yeniden doğrulama yapmaHash'li dosya adları
    stale-while-revalidate=NBayat kopyayı sun, arkada yenileYüksek trafikli sayfalar
    stale-if-error=NOrigin hata verirse bayat kopyayı sunKesinti dayanıklılığı

    Şimdi en çok yanlış anlaşılan ikiliye gelelim. no-cache, "önbellekleme" demek değildir. Tam tersine, yanıt saklanır; sadece her kullanımdan önce sunucuya "değişti mi" diye sorulur. Değişmemişse sunucu 304 döner ve gövde yeniden indirilmez. Yani no-cache aslında bant genişliğinden ciddi tasarruf sağlar.

    no-store ise gerçekten "hiç saklama" demektir. Yanıt ne diske ne belleğe yazılır, her istekte baştan indirilir. Bunu yalnızca ödeme adımları, oturum panelleri ve kişisel veri içeren yanıtlarda kullan. Statik dosyalara no-store vermek, en sık gördüğüm ve en pahalıya mal olan yapılandırma hatasıdır.

    private ile public ayrımı da önemlidir. private, "bu yanıt yalnızca bu kullanıcıya ait, CDN saklamasın" der. Oturum açmış kullanıcıya özel HTML üretiyorsan private doğru seçimdir; no-store gereksiz yere sert olur çünkü kullanıcının kendi tarayıcısında saklanmasında sakınca yoktur.

    max-age, s-maxage ve İki Katmanlı Süre#

    max-age tüm önbellekleri, s-maxage yalnızca paylaşımlı olanları (CDN, ters vekil) ilgilendirir. s-maxage varsa paylaşımlı önbellekler max-age'i yok sayar. Bu, tek bir başlıkla iki farklı ömür tanımlamanı sağlar ve pratikte en değerli tekniklerden biridir.

    # Tarayicida 1 dakika, CDN'de 12 saat
    add_header Cache-Control "public, max-age=60, s-maxage=43200";
    

    Neden asimetrik? Çünkü CDN'deki kopyayı istediğin an temizleyebilirsin, kullanıcının tarayıcısındakini temizleyemezsin. Bu yüzden riski kontrol edebildiğin katmana yığarsın: uçta uzun, tarayıcıda kısa. Uç sunucu ayarlarını Cloudflare cache kuralları yazısında pratik örneklerle anlattım.

    Süreyi seçerken kendine tek bir soru sor: bu içerik değiştiğinde eski hâlinin en fazla ne kadar süre görünmesine razıyım? Cevabın "hiç" ise dosya adına sürüm koy, TTL'i düşürme. Bu ayrım kritik: kısa TTL, hem önbelleği işlevsiz bırakır hem de sorunu tam çözmez.

    Bir de Age başlığı vardır ve okumayı bilmen gerekir. Age, yanıtın önbellekte kaç saniyedir durduğunu söyler. Kalan tazelik süresi max-age eksi Age kadardır. Age: 0 görüyorsan yanıt origin'den taze gelmiştir.

    Doğrulama ve Bayat Sunum Direktifleri#

    must-revalidate, bayat bir kopyanın hiçbir koşulda sunulmamasını zorunlu kılar. Normalde bazı önbellekler, sunucuya ulaşamadıklarında bayat kopyayı sunma özgürlüğüne sahiptir; must-revalidate bunu kapatır. Fiyat, stok, bakiye gibi yanlış gösterilmesi maliyetli verilerde anlamlıdır. Aksi hâlde gereksizdir ve dayanıklılığı azaltır.

    immutable, "bu URL'in içeriği asla değişmeyecek" taahhüdüdür. Kullanıcı sayfayı yenilediğinde tarayıcı normalde önbellekteki dosyaları doğrulatır; immutable bu doğrulamayı da atlatır. Yalnızca dosya adında içerik hash'i olan varlıklarda kullan. Adı sabit bir style.css dosyasına immutable verirsen güncellemen kullanıcılara aylarca ulaşmaz.

    stale-while-revalidate performans açısından en değerli direktiftir ve yeterince kullanılmaz. Tazelik süresi dolduğunda kullanıcı beklemek zorunda kalmaz: önbellek eski kopyayı anında sunar, yenilemeyi arka planda yapar. Yüksek trafikli bir sayfada TTL'in dolduğu an oluşan "hepsi birden origin'e koşuyor" dalgasını da yumuşatır.

    stale-if-error ise dayanıklılık direktifidir. Origin çöktüğünde ya da 5xx döndüğünde önbellek, belirtilen süre boyunca eski kopyayı sunmaya devam eder. Tek satırlık bu ekleme, kısa kesintilerde sitenin okunabilir kalmasını sağlar.

    # Yogun trafikli liste sayfasi icin dengeli bir set
    add_header Cache-Control "public, max-age=0, s-maxage=300, stale-while-revalidate=600, stale-if-error=86400";
    

    Buradaki max-age=0, tarayıcının her ziyarette doğrulama yapmasını ama CDN'in beş dakika boyunca doğrudan sunmasını sağlar. Uç katmandaki bu davranışın mimarisini edge caching nedir yazısında ele aldım.

    Nginx ve Apache'de Uygulama#

    Nginx tarafında konum blokları ile içerik tipine göre başlık verirsin. Dikkat edilecek nokta, add_header direktifinin bir blokta tanımlanmışsa üst bloktakileri devralmamasıdır; her blokta ihtiyacın olan başlıkları eksiksiz yazmalısın.

    # Hash'li varliklar: bir yil, dogrulama bile yapma
    location ~* "-[a-f0-9]{8,}\.(js|css)$" {
        add_header Cache-Control "public, max-age=31536000, immutable";
        access_log off;
    }
    
    # Adi sabit varliklar ve gorseller: uzun ama immutable degil
    location ~* \.(jpg|jpeg|png|webp|avif|gif|svg|ico|woff2)$ {
        add_header Cache-Control "public, max-age=2592000";
    }
    
    # HTML: tarayicida cok kisa, uc sunucuda orta
    location / {
        add_header Cache-Control "public, max-age=60, s-maxage=3600, stale-while-revalidate=120";
    }
    
    # Panel ve odeme: hicbir yere yazilmasin
    location ^~ /hesabim/ {
        add_header Cache-Control "no-store, private";
    }
    

    Apache kullanıyorsan aynı mantığı mod_headers ve mod_expires ile kurarsın. Paylaşımlı hostingde bu ayarlar genellikle .htaccess dosyasına yazılır:

    # Varliklara uzun omur
    <FilesMatch "\.(js|css|jpg|jpeg|png|webp|avif|gif|svg|ico|woff2)$">
        Header set Cache-Control "public, max-age=2592000"
    </FilesMatch>
    
    # HTML kisa omurlu
    <FilesMatch "\.(html|htm)$">
        Header set Cache-Control "public, max-age=60, s-maxage=3600"
    </FilesMatch>
    

    .htaccess üzerinden önbellek ve sıkıştırma kurulumunun tamamını htaccess önbellek ve sıkıştırma yazısında bulabilirsin. cPanel kullanıyorsan panelin kendi önbellek yöneticisi de benzer başlıkları üretir; ayrıntısı cPanel önbellek yöneticisi rehberinde.

    İçerik Tipine Göre Hazır Tablo#

    Aşağıdaki tabloyu başlangıç noktası olarak kullanabilirsin. Her satır, sahada işe yaradığını gördüğüm dengeyi yansıtıyor.

    İçerikÖnerilen başlık
    Hash'li JS/CSSpublic, max-age=31536000, immutable
    Görsel, font (adı sabit)public, max-age=2592000
    Blog / kategori HTMLpublic, max-age=60, s-maxage=3600, stale-while-revalidate=300
    Ürün sayfası HTMLpublic, max-age=0, s-maxage=300, stale-while-revalidate=600
    Genel API (herkese aynı)public, max-age=30, s-maxage=120
    Kişiye özel APIprivate, no-store
    Sepet / ödeme / panelno-store, private
    robots.txt, site haritasıpublic, max-age=3600

    Bir uyarı: hiç Cache-Control göndermezsen tarayıcılar sezgisel önbellekleme yapabilir. Last-Modified varsa, dosyanın yaşının küçük bir oranı kadar süreyi tazelik olarak varsayabilirler. Yani "başlık yazmazsam önbelleklenmez" varsayımı yanlıştır; davranış öngörülemez hâle gelir. Her yanıt için açık bir başlık göndermek her zaman daha iyidir.

    Sık Yapılan Hatalar#

    En yaygın hata, no-cache yazıp önbelleklemeyi kapattığını sanmaktır. Gerçekte yanıt saklanır, sadece doğrulanır. Gerçekten saklanmasını istemiyorsan no-store yazman gerekir.

    İkinci hata, adı sabit dosyalara immutable ya da çok uzun max-age vermektir. style.css dosyasına bir yıl verirsen yaptığın değişiklik mevcut ziyaretçilere pratikte hiç ulaşmaz. Uzun süre vermenin ön koşulu, dosya adının içerikle birlikte değişmesidir.

    Üçüncü hata, HTML'e uzun max-age vermektir. Tarayıcıdaki kopyayı temizleyemezsin; içerik güncellendiğinde kullanıcı günlerce eskisini görür. HTML'de tarayıcı süresini kısa tut, uzunluğu s-maxage ile CDN tarafına yığ.

    Dördüncü hata, Vary başlığını savurganca kullanmaktır. Vary: User-Agent gönderirsen her tarayıcı sürümü için ayrı kopya tutulur ve önbellek işlevsiz kalır. Meşru tek yaygın kullanım Vary: Accept-Encoding'dir.

    Beşinci hata, Nginx'te add_header devralma kuralını bilmemektir. Alt blokta bir add_header tanımladığın anda üst bloktakiler o blok için geçersiz olur; güvenlik başlıkların sessizce kaybolabilir. Her blokta tam listeyi yaz ya da devralmayı açıkça yönet.

    Sıkça Sorulan Sorular#

    no-cache ile no-store arasındaki fark nedir#

    no-cache, yanıtın saklanmasına izin verir ama her kullanımdan önce sunucuya doğrulatılmasını zorunlu kılar; içerik değişmemişse sunucu gövdesiz 304 döner ve bant genişliğinden tasarruf edilir. no-store ise yanıtın hiçbir yere yazılmamasını söyler, her istekte baştan indirilir. Hassas veri içermeyen içeriklerde no-store kullanmak gereksiz yere maliyetlidir.

    max-age değerini kaç saniye yapmalıyım#

    İçeriğin ne sıklıkta değiştiğine ve eski hâlinin ne kadar süre görünmesine razı olduğuna bağlıdır. Dosya adında içerik hash'i varsa bir yıl güvenle verilebilir. Adı sabit görsellerde bir ay makuldür. HTML'de tarayıcı süresini dakikalar mertebesinde tut ve uzun süreyi s-maxage ile CDN'e ver, çünkü uçtaki kopyayı istediğin an temizleyebilirsin.

    s-maxage ile max-age birlikte kullanılabilir mi#

    Evet, hatta önerilen kullanım budur. max-age tarayıcıyı, s-maxage ise CDN ve ters vekil gibi paylaşımlı önbellekleri ilgilendirir; s-maxage varsa paylaşımlı önbellekler max-age değerini yok sayar. Böylece tek bir başlıkla iki farklı ömür tanımlar, riski kontrol edebildiğin katmana yığarsın.

    immutable her yerde destekleniyor mu#

    Desteklemeyen istemciler bu direktifi yok sayar ve normal davranışa döner; yani zararı yoktur, sadece o istemcilerde ek kazanç sağlamaz. Asıl dikkat edeceğin nokta destek değil, doğru kullanımdır: yalnızca dosya adında içerik hash'i bulunan varlıklarda kullan. Adı sabit bir dosyada güncellemelerinin yayılmasını engeller.

    Cache-Control başlığımı nasıl test ederim#

    En güvenilir yol komut satırıdır: curl -sSI https://firmaniz.com/dosya.css komutu yanıt başlıklarını olduğu gibi gösterir. Tarayıcı geliştirici araçlarındaki Network sekmesi de işe yarar ama tarayıcının kendi önbelleği araya girebilir, o yüzden "Disable cache" seçeneğini kapatıp gerçek davranışı gözlemlemen gerekir. Değişiklik sonrası CDN kullanıyorsan uçtaki kopyayı temizlemeyi unutma.

    Expires başlığını hâlâ yazmalı mıyım#

    Gerekli değildir. Cache-Control: max-age varsa modern istemcilerin tamamı onu dikkate alır ve Expires yok sayılır. İkisini birlikte yazmak zararsızdır ama bakımı zorlaştırır çünkü iki ayrı yerde tutarlılık sağlaman gerekir. Yeni yapılandırmalarda yalnızca Cache-Control kullan.

    Kapanış#

    Cache-Control, önbellek davranışını belirleyen tek ve en güçlü araçtır; doğru yazıldığında hem sunucunu rahatlatır hem de kullanıcıya anlık açılan sayfalar sunar. Akılda tutulması gereken dört alışkanlık şu: no-cache ile no-store ayrımını asla karıştırma, uzun süreleri yalnızca adı içerikle birlikte değişen dosyalara ver, tarayıcı ve CDN sürelerini max-age ile s-maxage üzerinden ayrı yönet, stale-while-revalidate ve stale-if-error ile hem gecikmeyi hem kesinti riskini düşür.

    Bu başlıkları sunucu tarafında hayata geçirmek istersen htaccess önbellek ve sıkıştırma yazımız paylaşımlı hosting için, Nginx FastCGI cache rehberimiz kendi sunucun için yol gösterir. Hız odaklı bir zemin arıyorsan web hosting ve WordPress hosting paketlerimize, tam kontrol istiyorsan VDS sunucularımıza bakabilirsin. Yapılandırmayı bize bırakmak istersen sunucu yönetimi hizmetimiz bu ayarları senin yerine kurar ve izler.

    HTTPÖnbellekPerformans

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.