Geçen ay, mustafaerbay.com blogumda, özellikle yeni yazılar yayınladığımda veya popüler bir içeriğe trafik geldiğinde, sunucu yükünde anlamsız yükselişler görmeye başladım. Kendi VPS’imde 13’ten fazla Docker container yönetiyorum ve bir tanesi sevimsiz davranmaya başladığında, diğerleri de swap’e iniyor, kcompactd CPU’nun %90’ını yiyor ve SSH bile accept edemez hale geliyordu. Cloudflare’ın önünde olduğu bir site için bu durum beni şaşırttı. Hatta bir ara Astro build process’inin 2.5 GB RAM yiyip sistemdeki 7.6 GB RAM’i zorladığı bir anı anımsattı. Oysa Cloudflare’ın cache’i her şeyi yoluna koymalıydı, değil mi?
Sorun aslında Cloudflare’ın kendisi değil, benim cache bypass kurallarına olan güvenim ve origin sunucumdan gelen Cache-Control header’larının yarattığı kör noktaydı. Özellikle Astro’nun varsayılan max-age=0 döndürmesi, Cloudflare ile kurduğum cache mantığını nasıl alt üst ettiğini fark etmem biraz zamanımı aldı. Bu yazıda, bu cache kör noktasını nasıl teşhis ettiğimi, nedenlerini ve Cloudflare ile Nginx’i kullanarak cache davranışını nasıl kontrol altına aldığımı anlatacağım.
Sorun: Cache Bypass Kuralının Beklenmeyen Etkileri
Kendi blogumda, özellikle mustafaerbay.com/feed.xml gibi dinamik sayılabilecek veya /api/* gibi backend servisleri için Cloudflare üzerinde bazı cache bypass kuralları tanımlamıştım. Amacım, bu belirli URL’lerin her zaman güncel veriyi origin’den çekmesini sağlamaktı. Mantıklı geliyordu. Ancak zamanla fark ettim ki, bu kurallar beklenmedik yan etkilere yol açıyordu.
Özellikle blog yazılarımın bulunduğu /blog/* path’indeki statik içerikler bile zaman zaman doğrudan origin sunucuma gelmeye başlıyordu. Cloudflare panelinde bazı request’ler için cf-cache-status: HIT görsem de, origin sunucumun CPU ve I/O yükü, cache’in olması gerektiği gibi çalışmadığını gösteriyordu. Bu durum, özellikle yoğun trafik anlarında sunucumun aşırı yüklenmesine, hatta bir noktada Docker disk yangınına dönüştü. 33 GB build cache ve 23 GB unused image ile disk %100 doldu, bu da VPS’in tamamen kilitlenmesine neden oldu. Bu, sadece bir cache problemi değil, aynı zamanda operasyonel bir kabusa dönüşen zincirleme bir reaksiyondu.
Semptomlar ve İlk Gözlemler
- Yüksek Origin Yükü: Sunucumun CPU ve RAM kullanımı, Cloudflare’ın cache’i önünde olmasına rağmen beklenenden çok daha fazlaydı.
htop’tanodeprocess’lerinin zirve yaptığını görüyordum. - Değişken Sayfa Yükleme Süreleri: Bazı kullanıcılar sayfaları ışık hızında açarken, diğerleri bariz bir gecikme yaşıyordu. Bu, cache’in tutarsız çalıştığının açık bir işaretiydi.
cf-cache-status: BYPASS: Tarayıcı geliştirici araçlarında veyacurlile yaptığım testlerde, statik olması gereken bazı içerikler içincf-cache-status: BYPASSheader’ını görmeye başladım. Bu benim için ilk kırmızı bayraktı.- Disk Doluluğu: Artan trafik ve beklenmedik origin istekleri, log dosyalarının hızla büyümesine ve Docker container’larının geçici dosyalarının şişmesine neden oldu. Bu da yukarıda bahsettiğim %100 disk doluluğuna giden yolu açtı.
Teşhis: Nereden Başladım?
Sorunu anlamak için adım adım ilerledim. Paniklemek yerine, elimdeki verileri ve araçları kullanarak ne olup bittiğini anlamaya çalıştım.
Cloudflare Logları ve Analytics
İlk olarak Cloudflare panelindeki Analytics kısmına göz attım. Edge cache hit oranları, genel olarak iyi görünse de, bazı URL pattern’leri için Cache Bypass oranının yüksek olduğunu fark ettim. Bu, hangi request’lerin origin’e ulaştığına dair ilk ipucuydu. Özellikle Security ve Firewall loglarında, hangi kuralların tetiklendiğini ve bunun cache davranışını nasıl etkilediğini inceledim.
Origin Sunucu Logları
Nginx ve Node.js (Astro uygulamamın çalıştığı) loglarına daldım. Nginx’in access.log dosyasında, normalde Cloudflare’dan gelmesi gereken statik blog yazısı URL’leri için sürekli 200 OK yanıtları görmeye başladım. Bu loglarda X-Forwarded-For header’ına bakarak isteğin Cloudflare’dan mı yoksa doğrudan mı geldiğini teyit ettim. grep ve awk komutlarıyla belirli URL pattern’leri için istek sayılarını hızlıca karşılaştırdım.
# Nginx access log'unda "/blog/" içeren istekleri say
grep "/blog/" /var/log/nginx/access.log | wc -l
# Belirli bir IP'den (Cloudflare IP aralığı) gelen istekleri filtrele
grep "172.68.XXX.XXX" /var/log/nginx/access.log | grep "/blog/" | wc -l
curl ve httpie ile Testler
En somut verileri curl komutlarıyla elde ettim. Farklı header’lar (özellikle Cookie veya User-Agent) göndererek cache davranışını test ettim. Cloudflare’ın cf-cache-status header’ı, bir isteğin cache’ten mi geldiğini (HIT), origin’e mi gittiğini (BYPASS), yoksa ilk kez mi cache’lendiğini (MISS) net bir şekilde gösterir.
# Blog yazısı için cache durumunu kontrol et
curl -svo /dev/null https://mustafaerbay.com/blog/cloudflare-cachein-kor-noktasi-bypass-kuralinin-bedeli 2>&1 | grep -i "cf-cache-status"
# Cookie ile isteği test et (Cloudflare genellikle Cookie varsa bypass eder)
curl -svo /dev/null -H "Cookie: my_session_id=123" https://mustafaerbay.com/blog/cloudflare-cachein-kor-noktasi-bypass-kuralinin-bedeli 2>&1 | grep -i "cf-cache-status"
Browser Developer Tools
Son olarak, tarayıcımın geliştirici araçlarını (F12) açıp Network sekmesinde, sayfayı yenilediğimde gelen isteklerin header’larını inceledim. cf-cache-status ve cache-control header’ları burada da belirgin bir şekilde görünüyordu. Özellikle cf-cache-status: BYPASS ve Cache-Control: max-age=0 kombinasyonlarını gördüğümde problemin kaynağını iyice anlamaya başladım.
Root Cause Analizi: Neden Bypass Ediliyor?
Tüm bu teşhis adımları beni iki temel soruna yönlendirdi: Cloudflare’ın cache bypass kurallarının geniş kapsamı ve origin sunucumdan gelen Cache-Control header’larının yanlış yorumlanması.
Cloudflare Page Rules vs. Cache Rules
Cloudflare’da iki ana kural seti var: Page Rules ve Cache Rules. Page Rules daha eski ve daha genel amaçlıyken, Cache Rules daha spesifik olarak cache davranışını kontrol etmek için tasarlandı. Benim senaryomda, Page Rules’da tanımladığım bir kural, belirli path’ler için Cache Level: Bypass olarak ayarlanmıştı. Bu kural, *mustafaerbay.com/api/* gibi dinamik endpoint’leri hedeflerken, yanlışlıkla veya kuralın kapsamı nedeniyle /blog/* gibi statik içerikleri de etkiliyordu.
Cloudflare’ın varsayılan cache davranışı da burada önemliydi. Genellikle GET isteklerini cache’ler, ancak Cookie header’ı içeren istekleri, Query String içerenleri veya Cache-Control: no-cache gibi direktifleri olanları varsayılan olarak bypass eder. Benim bypass kuralım bu varsayılan davranışla birleşince, statik içerikler bile origin’e yönlendiriliyordu.
Cache-Control Header’ının Rolü
En büyük kör nokta, Astro uygulamamın varsayılan olarak döndürdüğü Cache-Control header’ıydı. Astro, statik site üreticisi olsa da, Node.js tabanlı bir SSR (Server-Side Rendering) veya API endpoint’i olarak çalıştığında, varsayılan olarak çoğu zaman Cache-Control: max-age=0, must-revalidate gibi header’lar döndürebiliyor.
Bu header, Cloudflare’a “bu içeriği anında geçerliliğini kontrol et” veya “cache’leme, her zaman origin’e git” komutu veriyordu. Benim Cloudflare bypass kuralım olmasa bile, origin’den gelen bu max-age=0 direktifi, Cloudflare’ın cache’lemesini engelliyordu. Cache-Control: public, max-age=X görmeyi beklerken, max-age=0 görmek, Cloudflare’ın içerikleri cache’ine almaması anlamına geliyordu. Kendi blogumda Astro’dan gelen max-age=0’ın beni bayağı uğraştırdığını söyleyebilirim. Bu durum, Cloudflare’ın güçlü cache mekanizmasını bile etkisiz hale getiriyordu.
Çözüm: Cache Davranışını Kontrol Altına Almak
Sorunun kök nedenini anladıktan sonra, Cloudflare ve Nginx’i kullanarak cache davranışını tam olarak kontrol altına almak için adımlar attım.
Cloudflare Cache Rules ile Daha Hassas Kontrol
İlk adım, Cloudflare’daki eski Page Rules’ı gözden geçirmek ve yerine daha spesifik Cache Rules tanımlamaktı.
- Mevcut Bypass Kurallarını İncele ve Daralt: Mevcut Page Rules’da bulunan
Cache Level: Bypassayarlı kuralları, sadece gerçekten dinamik olması gereken/api/*veya/login/*gibi path’lere özel hale getirdim./blog/*gibi statik içerikleri bu kuralların dışına çıkardım. - Statik İçerikler İçin Cache Rule Tanımla:
/blog/*path’indeki tüm içerikler için yeni bir Cache Rule oluşturdum. Bu kural,Edge Cache TTL’i belirli bir süreye (örneğin 1 saat veya 1 gün) ayarlıyor veCache Level’ıCache Everythingolarak belirliyor. Ayrıca,Bypass Cache on Cookiegibi ayarları sadece gerçekten gerektiği yerlerde (/account/*gibi) aktif tuttum.
Örnek bir Cloudflare Cache Rule (API’den veya Terraform ile tanımlarken bu formata benzer):
{
"rules": [
{
"id": "mustafa_blog_cache_rule",
"action": {
"id": "cache_settings",
"value": {
"edge_cache_ttl": 3600, // 1 saat
"cache_level": "cache_everything"
}
},
"expression": "(http.request.uri.path contains \"/blog/\") and (not http.cookie)"
},
{
"id": "mustafa_api_bypass_rule",
"action": {
"id": "cache_settings",
"value": {
"edge_cache_ttl": 0, // Bypass
"cache_level": "bypass_cache"
}
},
"expression": "http.request.uri.path contains \"/api/\""
}
]
}
Bu örnek, /blog/ path’indeki cookie içermeyen istekleri 1 saat boyunca cache’lerken, /api/ path’indeki tüm istekleri bypass ediyor. Cloudflare UI’dan bu kuralları oluşturmak çok daha kolay ve görseldir.
Origin Server ile Nginx Override
Cloudflare Cache Rules, Cloudflare tarafındaki cache’i yönetmek için harika. Ancak, origin sunucumdan gelen Cache-Control: max-age=0 header’ı hala bir sorundu. Cloudflare, bu direktife saygı duyma eğilimindeydi. Bu yüzden, Nginx’i kullanarak bu header’ı override etmeye karar verdim.
Nginx’in proxy_hide_header ve add_header direktiflerini kullanarak, origin’den gelen Cache-Control ve Pragma header’larını gizledim ve yerine kendi istediğim Cache-Control header’ını ekledim. Bu, Cloudflare’ın max-age=0 direktifini görmesini engelledi ve benim belirlediğim cache süresiyle içeriği cache’lemesini sağladı.
server {
listen 80;
server_name mustafaerbay.com;
# Cloudflare'dan gelen HTTP isteğini HTTPS'e yönlendir (isteğe bağlı, ama genel olarak iyi pratik)
# if ($http_x_forwarded_proto != 'https') {
# return 301 https://$host$request_uri;
# }
location /blog/ {
# Astro'dan gelen cache-control'u ve pragma'yı gizle
proxy_hide_header Cache-Control;
proxy_hide_header Pragma;
# Kendi cache-control'umuzu ekle: 1 saat boyunca public cache'lensin
add_header Cache-Control "public, max-age=3600";
proxy_pass http://localhost:3000; # Astro app'in çalıştığı port
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;
}
# Diğer location blokları (örneğin /api/ veya root path)
location / {
proxy_pass http://localhost:3000;
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;
}
# ... diğer Nginx ayarları
}
Bu Nginx konfigürasyonuyla, /blog/ path’i altındaki tüm istekler için Astro’dan gelen Cache-Control header’ı Nginx tarafından gizleniyor ve yerine Cache-Control: public, max-age=3600 header’ı ekleniyor. Bu sayede Cloudflare, bu içeriği 1 saat boyunca cache’leyebiliyor.
Uygulama Adımları ve Doğrulama
Bu değişiklikleri uyguladıktan sonra, her şeyin beklediğim gibi çalıştığından emin olmak için kapsamlı testler yaptım.
Adım 1: Mevcut Kuralları İncele ve Temizle
Cloudflare panelindeki tüm Page Rules ve Cache Rules’ı gözden geçirdim. Çakışan veya gereksiz bulduğum kuralları ya sildim ya da kapsamlarını daralttım. Özellikle /blog/* path’ini etkileyen tüm bypass kurallarını devre dışı bıraktım.
Adım 2: Origin Header’larını Kontrol Et
Nginx konfigürasyonunu değiştirmeden önce, curl -I http://localhost:3000/blog/yazi-adi gibi komutlarla doğrudan Astro uygulamasından gelen header’ları kontrol ettim. Cache-Control: max-age=0’ı gördüğümden emin oldum.
Adım 3: Cloudflare Cache Kuralı Tanımla
Yukarıda bahsettiğim mustafa_blog_cache_rule kuralını Cloudflare UI üzerinden tanımladım. Edge Cache TTL’i 1 saat olarak ayarladım ve Cache Level’ı Cache Everything yaptım. Cookie bypass’ı gibi seçenekleri bu kural için kapatarak, her koşulda cache’lenmesini sağladım (tabii Nginx’ten gelen Cache-Control header’ını dikkate alarak).
Adım 4: Nginx Konfigürasyonunda Değişiklikler
VPS’ime SSH ile bağlanıp /etc/nginx/sites-available/mustafaerbay.com dosyasını düzenledim ve yukarıdaki Nginx konfigürasyonunu uyguladım.
sudo nano /etc/nginx/sites-available/mustafaerbay.com
Değişiklikleri kaydettikten sonra Nginx konfigürasyonunu test ettim ve yeniden başlattım:
sudo nginx -t
sudo systemctl reload nginx
Adım 5: Test ve Doğrulama
Son olarak, curl ve tarayıcı geliştirici araçlarını kullanarak tekrar test ettim:
curl -svo /dev/null https://mustafaerbay.com/blog/cloudflare-cachein-kor-noktasi-bypass-kuralinin-bedeli 2>&1 | grep -i "cf-cache-status\|cache-control"
Artık cf-cache-status: HIT ve Cache-Control: public, max-age=3600 header’larını görüyordum. Bu, Cloudflare’ın içeriği başarıyla cache’lediği ve origin’e gereksiz yük bindirmediği anlamına geliyordu. Sunucumun CPU ve RAM kullanımı da belirgin şekilde düştü. Özellikle bloguma gelen trafik arttığında bile, Nginx loglarında 200 OK yerine 304 Not Modified veya BYPASS yerine HIT görmeye başladım.
Öğrenilen Dersler ve En İyi Pratikler
Bu olay bana birkaç önemli ders öğretti:
- Header’lar Kritik: HTTP header’ları, özellikle
Cache-Control, bir sistem mimarisinde en küçük ama en etkili detaylardan biri olabilir. Cloudflare gibi bir CDN kullanırken, origin sunucudan gelen header’ların nasıl yorumlandığını iyi anlamak gerekiyor. - Kapsamlı Test Şart: Sadece Cloudflare panelinde her şey yolunda görünüyor diye, her şeyin gerçekten yolunda olduğu anlamına gelmez.
curlile farklı senaryoları test etmek, gerçek davranışları görmek için şart. - Bypass Kurallarına Dikkat: Cache bypass kuralları güçlüdür, ancak onları olabildiğince spesifik tutmak gerekiyor. Geniş kapsamlı kurallar, benim yaşadığım gibi beklenmedik sonuçlara yol açabilir. Her zaman “bu kural neleri etkileyecek?” diye sormak lazım.
- Monitoring Vazgeçilmez: Performans monitörlerini sürekli izlemek, anormal origin yükleri veya disk dolulukları gibi sorunları erken tespit etmek için kritik. Benim gibi kendi VPS’inde her şeyi yöneten biri için bu daha da önemli hale geliyor. Bu durum bana pipeline reliability’deki “preflight resource guard” prensibini bir kez daha hatırlattı. Her zaman bir kaynağa yüklenmeden önce kontrol etmek, sonradan gelen yangınları söndürmekten iyidir.
- Trade-off’ları Anla: Cache’lemek her zaman iyi değildir. Dinamik içerikler için bypass etmek gerekebilir. Ancak statik içeriklerin de dinamik gibi davranmasını istemiyorsak, Nginx gibi bir ara katmanla bu trade-off’u yönetmek gerekiyor.
Sonuç
Cloudflare’ın cache mekanizması güçlü bir araç, ancak bypass kurallarını ve origin sunucudan gelen Cache-Control header’larını doğru yönetmek, performans ve operasyonel istikrar açısından hayati önem taşıyor. Benim yaşadığım deneyim, statik içeriklerin bile yanlış konfigürasyonlar nedeniyle origin’e yük bindirebileceğini gösterdi. Cloudflare Cache Rules ve Nginx’in proxy_hide_header ile add_header direktiflerini birleştirerek bu kör noktayı aştım ve blogumun çok daha istikrarlı çalışmasını sağladım.
Bu süreçte öğrendiklerim, benim gibi kendi sunucusunu yöneten ve performansın her milisaniyesine dikkat edenler için önemli dersler taşıyor. Senin de böyle canını sıkan bir cache hikayen var mı? Ya da bu sorunları çözmek için farklı yaklaşımların oldu mu? Yorumlarda duymak isterim.