D
P
0

WordPress & PHP

Staging WordPress di Subpath Malah 301 ke Production? Dua Blok Rewrite Numpuk di `.htaccess`

26 Juli 2026·4 menit baca
Staging WordPress di Subpath Malah 301 ke Production? Dua Blok Rewrite Numpuk di `.htaccess`

Klien punya situs WordPress produksi dan minta clone staging supaya saya bisa membangun ulang temanya tanpa menyentuh situs live. Saya pakai WP Staging, dan karena hosting-nya cuma satu domain, clone-nya jatuh di subpath: situs-klien.com/staging/. Plugin bilang sukses. Saya buka situs-klien.com/staging/, dan bukannya melihat clone, browser malah melompat 301 ke situs-klien.com/, production. URL di address bar berubah sendiri, dan yang tampil jelas tema serta konten produksi, bukan clone yang barusan saya buat.

Makin saya klik, makin aneh polanya:

  • situs-klien.com/staging/tentang/ (halaman staging) 301 ke situs-klien.com/tentang/, versi prod.
  • situs-klien.com/staging/wp-json/ balas 404. REST API staging mati total.
  • Archive custom post type situs-klien.com/staging/produk/ juga 404.
  • Slug yang memang tidak ada langsung 404, bukan diarahkan ke 404 milik staging.
  • Tapi situs-klien.com/staging/wp-admin/ jalan mulus. Saya bisa login ke dashboard staging tanpa masalah.

Jadi dashboard-nya hidup, tapi seluruh front-end-nya seakan bukan milik staging. Ini yang bikin saya penasaran: kalau clone-nya gagal total, wp-admin harusnya ikut rusak. Ini setengah hidup.

Dua tebakan pertama yang meleset

Tebakan pertama saya klasik untuk WP Staging: siteurl dan home di database clone masih menunjuk ke URL produksi. Kalau search-replace saat cloning tidak lengkap, WordPress akan terus menormalkan URL balik ke domain prod. Saya cek wp_options di database staging:

SELECT option_name, option_value
FROM wp_options
WHERE option_name IN ('siteurl', 'home');

Keduanya sudah benar, menunjuk ke https://situs-klien.com/staging. Jadi bukan itu. Tebakan kedua: mungkin search-replace konten kurang bersih, ada URL absolut prod yang nyangkut. Tapi itu tidak menjelaskan 301 di level request paling awal, sebelum PHP sempat merender apa pun. 301 yang terjadi sebelum tema dimuat itu bau rewrite, bukan bau konten.

wp-admin yang tetap hidup justru jadi petunjuk terbesarnya. Direktori wp-admin/ itu folder fisik yang benar-benar ada di disk. Kalau front-controller WordPress diarahkan ke tempat yang salah tapi folder fisik tetap kebuka normal, berarti masalahnya di aturan rewrite, bukan di database atau PHP.

Akar masalah: dua blok rewrite numpuk

Saya buka .htaccess di root staging (/staging/.htaccess), dan di situ jawabannya. Ada DUA blok rewrite WordPress yang bertumpuk dalam satu file:

# blok bare warisan produksi, ikut ke-clone, TANPA RewriteBase
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
 
# BEGIN WordPress  (blok bikinan WP Staging, ini yang benar)
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /staging/
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /staging/index.php [L]
</IfModule>
# END WordPress

Blok atas itu warisan .htaccess produksi yang ikut tersalin waktu cloning. Dia tidak punya RewriteBase dan mengarahkan semuanya ke /index.php di root, yaitu front-controller produksi. Dan yang mematikan: flag [L] di baris RewriteRule . /index.php [L]. [L] artinya last, berhenti memproses rule setelah baris ini. Untuk request apa pun yang bukan file atau folder fisik, blok atas cocok duluan, mengirimnya ke /index.php prod, lalu [L] menghentikan chain. Blok WP Staging di bawah yang punya RewriteBase /staging/ tidak pernah kebagian giliran jalan.

Begitu request masuk ke index.php produksi, WordPress prod tidak mengenali URL /staging/tentang/ sebagai miliknya, redirect_canonical() jalan, dan dia 301 ke bentuk kanonik versi prod: /tentang/. Itu sumber redirect-nya. REST dan archive CPT 404 karena sama-sama diserahkan ke instance prod yang tidak punya route itu dalam konteks subpath. Dan wp-admin selamat justru karena dia direktori fisik: RewriteCond %{REQUEST_FILENAME} !-d gagal (folder-nya ada), jadi rule di-skip dan Apache menyajikan wp-admin/ langsung tanpa lewat rewrite sama sekali.

Perbaikannya

Perbaikannya sepele begitu duduk perkaranya jelas: hapus blok bare warisan prod di atas, sisakan hanya blok # BEGIN WordPress yang punya RewriteBase /staging/.

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /staging/
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /staging/index.php [L]
</IfModule>
# END WordPress

Simpan, lalu verifikasi dari command line, jangan cuma dari browser:

# -I header saja, -L ikuti redirect, -s senyap
curl -sIL "https://situs-klien.com/staging/tentang/" | grep -i "^HTTP\|^location"

Yang kamu mau lihat sekarang: 200, tanpa 301 yang lompat ke domain prod. Kalau masih ada 301 dengan Location menunjuk ke root tanpa /staging/, berarti blok bare-nya belum benar-benar terhapus.

Satu jebakan yang hampir bikin saya mengira perbaikan gagal: browser meng-cache 301 dengan sangat agresif, kadang seperti permanen. Setelah .htaccess dibetulkan, browser saya masih melompat ke prod karena mengingat 301 yang lama. Uji di browser baru atau mode incognito, atau pakai curl yang tidak menyimpan cache sama sekali. Kalau tidak, kamu akan menyalahkan .htaccess yang sebenarnya sudah benar.

Catatan terakhir: bug ini muncul lagi persis sama waktu saya bikin clone staging kedua di subpath berbeda. Pola numpuk blok ini memang bawaan cloning WP Staging ke subpath, bukan kejadian sekali. Kalau kamu clone ke subdomain (staging.situs-klien.com) alih-alih subpath, masalah ini tidak muncul karena tiap subdomain punya document root sendiri dan tidak mewarisi .htaccess prod.

Checklist

  • Kalau URL staging 301 ke production sebelum PHP merender apa pun, curigai rewrite, bukan database atau konten.
  • wp-admin yang tetap hidup padahal front-end rusak itu petunjuk: direktori fisik lolos rewrite, jadi masalahnya di aturan rewrite front-controller.
  • Buka .htaccess staging dan cari blok WordPress ganda. Blok bare warisan prod dengan [L] menang duluan dan menghentikan chain.
  • Sisakan hanya blok # BEGIN WordPress yang punya RewriteBase menunjuk ke subpath staging.
  • Verifikasi dengan curl -sIL, bukan browser, karena 301 di-cache agresif. Tes ulang di incognito.
  • Kalau bisa, clone ke subdomain, bukan subpath. Subdomain punya document root sendiri dan tidak mewarisi .htaccess prod.