meta charset utf-8: AdSense tarayıcısının bozduğu Korece görsel adresleri, 404'ler ve dolan php-fpm havuzu
Okuyan taraf HTML'in başında meta charset utf-8 bildirimini bulamazsa ve yanıt başlığında da charset yoksa, belgenin karakter kodlamasını tahmin etmek ya da varsayılan değerle okumak zorunda kalır. Nginx ve Apache yapısındaki bir WordPress sunucusunda AdSense tarayıcısı bozuk Korece (ASCII dışı) görsel adreslerine yoğun istek gönderdi ve 404 işleme tüm PHP worker'larını doldurdu. Bunu günlüklerden izledikten sonra önbellekteki sayfaların yanıt başlığına charset ekledim ve olmayan statik dosyaları nginx'in doğrudan 404 ile yanıtlamasını sağladım; böylece aynı istekler worker'ları yeniden doldurmuyor.
1️⃣ meta charset utf-8: neden ilk 1.024 bayt içinde olmalı
Sunucu, belgenin karakter kodlamasını iki yerde bildirebilir: [Content-Type] yanıt başlığının arkasına eklenen charset ve HTML içine yazılan meta charset satırı.
HTML standardı, karakter kodlaması bildiriminin yalnızca ilk 1.024 bayt içinde yer almasını şart koşar ve okuyan tarafa da yalnızca bu ilk 1.024 baytı önceden okuyarak aramasını önerir. Bu yüzden head içinde olsa bile 1.024 baytın ötesine itilmiş bir meta charset bazı okuyucular tarafından bulunamayabilir.
Kodlamaya karar verirken okuyan taraf önce BOM (bayt sırası işareti) değerine, sonra yanıt başlığındaki charset değerine, ardından ilk 1.024 baytı önceden okuyarak bulduğu meta charset değerine bakar. Üçü de yoksa bayt yapısından tahmin eder ya da önceden belirlenmiş varsayılanı kullanır; HTML standardının önerdiği varsayılan değer tablosunda çoğu locale için bu değer windows-1252'dir.
Bir UTF-8 belgesini windows-1252 olarak okursanız, Korece bir harf gibi birden çok bayttan oluşan tek bir karakter, birkaç farklı karaktere dönüşür.
2️⃣ AdSense tarayıcısı Mediapartners-Google'ın bozduğu Korece (ASCII dışı) görsel adresleri
Düzeltmeden önceki önbellek sayfasını ilk 1.024 bayt ölçütüyle kontrol ettiğimde, Content-Type yanıt başlığında charset yoktu ve meta charset satırı 15.199. baytta duruyordu. Yani AdSense tarayıcısı Mediapartners-Google'ın aldığı önbellek sayfasında, iki yerde de bulunabilecek bir kodlama yoktu.
curl -sI https://example.com/sample-post/ | grep -i '^content-type'content-type: text/htmlBu yapıda adresin sonuna bir sorgu dizesi eklerseniz önbellek sayfası atlanır ve PHP'nin doğrudan oluşturduğu yanıt gelir; o yanıtta charset gerçekten vardı.
curl -sI 'https://example.com/sample-post/?nocache=1' | grep -i '^content-type'content-type: text/html; charset=UTF-8Önbellek dosyasında meta charset değerinin hangi baytta başladığını [grep -b] ile bayt düzeyinde görebilirsiniz.
grep -bo '<meta charset' /var/www/html/wp-content/cache/wp-rocket/example.com/sample-post/index-https.html | head -115199:<meta charsetAynı önbellek dosyasındaki img etiketlerinde UTF-8 çok dilli dosya adları (bu örnekte Korece) yüzde kodlaması olmadan olduğu gibi duruyordu. Bu HTML windows-1252 olarak okunursa [대시보드-화면-예시.png] gibi bir ad [대시보드-화면-예시.png] gibi karakterlere dönüşür ve bu karakterler yeniden UTF-8 ile yüzde kodlanıp istenirse sunucu var olmayan bir dosyayı arar.
Erişim günlüğünden bu tarayıcının aldığı görsel 404'lerini çıkardığınızda, bozuk adlı adresler art arda gelir.
grep 'Mediapartners-Google' /var/log/nginx/access.log | grep ' 404 ' | grep 'GET /wp-content/uploads/' | cut -d' ' -f1,4,7,9 | head -2203.0.113.3 [03/Nov/2026:20:40:51 /wp-content/uploads/2025/07/---1024x682.png 404
203.0.113.1 [03/Nov/2026:20:40:51 /wp-content/uploads/2025/07/---300x159.jpg 404Günlükte kalan adresler, orijinal dosya adının UTF-8 baytlarını windows-1252 karakterleri olarak okuyup yeniden UTF-8 ile kodlayınca çıkan değerle bayt bayt aynıydı. O günün günlüğünde aynı şekilde bozulmuş görsel adreslerine gelen istekler bu tarayıcıdan 202, tek bir betikten (python-requests) 6 taneydi; ziyaretçilerden gelen istek sayısı 0'dı.
Bozuk ad [대시보드-화면-예시.png] değerini UTF-8 dönüştürücü ile geri çevirdiğinizde, UTF-8 baytlarının windows-1252 olarak okunup bozulduğu durum olarak tanınır ve orijinal ad [대시보드-화면-예시.png] ortaya çıkar.
3️⃣ Olmayan görsellerin 404'leri php-fpm'i nasıl doldurdu
Bozuk adreslerdeki görseller sunucuda olmadığı için nginx, görsel location kuralındaki try_files'ın son öğesine göre istekleri Apache'ye aktardı ve WordPress 404 sayfasını PHP ile oluşturdu. Aynı gün gündüz kaydedilen 587 adet 404'e bakıldığında, önbelleksiz her 404 sayfası medyan 1,4 saniye sürdü.
Bozuk adlı görsel istekleri 20:40'tan itibaren 3 dakikada 200'ü biraz aşan sayıya yığıldı ve bunların 133'ü 404 ile bitti. Her isteğe bir php-fpm worker'ı atanır; her 404 4 ila 6 saniye sürerken istekler gelmeye devam edince havuz 20:40:55'te sınırına ulaştı.
Havuzun sınıra ulaştığı an, php-fpm hata kaydında php-fpm max_children uyarısı olarak kalır.
grep 'max_children' /var/log/php-fpm/error.log[03-Nov-2026 20:40:55] WARNING: [pool www] server reached pm.max_children setting (20), consider raising itHavuz dolduktan sonra gelen istekler boş bir worker bekledi. En uzun bekleyen 404, 58 saniyede bitti; tarayıcının 60 saniye bekleyip kestiği istekler 499, backend'den yanıt alamayan bir istek ise 504 olarak kaydedildi.
Aynı saatte çalışan önbellek ön yüklemesi ve zamanlanmış görevler (Action Scheduler) ile her dakika çalışan wp-cron kontrol edildi ve neden olarak elendi. Ön yükleme gün boyu aynı düşük hızda çalışıyordu, zamanlanmış görevler o saatte yalnızca ön yükleme işlerini işliyordu ve wp-cron komut satırı PHP ile çalıştığı için php-fpm worker'larını kullanmıyor.
4️⃣ nginx charset utf-8 ve olmayan statik dosyalar için doğrudan 404
Önbellek sayfalarını sunan nginx location'ına [charset utf-8;] satırını ekleyerek yanıt başlığına charset gelmesini sağladım; yükleme klasöründeki görseller ve statik dosyalar diskte yoksa artık WordPress'e aktarılmadan nginx doğrudan 404 döndürüyor.
# http bloğu: Cache-Control değerini yalnızca 404 yanıtları için belirler
map $status $up404_cc { 404 'no-store'; default ''; }
# Önbellekteki HTML'i doğrudan sunan location
location / {
try_files $cache_file @backend;
charset utf-8;
}
# Yükleme klasörü: olmayan görseller backend'e gitmeden doğrudan 404 alır
location ~* ^/wp-content/uploads/.+[.](jpg|jpeg|png)$ {
try_files $uri =404;
add_header Cache-Control $up404_cc always;
}map, http bloğuna konur; değeri boş dize olan bir başlığı nginx göndermediği için 200 yanıtlarına no-store eklenmez. Düzenli ifade location'ları yukarıdan aşağı uygulanır ve ilk eşleşen geçerli olur; bu yüzden bunu mevcut görsel location'ından önceye koydum ve css, js, yazı tipleri gibi statik dosyalar için aynı türden bir location daha ekledim.
Düzeltmeden sonra aynı adresi yeniden istediğimde, önbellek sayfasının yanıt başlığında charset vardı.
curl -sI https://example.com/sample-post/ | grep -i '^content-type'content-type: text/html; charset=utf-8curl -sI https://example.com/wp-content/uploads/2025/07/no-such-image.png | grep -i -E '^(HTTP|content-type|cache-control)'# Düzeltmeden önce: WordPress'in oluşturduğu 404
HTTP/2 404
content-type: text/html; charset=UTF-8
cache-control: no-cache, must-revalidate, max-age=0, no-store, private
# Düzeltmeden sonra: nginx'in doğrudan döndürdüğü 404
HTTP/2 404
content-type: text/html
cache-control: no-storeOlmayan bir görselin 404'ü, 319.058 baytı 1,01 saniyede oluşturan WordPress sayfasından, 555 baytı 0,015 saniyede döndüren nginx varsayılan sayfasına geçti. [Cache-Control: no-store] değerini yalnızca 404'lere ekleyerek CDN'nin olmayan görsellerin 404'lerini saklamamasını sağladım. PHP'nin zaten charset ile gönderdiği yanıtlara nginx ikinci bir charset eklemediğinden, yanıt başlığında yalnızca bir kez göründü.
| Kontrol edilen istek | Düzeltmeden önce | Düzeltmeden sonra |
|---|---|---|
| Olmayan görsel | 319.058 B, 1,01 saniye (WordPress 404) | 555 B, 0,015 saniye (nginx 404) |
| Bozuk adlı görsel | 1,05 saniye (WordPress 404) | 0,014 saniye (nginx 404) |
| Önbellekteki HTML'in yanıt başlığı | text/html | text/html; charset=utf-8 |
| PHP'nin doğrudan oluşturduğu yanıt başlığı | text/html; charset=UTF-8 | Aynı (charset bir kez) |
| Var olan görsel | 200 | 200 olarak kalır |
İstek geldikten sonra PHP ile küçük resim veya WebP oluşturan görsel eklentileri kullanan sitelerde bu 404 işleme uygun değildir. nginx önce 404 döndürdüğü için görsel hiç oluşturulmaz. Yapınız WebP'yi yükleme sırasında dosya olarak önceden hazırlıyorsa, olduğu gibi uygulayabilirsiniz.
5️⃣ meta charset neden geriye itildi: WP Rocket Delay JS
Yanıt başlığına charset ekledikten sonra da önbellekteki HTML'in meta charset satırı 15.199. baytta durmaya devam ediyor. PHP'nin doğrudan oluşturduğu sayfada aynı satır 135. baytta bulunuyordu.
WP Rocket'in Delay JS özelliği açıkken, önbellek dosyası oluşturulurken head'in hemen arkasına lazyload gibi gecikmeli çalışan betikler eklenir, ardından meta charset değerini yeniden head'in en başına taşıyan bir adım çalışır. Bu adım tüm belgeye düzenli ifade uyguladığı için, yaklaşık 1,2 MB'lık büyük bir yazıda PHP'nin backtrack sınırını aşar ve başarısız olur.
Düzenli ifade başarısız olunca WP Rocket, betikleri eklenmiş ama meta charset değeri taşınamamış HTML'i olduğu gibi önbelleğe alır; sonuçta meta charset ilk 1.024 baytın dışına itildi. Aynı PHP ayarlarıyla ayrıca yaptığım yeniden üretimde, backtrack sınırı varsayılan 1.000.000 ve 5.000.000 iken taşıma başarısız oldu, 10.000.000'dan itibaren başarılı oldu.
Bu kez sorunu yanıt başlığına charset ekleyerek çözdüm, meta charset değerini öne taşıyan bir düzeltme yapmadım. backtrack sınırını yükseltmek WP Rocket'in asıl davranışını geri getirir ama aynı PHP'de çalışan tüm düzenli ifadelerin sınırını 10 katına çıkarır, bu yüzden önermiyorum.
🔢 SSS & önerilen içerikler
HTTP 404
HTTP 404 hatası (istemci hatası): nedenleri ve çözümü
HTTP durum kodlarından biri olan HTTP 404 hata sayfası, bir sayfa bulunamadığında görünür.
AdSense
WordPress ads.txt ayarı: AdSense uyarısını çözme
ads.txt, sitenin kök dizinine konan ve bu alan adının reklam alanlarını satmaya yetkili satıcıların kim olduğunu herkese açık şekilde listeleyen bir metin dosyasıdır.
WP Rocket önbelleği
WP Rocket ve Korece URL: nginx'in önbellek dosyasını bulamama nedeni ve njs ile çözüm
İngilizce slug'lı yazılarda nginx önbellek dosyasını doğrudan sunar, ancak Korece slug'lı yazılarda aynı önbellek dosyası olsa bile dosya ancak PHP bir kez çalıştıktan sonra sunulur.
ℹ️ Satış ortaklığı bildirimi
Bu sitenin içeriklerinde satış ortaklığı bağlantıları bulunur. Bir ziyaretçi bu bağlantılar üzerinden ürün veya hizmet satın alırsa bu site satıcıdan komisyon alır. Bu süreçte alıcının ödediği tutar (kampanya indirimlerinde tutar düşer↓) artmaz. Yayımlanan fiyat, indirim ve stok bilgileri yazının yazıldığı zamana aittir ve gerçek durumdan farklı olabilir; bu yüzden satın almadan önce son bilgiyi satıcıdan kontrol etmenizi rica ederiz. Ürünler kendi ölçütlerimize göre seçilir ve değerlendirilir; komisyon alınıp alınmaması tanıtım sırasını veya değerlendirme içeriğini etkilemez.