WordPress Permalinks Not Working: Fix 404 Errors on Posts and Pages
Posts returning 404 after a migration, host move, or plugin update? Here is the full sequence I use to rebuild permalinks and restore clean URLs without losing SEO.

What's Happening
Your homepage loads, but every other URL on the site throws a 404. This is the classic broken-permalinks pattern in WordPress, and it almost always traces back to a missing rewrite block, a damaged .htaccess file, or a server that no longer knows how to hand pretty URLs back to WordPress. The fix is usually five minutes once you know which of the three causes you are dealing with.
Broken permalinks are one of those WordPress problems that look terrifying for thirty seconds and then turn out to be embarrassingly small. The homepage loads fine, branding intact, navigation intact, but the moment you click any post or page link the site throws a 404. New site owners panic. Experienced site owners go straight to Settings > Permalinks because they have seen this pattern enough times to know what it usually is.
Most cases trace back to one of three things: a rewrite rules array in the database that got out of sync, a .htaccess file that lost its WordPress block during a migration, or an Nginx server block that is missing the try_files directive. Each has a clean fix. This guide walks the full sequence in the order I run it on client sites, from the one-click reset that solves the majority of cases to the harder problems where a plugin registered a broken rewrite rule and is fighting WordPress every time you save.
Step 1: The Thirty-Second Reset That Fixes Most Cases
Before you touch a file or open the database, log into wp-admin and go to Settings > Permalinks. You do not need to change the structure. Just scroll to the bottom and click Save Changes. WordPress regenerates the entire rewrite rules array, writes a fresh .htaccess block if it has permission to do so, and updates the cached version stored in wp_options.
On every site I have ever debugged for broken permalinks, this single step has fixed the problem about seven times out of ten. It costs you nothing to try first and rules out the most common cause before you go digging into server config. If the 404s stop after you save, you are done. Move on to clearing your caches and call it a day.
Step 2: Check Your .htaccess File on Apache
If the reset did not work and your site runs on Apache (most shared hosting does), the next suspect is .htaccess. Connect via FTP or your host's file manager, navigate to the WordPress root (the folder with wp-config.php in it), and look for a file named .htaccess. The dot at the front makes it hidden on many systems, so you may need to enable 'show hidden files' first.
Open it. You are looking for a block that starts with # BEGIN WordPress and ends with # END WordPress. If that block is missing, was commented out, or the file does not exist at all, that is your problem. Paste the default WordPress rewrite block back in and save.
# 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
Step 3: Fix the Server Block on Nginx
Nginx ignores .htaccess entirely. If your site is on Nginx (most managed WordPress hosts and a lot of VPS setups), the rewrite rules live inside the server block in the Nginx config file. The line you need is one specific try_files directive inside location /.
If you have shell access, edit /etc/nginx/sites-available/yourdomain.conf or wherever your host stores the server block. Add or restore the try_files line, save, then run sudo nginx -t to test the config and sudo systemctl reload nginx to apply it. On managed hosts where you do not have shell access, the control panel almost always has a 'Reset Nginx Rules' or 'Repair Permalinks' button that does the same thing.
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
}Step 4: Reset the Rewrite Rules in the Database
If the file edits did not stick, the next suspect is a corrupted rewrite_rules entry in wp_options. WordPress caches the compiled rules array in a single row of that table. When the array gets stale or contains rules that no longer apply, WordPress sometimes serves the cached version instead of regenerating, even after you click Save Changes.
Open phpMyAdmin, find the wp_options table, and search for the option_name value rewrite_rules. Set the option_value to an empty string and save. Then go back to wp-admin and resave Settings > Permalinks one more time. WordPress will rebuild the rules from scratch with no cached fragments left over.
-- Run in phpMyAdmin against your WordPress database
UPDATE wp_options
SET option_value = ''
WHERE option_name = 'rewrite_rules';Step 5: Isolate a Plugin or Theme Conflict
When file fixes and database resets both fail, the problem is almost always a plugin (or, rarely, a theme) registering a rewrite rule that overrides or conflicts with the WordPress defaults. SEO plugins, redirect plugins, multilingual plugins, and any plugin that adds custom post types are the usual suspects.
Deactivate every plugin from the WordPress admin. Resave permalinks. Test a post URL. If it works, the issue is in one of the plugins you just turned off. Reactivate them in groups of five, resaving permalinks each time, until the 404s come back. The last group you switched on contains the culprit. Narrow within that group one at a time.
If disabling every plugin did not fix it, switch to a default theme like Twenty Twenty-Five. If the URLs start working with the default theme, your active theme is registering broken rewrite rules in its functions.php. Look for any call to add_rewrite_rule() or register_post_type() that uses a malformed slug or rewrite array.
Step 6: Fix Custom Post Type 404s
A common variant of this problem is regular posts working fine while custom post type URLs (products, properties, events, portfolio items) throw 404s. This happens because the plugin or theme that registers the CPT only flushes the rewrite rules during activation. If you imported the CPT data without re-running the activation hook, the rules never got added.
The fix is to deactivate then reactivate the plugin that registers the CPT. That triggers the activation hook, which calls flush_rewrite_rules(), which adds the missing rules. Do not call flush_rewrite_rules() on every page load from your code, as some tutorials still suggest. It is an expensive operation and only needs to run when the rules actually change.
// One-shot flush. Drop into mu-plugins, load any page, then delete the file.
add_action('init', function () {
flush_rewrite_rules();
}, 11);Step 7: Clear Every Layer of Cache
WordPress sites today usually have three to five layers of caching stacked on top of each other. Browser cache, page cache plugin (WP Rocket, LiteSpeed, W3 Total Cache), object cache (Redis, Memcached), reverse proxy cache (Varnish, Nginx fastcgi cache), and CDN cache (Cloudflare). If any of these is holding a 404 response from before your fix, the URL will keep returning 404 even though the underlying rewrite is now correct.
Flush them in order. Start at the application layer (the page cache plugin), then the object cache, then the server-level cache, then the CDN. Test in an incognito window after each step so the browser cache does not lie to you. Most cases I see where 'the fix did not work' are actually cache layers that still hold the old 404.
A Real Client Example
A photographer client moved her portfolio site from SiteGround to Kinsta. The migration tool copied everything cleanly except, as it turned out, the .htaccess file. SiteGround uses Apache, Kinsta uses Nginx, so even if .htaccess had transferred it would have done nothing. The homepage worked, About worked (because Kinsta caches that as a static page), but every portfolio item under /work/ returned 404.
The fix took three minutes. I logged into the Kinsta control panel, clicked the 'Reset Permalinks' button under Tools, then went to wp-admin and resaved Settings > Permalinks. The button restored the try_files directive in her server block. The wp-admin save flushed the rewrite_rules option. All 240 portfolio URLs worked on the first refresh. She had spent two hours assuming it was a database corruption problem before reaching out.
How to Prevent This From Happening Again
After every host migration, theme switch, or major plugin update, resave Settings > Permalinks. It costs nothing and prevents most permalink issues before they happen. If you run a staging site, do the same thing after every staging-to-production push.
Keep a backup of your current .htaccess file in version control or a notes file. If something corrupts it, you have the exact working version to restore. The same applies to your Nginx server block if you have access to it.
Avoid installing two plugins that handle redirects or rewrite rules at the same time. SEO plugins like Yoast and Rank Math include redirect modules. Standalone redirect plugins like Redirection do the same job. Running both is the fastest way to create a rewrite conflict that takes days to track down.
Final Checklist Before You Close the Ticket
Run through this list and you are safe to mark the issue resolved.
- A post URL loads correctly (not the homepage, an actual single post)
- A category or tag archive URL loads correctly
- A custom post type URL loads correctly if the site has any
- Pagination URLs work (/page/2/, /category/news/page/2/)
- Resaving Settings > Permalinks does not throw a warning about .htaccess being unwritable
- Every cache layer has been flushed and a fresh incognito visit returns 200, not 404
Complete Fix Checklist
- 1Log in to wp-admin, go to Settings > Permalinks, and click Save Changes once without editing anything to flush the rewrite rules.
- 2Open your .htaccess file at the WordPress root and confirm the WordPress rewrite block is present and not commented out.
- 3On Nginx, confirm the server block has the try_files $uri $uri/ /index.php?$args; directive, since Nginx does not read .htaccess.
- 4Switch to a default theme and disable all plugins to rule out a plugin or theme registering a broken rewrite rule.
- 5Repair the wp_options table and reset the rewrite_rules entry to an empty serialized array, then resave permalinks.
- 6Test a single post URL, a category URL, and a custom post type URL to confirm every rewrite group is working.
- 7Clear the page cache, object cache, and any CDN cache before declaring the issue fixed.
Quick Tips
- Resaving Settings > Permalinks rewrites the rules without changing your URL structure, this is safe to do on a live site
- On shared hosts the most common cause is a .htaccess file that lost the WordPress block during a migration or restore
- Custom post types from a plugin need flush_rewrite_rules() to register, deactivating then reactivating the plugin usually does it
