D
P
0
← Semua artikel Read in English

Cache & CDN: Deploy Sukses, Perubahan Tak Muncul

Rule Bypass Ada, Header Tetap cf-cache-status: HIT. Cache Rules Cloudflare Itu Last-Match-Wins

· · 9 menit baca
Rule Bypass Ada, Header Tetap cf-cache-status: HIT. Cache Rules Cloudflare Itu Last-Match-Wins

Header-nya cf-cache-status: HIT, dan itu terjadi persis di halaman yang paling tidak boleh di-cache: halaman masuk, halaman daftar, halaman akun. Rule Bypass untuk path auth sudah ada di zona itu. Pertanyaan pertama yang saya kejar sudah bisa ditebak, apakah isi expression-nya salah.

Kasus ini akhirnya ditutup dengan tiga jawaban akurat untuk tiga pertanyaan yang keliru.

Satu hal perlu saya taruh di depan karena dia membentuk seluruh cara kerja saya di sini. Zona Cloudflare-nya bukan milik saya. Perubahan Cache Rules tercatat sebagai tugas orang di sisi klien yang memegang Cloudflare, jadi yang bisa saya kerjakan cuma dua hal: mengukur dari header sebagai pengunjung anonim, dan menyerahkan daftar path yang benar untuk dipasang di sana.

Kerusakannya tidak kelihatan seperti kerusakan cache

Ini situs WordPress dengan area keanggotaan di belakang Cloudflare. Halaman auth-nya memanggang penanda waktu formulir langsung ke dalam HTML, dan nonce di halaman yang ter-cache lebih dari 12 sampai 24 jam ikut rusak tanpa bypass.

Waktu HTML halaman masuk benar-benar ikut ter-cache, ini yang terukur: cf-cache-status: HIT, dan penanda waktu di dalam HTML-nya berumur 2512 detik. Jendela yang dianggap masih wajar cuma 1800 detik, jadi pemeriksa bot menyimpulkan pengirimnya bot. Di halaman masuk hasilnya kiriman ditolak dengan error retry. Di alur klaim profil hasilnya lebih buruk lagi, dan ini yang paling jahat dari semuanya: kirimannya dianggap bot lalu dibalas pura-pura berhasil, tanpa suara, tanpa tanda apa pun ke orangnya.

Perbaikan sisi kodenya sederhana. Jendela basi 1800 detik itu dinaikkan jadi 12 jam dan dibuat bisa difilter, disamakan dengan umur nonce.

Yang bikin bug seperti ini susah dilihat adalah cara cache memperlakukan orang yang sedang login. Admin yang login melewati kedua lapis cache, jadi editor melihat versi baru sementara publik melihat versi lama, dan gejalanya terbaca seperti hasil kerja yang tidak sesuai mockup. Efek yang sama terukur di dasbor pemilik profil: header-nya DYNAMIC karena Cloudflare mem-bypass berdasarkan cookie login, bukan berdasarkan path.

Pertanyaan salah yang pertama: apakah isi rule-nya benar

Asumsi yang saya bawa masuk adalah rule bypass-nya sudah memuat path yang kira-kira sama dengan halaman masuk, akun, dan klaim. Asumsi itu salah, dan salahnya tercatat eksplisit di catatan projeknya. Path masuk, daftar, klaim, akun, lupa kata sandi, dan dua path pengiriman formulir tidak pernah ada di dalam rule bypass sama sekali. Yang ada di sana cuma set standar WordPress.

Jadi langkah pertamanya bukan debugging, cuma menulis daftar. Permintaan bypass ke sisi infra saya catat sebagai permintaan yang masih menggantung, lengkap dengan daftar path yang eksplisit. Draf pertama yang saya serahkan berisi delapan path. Setelah ditinjau ulang, daftar itu masih kurang tiga: halaman reset kata sandi, halaman sunting profil, dan halaman permintaan profil. Delapan tambah tiga, sebelas.

Pertanyaan yang benar: rule mana yang menang

Titik baliknya satu bukti kecil yang tidak mau cocok dengan cerita mana pun. Path reset kata sandi sudah ada di dalam expression rule Bypass, dan header-nya tetap HIT. Kalau isi rule-nya sudah benar dan hasilnya tetap salah, pertanyaan "apakah rule ini benar" memang tidak akan membawa ke mana-mana. Pertanyaan yang seharusnya diajukan dari awal: dari semua rule yang cocok dengan URL ini, siapa yang menang?

Cache Rules Cloudflare dievaluasi last-match-wins. Yang menang bukan kecocokan pertama, tapi rule terakhir yang cocok. Di zona ini rule Bypass duduk di atas rule cache halaman publik, dan rule cache itulah yang menang, lengkap dengan TTL satu tahunnya.

Sebelum
1. Bypass cache        (daftar path auth)
2. Cache HTML publik   (edge TTL 1 tahun)   <- ikut cocok, dan dia yang TERAKHIR
 
Sesudah
1. Cache HTML publik   (edge TTL 1 tahun)
2. Bypass cache        (daftar path auth)   <- sekarang dia yang TERAKHIR

Jadi rule Bypass-nya benar dan diabaikan pada saat yang sama. Dua hal itu tidak saling bertentangan, dan selama saya kira bertentangan, saya membaca field yang isinya memang tidak perlu diperbaiki.

Angka TTL-nya bukan angka biasa. HTML situs ini disajikan lewat Cloudflare dengan header seperti ini:

cf-cache-status: HIT
cache-control: max-age=31536000

31536000 detik itu satu tahun, dan yang kena HTML-nya, bukan gambar atau CSS-nya.

Selama TTL itu berlaku, purge di origin tidak menyentuh apa pun

Konsekuensi paling mahal dari TTL satu tahun bukan di halaman auth, tapi di tiap deploy. Membersihkan cache plugin di origin saja tidak cukup. URL publik tetap menyajikan salinan edge yang basi dengan cf-cache-status: HIT sampai Purge Everything dijalankan di Cloudflare. Di catatan aktivasi projeknya, langkah itu akhirnya ditulis sebagai langkah wajib, dengan kalimat terus terang bahwa clear plugin sendirian percuma.

Salah paham ini tidak murah. Dalam satu sesi saja, kesalahpahamannya menghabiskan sekitar enam siklus cache.

Dua kejadian yang mengingatkan bahwa ini bisa menipu siapa saja. Sekali waktu saya sendiri yang mengeluh halaman akun kosong, bisa diakses tanpa login, dan nav-nya masih versi lama, padahal servernya sudah benar dan yang basi cache peramban serta edge. Obatnya purge Cloudflare, clear plugin, hard refresh. Di kesempatan lain justru dari sisi klien masuk laporan bahwa halamannya terlihat belum berubah, dan setelah dibalas ternyata cache-nya ada di sisi dia sendiri.

Cara membaca header supaya tidak dibohongi pemeriksaan sendiri

Sebelum melangkah lebih jauh saya butuh cara memeriksa yang tidak bohong, dan kebiasaan lama justru sumber masalah baru. Parameter ?nowprocket=1 ikut ter-cache di edge di bawah cache key-nya sendiri. Memakai ulang parameter yang sama bukan mengambil kondisi terbaru, melainkan menyajikan salinan basi dari pemeriksaan saya sendiri sebelumnya. Kalau memang harus dipakai, ganti nilainya tiap kali, misalnya tambah &v=2, supaya kuncinya ikut berganti.

Yang saya pakai akhirnya jalur paling polos, dijalankan dari konsol peramban dalam keadaan tidak login:

const r = await fetch('/signin/?cb=' + Math.random(), {
  cache: 'reload',
  credentials: 'omit',
});
console.log(r.headers.get('cf-cache-status'), r.headers.get('age'));

credentials: 'omit' memastikan saya terbaca sebagai pengunjung anonim. cache: 'reload' membuang cache peramban dari perhitungan. Lalu cf-cache-status dan age yang dibaca, bukan tampilan halamannya. Query cache-buster acak ?cb= memaksa MISS sampai ke origin, dan itu gunanya kalau yang mau dibuktikan adalah berkasnya memang sudah benar-benar terdeploy.

Ada satu kemudahan yang gampang dilewatkan di sini. URL yang benar-benar baru belum pernah masuk cache, permintaan pertamanya pasti MISS ke origin, jadi halaman baru bisa diuji sebelum purge. URL yang sudah ada wajib dipurge dulu.

Pertanyaan salah yang kedua: apa istimewanya path ini

Setelah urutan rule dibalik dan path yang kurang ditambahkan, saya sapu ulang semuanya. Hampir semua berubah. Satu tidak: halaman masuk tetap HIT, sendirian.

Pertanyaan berikutnya kelihatan masuk akal, apa yang istimewa dari path ini sampai dia beda dari sepuluh saudaranya. Jawabannya tidak ada di lapisan yang rumit. Yang menyelesaikannya justru datang dari sisi klien: orang yang memegang Cloudflare mengirimkan tangkapan layar rule-nya sendiri, dan di tangkapan layar itu terbaca field path-nya tertulis /signins/, dengan huruf s kelebihan.

Rule itu tidak pernah gagal. Dia memang tidak pernah cocok dengan apa pun, karena tidak ada halaman bernama begitu. Pertanyaan yang benar ternyata sangat membosankan: apa yang benar-benar tertulis di field itu, karakter per karakter.

Pertanyaan salah yang ketiga: apakah BYPASS itu kabar baik

Ada satu jebakan lagi yang sempat bikin saya mengira sudah selesai padahal belum. Beberapa halaman membalas cf-cache-status: BYPASS, dan itu gampang dibaca sebagai bukti rule Bypass-nya bekerja.

Bukan itu sebabnya. Halaman-halaman itu membalas 302 untuk pengunjung anonim, dan origin mengirim no-store pada respons redirect-nya. Cloudflare melewati cache karena origin yang menyuruh, bukan karena rule-nya. Kata BYPASS di situ menceritakan perilaku origin.

Di bawah Cache Rules, path yang berhasil di-bypass justru menampilkan DYNAMIC, bukan BYPASS. Salah membaca kata yang mana yang menandakan sukses membuat halaman yang benar-benar bermasalah tetap dibiarkan HIT.

Verifikasi yang lebih ketat daripada yang disuruh insting

Sapuan terakhirnya saya buat lebih ketat, karena ketiga kesalahan sebelumnya lolos dari pemeriksaan yang cuma sekali jalan.

Sebelas path auth harus DYNAMIC dan stabil tiga kali berturut-turut. Sekali saja tidak cukup, karena urutan MISS lalu HIT bisa terbaca meyakinkan kalau kebetulan yang tertangkap cuma yang pertama.

Lalu sisi sebaliknya, yang jauh lebih mudah dilupakan. Beranda dan halaman indeks publik harus tetap MISS di permintaan pertama lalu HIT di permintaan berikutnya. Itu buktinya caching publik tidak ikut rusak dan rule bypass-nya tidak cocok terlalu luas. Memperbaiki halaman auth sambil mematikan caching seluruh situs bukan perbaikan, cuma tukar bug.

Dengan sapuan itu, statusnya ditutup 3 Juli 2026 sekitar pukul 18.00, sebelas dari sebelas, dan verifikasinya lewat header bukan lewat tampilan halaman. Tiga hari kemudian dicek lagi, masih sebelas dari sebelas. Kelas kerusakan nonce basi akibat cache dinyatakan tertutup.

Bagian yang harus saya koreksi sendiri

Cerita ini akan terlalu rapi kalau berhenti di situ, jadi dua koreksi.

Pertama, tidak semua yang saya curigai cache ternyata cache. Sempat ada dugaan di catatan audit saya bahwa cache HTML satu tahun mem-cache halaman akun lalu membuang Set-Cookie. Bukti QA-nya sebagian justru bertentangan: POST login ke admin-post.php membalas cf-cache-status: DYNAMIC dengan no-store, artinya tidak di-cache, dan /wp-admin/ yang tidak pernah di-cache Cloudflare pun ikut memantul ke halaman login. Penyebab sebenarnya bukan cache sama sekali. Lockdown yang jalan di admin_init tidak punya bypass untuk admin-post.php, jadi tiap POST dari pengunjung yang belum login dialihkan sebelum handler-nya sempat jalan. Itu sebabnya basis datanya tercatat nol member.

Kedua, keadaan zonanya berubah setelahnya. Rekomendasi saya ke sisi klien memang TTL HTML satu tahun terlalu agresif. Pengukuran 9 Agustus 2026 menunjukkan tiap halaman konten sudah DYNAMIC dengan max-age=0, jadi edge tidak lagi memegang HTML sama sekali. Yang memegang halaman basi pindah ke cache plugin di origin, dengan umur sepuluh hari, sehingga halaman yang tidak dipurge bisa salah selama satu setengah minggu.

Satu peringatan yang menempel di keadaan baru itu. Zona ini tidak punya page rule cache_everything, dan tanpa itu fungsi auto purge by URL milik plugin berhenti lebih awal. Artinya kalau suatu hari ada yang menambahkan Cache Rule untuk HTML di zona ini, kegagalan purge otomatisnya jadi senyap. Sekalian saya betulkan catatan saya sendiri yang keliru: add-on Cloudflare di plugin itu sebenarnya sudah terkonfigurasi lengkap, klaim lama bahwa tokennya tidak ada ternyata salah.

Yang saya bawa pulang

Tiga jawaban akurat untuk tiga pertanyaan yang keliru. Apakah expression-nya benar? Benar, dan tidak relevan, karena rule lain yang menang. Apa istimewanya path ini? Tidak ada, karena yang tertulis di field itu memang bukan path itu. Apakah BYPASS berarti rule-nya jalan? Tidak, karena kata itu datang dari origin.

Kalau sebuah aturan konfigurasi kelihatan diabaikan padahal isinya benar, pertanyaan yang berguna bukan "apakah aturan saya benar", melainkan "bagaimana sistem ini mengevaluasi aturan, dan siapa yang menang kalau lebih dari satu cocok". Untuk Cache Rules Cloudflare, jawabannya yang terakhir.

Dan kalau aturannya sudah diurutkan benar tapi tetap tidak menyentuh apa pun, berhenti berteori sebentar. Baca lagi apa yang benar-benar tertulis di field itu, huruf per huruf, kalau perlu minta tangkapan layarnya dari orang yang memegang dasbornya.