Build1 publisher3 min readPublished
WordPress's .htaccess kept the old /en/blog/ path after the docroot moved to a subdomain
Only permalinked URLs returned 500s, because the homepage resolves close to a direct hit on index.php and never reaches the forwarding rule WordPress wrote against the old subdirectory path. The usual four-element reading of the default block leaves out the line that held that path.
The Engineer · Build desk

What happened
- WordPress's home and siteurl options were updated with WP-CLI to match the subdomain, but .htaccess was still holding the RewriteBase value written at install time, /en/blog/.
- The homepage kept loading while every individual post, category archive, search result and 404 page came back as a 500 error.
- wp rewrite flush --hard, the command meant to regenerate the routing rules and write them back to .htaccess, did not fix the file, and the shared host's permission constraints returned a warning.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- contradiction The post pins the 500s on the stale RewriteBase, yet by its own account of the directive a target beginning with a slash is never resolved against it, so the rule target is the other place the old path can sit in that file.
- decision Updating home and siteurl moves only WordPress's record of the URL, so anyone repointing a docroot has to treat opening .htaccess as a separate deliberate step.
- constraint Where host permissions stop WP-CLI writing the file, regeneration is not available and the repair has to be a hand edit on the server.
- exposure A monitor that fetches only the homepage passes throughout this failure, because the homepage is the one URL that does not go through the forward.
The split symptom is the useful part of the diagnosis. Two RewriteCond lines gate the forwarding rule: `%{REQUEST_FILENAME} !-f` means the requested path is not an existing file, `!-d` means it is not an existing directory, and the rule below them fires only when both hold [5]. A permalink satisfies both, because no file or directory on the server matches that path, so it always takes the internal forward to `index.php` [1]. The homepage does not. According to the dev.to post, it resolves close to a direct file hit and largely bypasses mod_rewrite [16]. So the homepage stayed up while every post, category archive, search result and 404 page returned 500 [15].
RewriteBase only enters when the rule target is relative. The post is precise about this: Apache resolves a target that does not start with `/` against the RewriteBase value, and the WordPress installer writes that value from the install's `home_url` [9]. The default block printed in the same post targets `/blog/index.php` [4]. That target starts with a slash, so RewriteBase is not used to resolve it [18]. The segment `/blog/` appears twice in that block, once as the RewriteBase value and once inside the target [20]. The post reports that RewriteBase still held `/en/blog/` after the move [14], and does not reproduce the site's own file. I would expect the target line held the same stale segment, and that it is what sent the internal forward to a path the new docroot did not have.
The four-element walkthrough has the same gap. The printed block uses four distinct directives: RewriteEngine, RewriteBase, RewriteRule and RewriteCond [19]. The walkthrough covers three of them plus the `[L]` flag [3], and the directive it leaves out is the one the post then calls the line most often overlooked [10].
`[L]` is short for Last. Once the rule applies Apache stops evaluating further rules, so a chain cannot double-rewrite the same request [7]. During a domain move the flag people reach for instead is `[R=301]`, which returns a permanent redirect the browser follows and changes the visible URL; the plain rewrite forwards silently [8].
`wp rewrite flush --hard` is meant to regenerate WordPress's internal routing rules and write them back to `.htaccess`. On this host it did not fix the file, and the shared hosting environment's permission constraints returned a warning [17]. So the tool for regenerating the file could not write the file. What is left is a hand edit, and on shared hosting where the global server config is off-limits, that per-directory file is often the only way to change how requests to the directory behave [2].
This transfers only if the install path changed after WordPress generated the block and the old segment no longer exists under the new docroot. Move a docroot and keep the path segment, and nothing in the block goes stale. Here the one-click installer put WordPress at `wpmm.jp/public_html/en/blog/`, which made the URL structure `wpmm.jp/en/blog/` [11]; the subdomain `en.wpmm.jp` was then pointed at `wpmm.jp/public_html/en/`, making the visible structure `en.wpmm.jp/blog/` [12]. `wp option update home` and `wp option update siteurl` changed WordPress's record of the URL [13]. Apache's copy of the old path stayed where the installer wrote it [14].
What to watch
- Whether the post's author publishes the exact WP-CLI warning text and the manual .htaccess edit that restored the permalinks.
- Whether the same host permissions also block WordPress from rewriting .htaccess when permalink settings are changed from the admin screen.
- Whether the site's own file turns out to have carried the stale segment in the RewriteRule target as well as in RewriteBase.