meta charset utf-8: 404 dari alamat gambar berbahasa Korea yang dirusak crawler AdSense dan php-fpm yang penuh
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.
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/htmlDi 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-8Posisi 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 -115199:<meta charsetTag 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 -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 404Alamat 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.
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 itPermintaan 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-8curl -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-store404 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 diperiksa | Sebelum diperbaiki | Setelah perbaikan |
|---|---|---|
| Gambar yang tidak ada | 319.058 B, 1,01 detik (404 WordPress) | 555 B, 0,015 detik (404 nginx) |
| Gambar dengan nama rusak | 1,05 detik (404 WordPress) | 0,014 detik (404 nginx) |
| Header respons HTML cache | text/html | text/html; charset=utf-8 |
| Header respons yang langsung dibuat PHP | text/html; charset=UTF-8 | Sama (charset sekali) |
| Gambar yang ada | 200 | Tetap 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
HTTP 404
Error HTTP 404 (error klien): penyebab dan solusi
Halaman error HTTP 404, salah satu kode status HTTP, muncul saat halaman tidak dapat ditemukan.
AdSense
Pengaturan ads.txt WordPress: cara mengatasi peringatan AdSense
ads.txt adalah file teks yang diletakkan di root situs dan berisi daftar publik penjual resmi yang boleh menjual ruang iklan domain tersebut.
Cache WP Rocket
WP Rocket dan URL bahasa Korea: penyebab nginx tidak menemukan file cache dan solusi dengan njs
Untuk artikel dengan slug bahasa Inggris, nginx langsung menyajikan file cache, tetapi untuk artikel dengan slug bahasa Korea, file cache yang sama baru disajikan setelah PHP berjalan sekali.
ℹ️ 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.