meta charset utf-8: AdSense tarayıcısının bozduğu Korece görsel adresleri, 404'ler ve dolan php-fpm havuzu

Test ortamıRHEL ailesiAlmaLinux 9.8Nginx ve Apache yapısıÖnbellek eklentisinin statik HTML'ini nginx doğrudan sunar2026-11-04 tarihinde kontrol edildi
Bazı adresler, yollar ve sayılar örnek değerdir

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.

🧭 meta charset utf-8 인코딩 결정 순서
설명 다이어그램
🔍 순서
📄 응답
🔖BOM(바이트 순서 표시)있으면 그 인코딩으로 정함없음없음없음
📨Content-Type 머리의 charset있으면 그 값으로 정함charset 없음charset 없음
🏷️meta charset 줄앞 1,024바이트 안에 있으면 그 값으로 정함1,024바이트 뒤로 밀려 찾지 못함
➜
🔎 읽는 쪽
🔤문서 인코딩찾은 값으로 읽음찾은 값으로 읽음찾은 값으로 읽음짐작하거나 기본값 windows-1252로 읽음
인코딩을 정할 때는 BOM(바이트 순서 표시)을 먼저 보며, BOM이 있으면 그 인코딩으로 읽습니다.
BOM이 없으면 다음은 응답 머리 Content-Type 뒤에 붙는 charset입니다.
그다음이 앞 1,024바이트 미리 읽기로 찾은 meta charset입니다. head 영역 안에 있더라도 1,024바이트 뒤로 밀린 meta charset은 찾지 못하는 읽는 쪽이 생깁니다.
셋 다 없으면 바이트 모양으로 짐작하거나 정해 둔 기본값으로 읽게 됩니다. 머리에 charset이 없고 meta charset이 1,024바이트 뒤에 있으면, 대부분의 locale에서 기본값인 windows-1252로 읽힐 수 있습니다.

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/html

Bu 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 -1
15199:<meta charset

Aynı ö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 -2
203.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 404

Gü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ü.

🌐 Mediapartners-GoogleBozuk adlı görsel adresi isteği
nginx görsel location'ıtry_files diskteki dosyayı kontrol eder
Dosya varnginx doğrudan 200 döner
Dosya yoktry_files'ın son öğesi olan backend'e aktarılır
Apache → WordPress404 sayfası PHP ile oluşturulur (medyan 1,4 saniye)
php-fpm worker'larıİstek başına bir worker
Havuz sınırı (pm.max_children)Dolunca sonraki istekler bekler (499, 504)

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 it

Havuz 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-8
curl -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-store

Olmayan 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 istekDüzeltmeden önceDüzeltmeden sonra
Olmayan görsel319.058 B, 1,01 saniye (WordPress 404)555 B, 0,015 saniye (nginx 404)
Bozuk adlı görsel1,05 saniye (WordPress 404)0,014 saniye (nginx 404)
Önbellekteki HTML'in yanıt başlığıtext/htmltext/html; charset=utf-8
PHP'nin doğrudan oluşturduğu yanıt başlığıtext/html; charset=UTF-8Aynı (charset bir kez)
Var olan görsel200200 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

Google'ın tarayıcı belgeleri, Mediapartners-Google için yapılan tarama ayarlarının AdSense ve Ad Manager gibi reklam ürünlerini etkilediğini belirtir. Bu tarayıcı robots.txt'nin genel kuralı [User-agent: *] değerine uymaz; engellemek için adını ayrıca yazmanız gerekir. Sorun bozuk adreslerdeki 404'lerse, olmayan dosyaları web sunucusunun doğrudan 404 ile yanıtlaması reklam taramasına dokunmaz.

HTTP önbellekleme standardı, bir yanıt saklanırken alınan yanıt başlıklarının da birlikte saklanmasını şart koşar; bu yüzden düzeltmeden önce CDN'nin sakladığı kopyalar charset olmayan [Content-Type: text/html] haliyle gitmeye devam eder. Süreleri dolup kaynak sunucudan yeniden alındığında ya da yalnızca o yazının adresini CDN önbelleğinden temizlediğinizde, charset içeren yeni başlığa geçerler.

URL standardı, adres yolundaki ASCII dışı karakterlerin istekten önce UTF-8 yüzde kodlamasına çevrilmesini şart koşar; bu yüzden belgeyi UTF-8 olarak doğru okuyan taraf orijinal dosya adıyla istekte bulunur. Yalnızca Latin harfleri ve rakamlardan oluşan dosya adları, belge yanlışlıkla windows-1252 olarak okunsa bile aynı kalır; bu yüzden hem yanıt başlığındaki charset hem de meta charset eksik olan sayfalarda bile adresleri bozulmaz.

ℹ️ 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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

İnsan olduğunuzu kanıtlayın: 2   +   3   =