A WordPress URL like https://example.com/blog/some-post-title/ looks clean, but there’s no directory or file on the server that actually matches that path. Behind the scenes, Apache’s mod_rewrite module intercepts the request and silently forwards it to index.php. The rules that make this happen live in .htaccess, and when a WordPress site suddenly starts throwing 404s or 500s for no obvious reason, the cause is very often a misread rule in that file.
Note:
.htaccessis a configuration file Apache reads on a per-directory basis. On shared hosting environments where you can’t edit the server’s global configuration directly, dropping this file into a directory is often the only way to change how requests to that directory behave — which is exactly why it’s one of the files people touch most often on shared hosting.
The Basic Syntax — Four Building Blocks
The .htaccess block WordPress writes out by default is built from four elements working together:
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /blog/
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /blog/index.php [L]
</IfModule>
# END WordPress
RewriteEngine On— turns on rewriting for this directory. Without it, every line below is ignored.RewriteCond(condition) — sets a precondition for theRewriteRuleimmediately following it.%{REQUEST_FILENAME} !-fmeans “the requested path is not an existing file,” and!-dmeans “not an existing directory.” The rule below only fires when both conditions hold.RewriteRule(the rewrite itself) — takes the formpattern target [flags]..matches “one or more of any character” — essentially any path — and forwards it internally to/blog/index.php. That single line is the actual mechanism behind WordPress’s pretty permalinks.- The
[L]flag — short for “Last.” Once this rule applies, Apache stops evaluating further rules, which prevents a chain of rules from double-rewriting the same request in unintended ways.
Other flags worth knowing: [R=301] (returns a genuine permanent redirect the browser follows, changing the visible URL, as opposed to a silent internal forward), [NC] (case-insensitive matching), and [QSA] (appends the original query string onto the rewritten target instead of dropping it). A plain internal rewrite and an [R=301]-flagged redirect can look superficially similar in a rule file but behave completely differently — that distinction is worth keeping straight.
RewriteBase — the Anchor Point for Relative Paths
The line most often overlooked is RewriteBase /blog/. When a RewriteRule‘s target is a relative path (one that doesn’t start with /), Apache resolves it against this RewriteBase value. The WordPress installer generates this value automatically, based on whatever URL path the install is actually running at (its home_url). That works fine under normal conditions — but if the server structure changes after installation, this one value can be left pointing at a path that no longer exists.
A Real Example — The Trap We Hit During a Subdomain Migration
We hit exactly this trap while setting up the English version of this blog (en.wpmm.jp/blog/). Here’s how it played out:
- We first installed WordPress at
wpmm.jp/public_html/en/blog/through the hosting provider’s one-click installer. At that point the site’s URL structure waswpmm.jp/en/blog/— a subdirectory under the main domain. - We then created a subdomain,
en.wpmm.jp, and pointed its docroot at that samewpmm.jp/public_html/en/directory. The visible URL structure now becameen.wpmm.jp/blog/. - We updated WordPress’s internal URL settings to match the new subdomain via
wp option update homeandwp option update siteurl. - But
.htaccess‘sRewriteBasewas still holding the value from step 1:/en/blog/.
What happened next was interesting. The homepage (a near-direct hit on index.php) loaded fine, but every individual post, category archive, search result, and 404 page came back as a 500 error. The homepage largely bypasses mod_rewrite and resolves close to a direct file hit, while any permalinked URL has to pass through the RewriteRule internal forward. When that forward resolved paths against the stale RewriteBase /en/blog/, the result no longer matched the actual document root structure, and WordPress couldn’t recognize the incoming request as a valid URL.
One more wrinkle: running wp rewrite flush --hard — the WP-CLI command meant to regenerate WordPress’s internal routing rules and write them back to .htaccess — didn’t fix the file. The shared hosting environment’s permission constraints returned a warning (“Regenerating a .htaccess file requires special configuration”) without actually modifying anything. The fix ended up being simple but manual: back up .htaccess, then hand-edit RewriteBase to the correct path (/blog/).
The General Lesson
The takeaway generalizes beyond mod_rewrite specifically, to auto-generated configuration files as a category: a config file generated at install time captures a snapshot of the conditions that existed at that moment — it doesn’t automatically track the infrastructure changing underneath it later. Even a “regenerate” command like wp rewrite flush can’t be trusted blindly, since the permissions of the environment it runs in may quietly prevent it from touching the actual file. After any infrastructure change — subdomain migration, directory move, domain switch — opening .htaccess directly and visually confirming that RewriteBase still matches the real path structure is a small extra step that turns out to be the most reliable way to catch this class of bug.
Summary
| Element | Role |
|---|---|
RewriteEngine On |
Turns on rewriting for this directory |
RewriteCond |
Sets a precondition for the RewriteRule below it (e.g., only if not an existing file/directory) |
RewriteRule |
Forwards or redirects matching requests to another path |
RewriteBase |
The anchor directory relative paths resolve against — a value frozen at install time |
[L] |
Stops evaluating further rules once this one matches |
[R=301] |
Returns a real permanent redirect (visible URL change), not a silent internal forward |
.htaccess reads like an incantation at first glance, but it’s decodable once you know what these four elements are each responsible for. RewriteBase in particular is worth checking deliberately any time the underlying infrastructure changes — it’s exactly the kind of value that gets left behind.