WordPress Posts Return 404 but the Homepage Works
If your homepage loads fine but every individual post and page returns a 404, the URL rewrite rules WordPress relies on are missing or broken in your
Published
If your homepage loads fine but every individual post and page returns a 404, the URL rewrite rules WordPress relies on are missing or broken in your .htaccess file. The fastest fix: go to Settings → Permalinks in wp-admin and click Save Changes without changing a thing. That forces WordPress to regenerate the rewrite block. If the 404s persist, your .htaccess isn’t writable by the web server, Apache is configured to ignore it, or you’re on Nginx (which doesn’t use .htaccess at all).
Why the homepage works but posts don’t
Your homepage works because a request to / is served straight from index.php at the document root. No rewriting required.
A request to /2026/my-post/ is different. That path is not a real folder on disk. For WordPress to answer it, Apache has to internally route the request to index.php, and WordPress then reads REQUEST_URI, matches it against the rewrite rules stored in the rewrite_rules option in the database, resolves the query vars (WP::parse_request()), and loads the right post.
The handoff from Apache to index.php is the part that breaks. It depends entirely on this block in .htaccess:
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
The two RewriteCond lines say: if the requested path is not a real file (!-f) and not a real directory (!-d), send it to index.php. Remove that block and Apache looks for a literal /2026/my-post/ directory, doesn’t find one, and returns its own 404 — before WordPress ever sees the request.
Why WordPress overwrites .htaccess when you save permalinks
This is the part most guides skip. When you click Save on the Permalinks screen, WordPress calls flush_rewrite_rules(). On Apache with a writable .htaccess, that triggers save_mod_rewrite_rules(), which regenerates the block above via WP_Rewrite::mod_rewrite_rules() and writes it back using insert_with_markers().
insert_with_markers() only touches the lines between # BEGIN WordPress and # END WordPress. Everything outside those markers is left alone. Everything inside is discarded and rewritten. Newer WordPress versions even print the warning directly in the file:
Any changes to the directives between these markers will be overwritten.
So “WordPress overwrites my .htaccess” is true but narrow: it only rewrites its own marked block, and only when the file is writable. If the file is not writable, the save silently does nothing to disk. WordPress shows a yellow notice on the Permalinks screen telling you to paste the rules manually, and if you miss that notice, your rewrite block stays missing and every post keeps returning 404.
How to fix it, in order
1. Re-save your permalinks. Settings → Permalinks → Save Changes. No edits needed. This re-runs the flush and rewrites the block. It resolves the majority of cases, including stale rules after a migration.
2. Check that .htaccess exists and is writable. It lives in the same folder as wp-config.php. It’s a dotfile, so enable “show hidden files” in your FTP client or use:
ls -la /path/to/wordpress/.htaccess
If it’s missing, create it and paste the correct block. If your install lives in a subdirectory, RewriteBase and the final rewrite target must match that path — getting this wrong is the most common reason a hand-pasted block still 404s. Our .htaccess generator builds the exact block for your install path so you don’t have to guess.
3. Confirm Apache is allowed to read .htaccess. If the virtual host has AllowOverride None, Apache ignores the file completely regardless of its contents. You need AllowOverride All (or at least FileInfo) on the WordPress directory. This is in the server config, not .htaccess, so it requires server access:
<Directory /var/www/html>
AllowOverride All
</Directory>
4. Confirm mod_rewrite is enabled. On Debian/Ubuntu:
sudo a2enmod rewrite
sudo systemctl restart apache2
5. On Nginx, there is no .htaccess. Rewrites go in the server block instead:
location / {
try_files $uri $uri/ /index.php?$args;
}
Quick diagnostic: whose 404 is it?
Look at the 404 page. If it’s an unstyled Apache or host error page, Apache never reached WordPress — that’s a .htaccess or server-config problem (steps 2–4). If it’s your theme’s styled 404, WordPress is receiving the request but can’t match it — the rules in the database are stale, and re-saving permalinks (step 1) is the fix. This one distinction saves most people an hour of guessing.
What not to do
Don’t deactivate all your plugins first. A pure “posts 404, homepage works” symptom is a rewrite problem, not a plugin conflict. Disabling plugins is the reflex answer on forums and it almost never touches this.
Don’t chmod 777 your .htaccess. WordPress needs the file writable by the web server user, which is not the same thing as world-writable. 644 with the correct owner is fine and safe; 777 is a security hole that won’t fix the routing anyway.
Don’t paste custom rewrite rules inside the # BEGIN WordPress / # END WordPress markers. They’ll be wiped the next time anyone saves permalinks. Put custom directives above the # BEGIN WordPress line.
Don’t reinstall WordPress. The core files are fine. This is one missing block of text, not a corrupt install.
Don’t blame your theme. Permalink routing is WordPress core. A theme cannot cause it and cannot fix it.
Still stuck?
If you’ve re-saved permalinks, confirmed the block is present and writable, and set AllowOverride All, the remaining suspect is a security plugin or a host-level rule regenerating or locking .htaccess behind your back. Check the file’s modification timestamp right after a permalink save — if it didn’t change, something is blocking the write. Regenerate a clean block with the .htaccess generator, paste it in, and re-test with a hard refresh.