meta charset utf-8: 404 dari alamat gambar berbahasa Korea yang dirusak crawler AdSense dan php-fpm yang penuh

Lingkungan ujiKeluarga RHELAlmaLinux 9.8Susunan Nginx dan Apachenginx langsung menyajikan HTML statis dari plugin cacheDiperiksa 2026-11-04
Sebagian alamat, path, dan angka adalah nilai contoh

Jika pembaca tidak menemukan deklarasi meta charset utf-8 di bagian awal HTML dan header respons juga tidak punya charset, pembaca harus menebak pengodean karakter dokumen atau membacanya dengan nilai bawaan. Di server WordPress dengan susunan Nginx dan Apache, crawler AdSense mengirim banyak permintaan sekaligus ke alamat gambar berbahasa Korea (non-ASCII) yang rusak, dan penanganan 404 memenuhi semua worker PHP. Setelah melacaknya lewat log, saya menambahkan charset ke header respons halaman cache dan membuat nginx langsung menjawab file statis yang tidak ada dengan 404, sehingga permintaan yang sama tidak lagi memenuhi worker.

1️⃣ meta charset utf-8: alasan harus berada di 1.024 byte pertama

Server bisa memberi tahu pengodean karakter dokumen di dua tempat: charset yang ditempel di belakang header respons [Content-Type], dan baris meta charset yang ditulis di dalam HTML.

Standar HTML menetapkan bahwa deklarasi pengodean karakter hanya boleh muncul di 1.024 byte pertama, dan menyarankan pembaca untuk membaca lebih dulu hanya 1.024 byte pertama itu untuk mencarinya. Karena itu, walaupun berada di dalam head, meta charset yang terdorong melewati 1.024 byte bisa terlewat oleh sebagian pembaca.

Untuk menentukan pengodean, pembaca memeriksa BOM (byte order mark) lebih dulu, lalu charset di header respons, kemudian meta charset yang ditemukan dengan membaca lebih dulu 1.024 byte pertama. Jika ketiganya tidak ada, pembaca menebak dari bentuk byte atau memakai nilai bawaan yang sudah ditetapkan, dan dalam tabel nilai bawaan yang diusulkan standar HTML, sebagian besar locale mendapat windows-1252.

Jika dokumen UTF-8 dibaca sebagai windows-1252, satu karakter yang terdiri dari beberapa byte, seperti huruf Korea, berubah menjadi beberapa karakter lain.

🧭 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️⃣ Alamat gambar berbahasa Korea (non-ASCII) yang dirusak crawler AdSense Mediapartners-Google

Saat memeriksa halaman cache sebelum perbaikan dengan patokan 1.024 byte pertama, header respons Content-Type tidak punya charset dan baris meta charset berada di byte ke-15.199. Jadi halaman cache yang diterima crawler AdSense Mediapartners-Google tidak punya pengodean yang bisa ditemukan di kedua tempat itu.

curl -sI https://example.com/sample-post/ | grep -i '^content-type'
content-type: text/html

Di susunan ini, jika Anda menambahkan query string di belakang alamat, halaman cache dilewati dan yang datang adalah respons yang langsung dibuat PHP, dan respons itu memang membawa charset.

curl -sI 'https://example.com/sample-post/?nocache=1' | grep -i '^content-type'
content-type: text/html; charset=UTF-8

Posisi awal meta charset di dalam file cache bisa Anda periksa sampai ke satuan byte dengan [grep -b].

grep -bo '<meta charset' /var/www/html/wp-content/cache/wp-rocket/example.com/sample-post/index-https.html | head -1
15199:<meta charset

Tag img di file cache yang sama berisi nama file multibahasa UTF-8 (di sini bahasa Korea) apa adanya, tanpa percent-encoding. Jika HTML ini dibaca sebagai windows-1252, nama seperti [대시보드-화면-예시.png] berubah menjadi karakter seperti [대시보드-화면-예시.png], dan jika karakter itu di-percent-encode lagi sebagai UTF-8 lalu diminta, server mencari file yang tidak ada.

Jika Anda mengambil 404 gambar yang diterima crawler ini dari log akses, alamat dengan nama rusak muncul berturut-turut.

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

Alamat yang tersisa di log sama persis sampai ke byte dengan nilai yang didapat dari membaca byte UTF-8 nama file asli sebagai karakter windows-1252 lalu mengodekannya lagi ke UTF-8. Di log hari itu, permintaan ke alamat gambar yang rusak dengan cara yang sama berjumlah 202 dari crawler ini dan 6 dari satu skrip (python-requests), sedangkan dari pengunjung 0.

Jika nama rusak [대시보드-화면-예시.png] dikembalikan dengan alat Konverter UTF-8, nama itu dikenali sebagai byte UTF-8 yang dibaca sebagai windows-1252, dan nama aslinya [대시보드-화면-예시.png] muncul.

3️⃣ Proses 404 gambar yang tidak ada memenuhi php-fpm

Karena gambar di alamat yang rusak tidak ada di server, nginx meneruskan permintaan ke Apache sesuai item terakhir try_files di aturan location gambarnya, dan WordPress membuat halaman 404 dengan PHP. Dari 587 kejadian 404 yang tercatat pada siang hari di tanggal yang sama, setiap halaman 404 tanpa cache memakan waktu median 1,4 detik.

🌐 Mediapartners-GooglePermintaan alamat gambar dengan nama rusak
location gambar di nginxtry_files memeriksa file di disk
File adanginx langsung menjawab 200
File tidak adaDiteruskan ke backend, item terakhir try_files
Apache → WordPressHalaman 404 dirender dengan PHP (median 1,4 detik)
Worker php-fpmSatu worker per permintaan
Batas pool (pm.max_children)Jika penuh, permintaan berikutnya menunggu (499, 504)

Permintaan gambar dengan nama rusak menumpuk hingga sedikit lebih dari 200 dalam 3 menit sejak 20:40, dan 133 di antaranya berakhir dengan 404. Setiap permintaan mendapat satu worker php-fpm, dan karena permintaan terus datang sementara setiap 404 memakan 4 sampai 6 detik, pool mencapai batasnya pada 20:40:55.

Saat pool mencapai batas, log kesalahan php-fpm mencatatnya sebagai peringatan php-fpm max_children.

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

Permintaan yang masuk setelah pool penuh menunggu worker yang kosong. 404 yang menunggu paling lama selesai setelah 58 detik, permintaan yang diputus crawler setelah menunggu 60 detik tercatat sebagai 499, dan satu permintaan yang tidak mendapat respons dari backend tercatat sebagai 504.

Preload cache, tugas terjadwal (Action Scheduler) yang berjalan pada waktu yang sama, dan wp-cron yang dijalankan setiap menit sudah diperiksa dan dikeluarkan dari daftar penyebab. Preload berjalan seharian dengan kecepatan rendah yang sama, tugas terjadwal pada jam itu hanya memproses pekerjaan preload, dan wp-cron dijalankan dengan PHP baris perintah sehingga tidak memakai worker php-fpm.

4️⃣ nginx charset utf-8 dan 404 langsung untuk file statis yang tidak ada

Saya menambahkan satu baris [charset utf-8;] ke location nginx yang menyajikan halaman cache agar header respons membawa charset, dan untuk gambar serta file statis di folder upload, jika tidak ada di disk, nginx sekarang langsung mengembalikan 404 tanpa meneruskannya ke WordPress.

# Blok http: tetapkan nilai Cache-Control hanya untuk respons 404
map $status $up404_cc { 404 'no-store'; default ''; }

# location yang langsung menyajikan HTML cache
location / {
    try_files $cache_file @backend;
    charset utf-8;
}

# Folder upload: gambar yang tidak ada langsung mendapat 404 tanpa diteruskan ke backend
location ~* ^/wp-content/uploads/.+[.](jpg|jpeg|png)$ {
    try_files $uri =404;
    add_header Cache-Control $up404_cc always;
}

map diletakkan di blok http, dan karena nginx tidak mengirim header yang nilainya string kosong, respons 200 tidak mendapat no-store. location regex diterapkan dari atas dan yang cocok lebih dulu yang berlaku, jadi saya menaruhnya sebelum location gambar yang sudah ada, dan menambahkan satu location lagi dengan cara yang sama untuk file statis seperti css, js, dan font.

Saat alamat yang sama diminta lagi setelah perbaikan, header respons halaman cache sudah membawa charset.

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)'
# Sebelum perbaikan: 404 yang dibuat WordPress
HTTP/2 404
content-type: text/html; charset=UTF-8
cache-control: no-cache, must-revalidate, max-age=0, no-store, private
# Setelah perbaikan: 404 yang langsung dikembalikan nginx
HTTP/2 404
content-type: text/html
cache-control: no-store

404 untuk gambar yang tidak ada berubah dari halaman WordPress yang membuat 319.058 byte dalam 1,01 detik menjadi halaman bawaan nginx yang dikembalikan dalam 0,015 detik dengan 555 byte. Dengan menambahkan [Cache-Control: no-store] hanya pada 404, CDN tidak menyimpan 404 gambar yang tidak ada. Pada respons yang sudah dikirim PHP dengan charset, nginx tidak menambahkan charset lagi, jadi charset hanya muncul sekali di header respons.

Permintaan yang diperiksaSebelum diperbaikiSetelah perbaikan
Gambar yang tidak ada319.058 B, 1,01 detik (404 WordPress)555 B, 0,015 detik (404 nginx)
Gambar dengan nama rusak1,05 detik (404 WordPress)0,014 detik (404 nginx)
Header respons HTML cachetext/htmltext/html; charset=utf-8
Header respons yang langsung dibuat PHPtext/html; charset=UTF-8Sama (charset sekali)
Gambar yang ada200Tetap 200

Penanganan 404 ini tidak cocok untuk situs yang memakai plugin gambar yang membuat thumbnail atau WebP dengan PHP setelah permintaan masuk. Karena nginx lebih dulu mengembalikan 404, gambarnya tidak pernah dibuat. Jika susunan Anda membuat file WebP lebih dulu saat upload, Anda bisa menerapkannya apa adanya.

5️⃣ Alasan meta charset terdorong ke belakang: WP Rocket Delay JS

Bahkan setelah charset ditambahkan ke header respons, meta charset di HTML cache masih berada di byte ke-15.199. Di halaman yang langsung dibuat PHP, baris yang sama ada di byte ke-135.

Saat Delay JS WP Rocket dinyalakan, pembuatan file cache menyisipkan skrip eksekusi tertunda seperti lazyload tepat setelah head, lalu sebuah tahap memindahkan meta charset kembali ke paling depan head. Tahap pemindahan ini memakai regex pada seluruh dokumen, sehingga pada artikel besar sekitar 1,2 MB tahap ini melewati batas backtrack PHP dan gagal.

Jika regex gagal, WP Rocket menyimpan HTML apa adanya ke cache, dengan skrip yang sudah disisipkan tetapi meta charset yang tidak terpindahkan, dan akibatnya meta charset terdorong ke luar 1.024 byte pertama. Dalam reproduksi terpisah dengan pengaturan PHP yang sama, pemindahan gagal saat batas backtrack berada di nilai bawaan 1.000.000 dan di 5.000.000, dan berhasil mulai 10.000.000.

Kali ini saya menyelesaikannya dengan menambahkan charset ke header respons, dan tidak memperbaiki posisi meta charset. Menaikkan batas backtrack memulihkan perilaku asli WP Rocket, tetapi juga menaikkan batas semua regex yang berjalan di PHP yang sama hingga 10 kali lipat, jadi saya tidak menyarankannya.

🔢 FAQ & Konten yang Direkomendasikan

Dokumentasi crawler Google menjelaskan bahwa pengaturan crawling yang ditujukan ke Mediapartners-Google memengaruhi produk iklan seperti AdSense dan Ad Manager. Crawler ini tidak mengikuti aturan umum robots.txt [User-agent: *], jadi untuk memblokirnya Anda harus menuliskan namanya secara terpisah. Jika masalahnya adalah 404 di alamat yang rusak, membiarkan server web langsung menjawab file yang tidak ada dengan 404 tidak mengganggu crawling iklan.

Standar cache HTTP menetapkan bahwa saat menyimpan respons, header respons yang diterima ikut disimpan, jadi salinan yang disimpan CDN sebelum perbaikan tetap dikirim sebagai [Content-Type: text/html] tanpa charset. Setelah masa simpannya habis dan diambil lagi dari server asal, atau setelah Anda menghapus hanya alamat artikel itu dari cache CDN, salinan itu berganti ke header baru yang membawa charset.

Standar URL menetapkan bahwa karakter di luar ASCII pada path alamat diubah menjadi percent-encoding UTF-8 sebelum diminta, jadi pembaca yang membaca dokumen dengan benar sebagai UTF-8 meminta nama file aslinya. Nama file yang hanya memakai huruf Latin dan angka tetap sama walaupun dokumen salah dibaca sebagai windows-1252, sehingga alamatnya tidak rusak bahkan di halaman yang kehilangan charset di header respons maupun meta charset.

ℹ️ Pemberitahuan Afiliasi
Konten di situs ini berisi tautan afiliasi. Jika pengunjung membeli produk atau layanan melalui tautan tersebut, situs ini menerima komisi dari penjual. Harga yang dibayar pembeli(turun saat ada diskon promo ↓), tidak naik karena hal ini. Harga, diskon, dan informasi stok yang ditampilkan berlaku pada saat penulisan dan bisa berbeda dengan kondisi sebenarnya, jadi pastikan kembali di situs penjual sebelum membeli. Pemilihan dan penilaian produk dibuat berdasarkan standar kami sendiri, dan ada tidaknya komisi tidak memengaruhi urutan maupun isi penilaian.

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *

Buktikan bahwa Anda manusia: 7   +   10   =