Common causes
- The .htaccess file is missing or lacks the WordPress rewrite block
- Apache mod_rewrite is not enabled
- AllowOverride None in the Apache vhost, so .htaccess is ignored
- Nginx server block missing try_files $uri $uri/ /index.php?$args;
- Rewrite rules in the database were not flushed after a migration or plugin change
- The site moved to a subfolder but RewriteBase was not updated
How to fix it
- Re-save permalinks. Go to Settings > Permalinks and click Save Changes without changing anything. This flushes rewrite rules and tries to rewrite .htaccess.
- Restore the .htaccess block. Make sure the WordPress root has a .htaccess with the standard # BEGIN WordPress block. Create it if missing, with permissions 644.
- Enable mod_rewrite and AllowOverride. On Ubuntu run sudo a2enmod rewrite. In the vhost's <Directory> for the site set AllowOverride All (or at least FileInfo), then run apachectl configtest and reload Apache.
- Add try_files on Nginx. Nginx ignores .htaccess. In the server block add location / { try_files $uri $uri/ /index.php?$args; }, then run nginx -t and reload.
- Check RewriteBase for subfolders. If WordPress is in /blog/, use RewriteBase /blog/ and RewriteRule . /blog/index.php [L] in .htaccess.
Default WordPress .htaccess block (site in web root)
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress How to stop it happening again
- Include .htaccess (a hidden file) when migrating sites
- Add the Nginx try_files rule to every WordPress server block
- Re-save permalinks after migrations and plugin changes
- Keep custom rules outside the # BEGIN/END WordPress markers