Skip to content

When to Flush WordPress Rewrite Rules: Fixing 404s After Permalink or Post Type Changes

“I added a custom post type and both the archive and single pages return 404.” “I changed the permalink structure and only some URLs stopped working.” These are familiar symptoms in WordPress maintenance, and the cause is often stale rewrite rules.

This article explains what rewrite rules are, where they are stored, and when they need to be regenerated (“flushed”). It describes general WordPress behavior, not any specific product.

Note: The basics of Apache’s .htaccess and mod_rewrite are covered in a separate article. This one is about the layer inside it: WordPress’s own rules.

Two different “rewrites”

URL handling in WordPress has two stages with different jobs.

Stage Handled by Role
1 Web server (.htaccess etc.) Passes requests for non-existent files or directories to index.php
2 WordPress core (rewrite rules) Translates the URL that reached index.php into a query for a post or archive

A request for /news/2026/hello/ is handed to index.php at stage 1, then interpreted at stage 2 as something like “post type news, slug hello.” Even if stage 1 is correct, WordPress returns 404 when stage 2 has no matching rule. When people say “you need to flush,” they usually mean stage 2.

Where the rules live

Stage 2 rules are stored in the rewrite_rules option in the wp_options table, as a map from regular expressions to internal queries. Rebuilding every rule on every request would be costly, so WordPress builds them once and keeps the result in the database.

That is where the trap is. Changing what the rules are derived from (post types, taxonomies, the permalink structure) does not automatically rewrite the saved rewrite_rules. WordPress keeps using the old map, has no entry for the new URL, and returns 404. Flushing rebuilds the map.

When a flush is needed

  1. After changing the permalink structure. Saving it on the Permalinks screen rebuilds the rules.
  2. After adding or changing custom post types or taxonomies. Changing the rewrite argument (such as the slug) of register_post_type() or register_taxonomy() changes the rules.
  3. After activating or deactivating a plugin that adds rules. Plugins with their own URLs or endpoints need a rebuild at activation.
  4. After moving a site to another environment. A different domain or install directory can call for a recheck.
  5. After .htaccess was lost or overwritten. This relates to the “hard” flush below.

Soft flush vs. hard flush

flush_rewrite_rules() behaves differently depending on its $hard argument.

  • Soft ($hard = false): rebuilds only the rewrite_rules option in the database.
  • Hard ($hard = true, the default): also rewrites WordPress’s block in .htaccess on Apache (when the file is writable and mod_rewrite is available).

Clicking “Save Changes” on the Permalinks screen effectively performs this rebuild. In WP-CLI:

# List the current rules
wp rewrite list --format=table

# Rebuild the database side only (soft)
wp rewrite flush

# Also rewrite .htaccess (hard)
wp rewrite flush --hard

# Set the permalink structure (rules are updated on save)
wp rewrite structure '/%postname%/'

When you cannot use the admin screen, or want to check several sites in turn, running wp rewrite list first to see whether a rule for the expected URL exists helps narrow down the cause.

A common mistake: flushing on every request

Some code calls flush_rewrite_rules() from functions.php or an init hook because “it fixes the 404.” Avoid this.

  • A flush builds the rules and writes them to the database. Running it on every request is unnecessary load.
  • A hard flush may also write to .htaccess.
  • It can appear to work while hiding the real problem: when the types are registered.

The right place is inside the activation hook (register_activation_hook), and the order matters:

function myplugin_register_types() {
    register_post_type( 'news', array(
        'public'  => true,
        'rewrite' => array( 'slug' => 'news' ),
    ) );
}
add_action( 'init', 'myplugin_register_types' );

function myplugin_activate() {
    // Register first, then flush. Reversed, the new rules are missing.
    myplugin_register_types();
    flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'myplugin_activate' );

function myplugin_deactivate() {
    flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'myplugin_deactivate' );

At activation time init has not yet run for this plugin, so call your registration function yourself before flushing. Skip that, and the flush will not include the new rules: you activate the plugin and still get 404.

Narrowing down the cause

Symptom First suspect
Only the new post type’s single pages 404 Stale rules. Check with wp rewrite list
Home works, but all posts 404 Missing .htaccess, or rewriting disabled on the server
The rule exists but still 404 Slug conflict (such as a page with the same slug) or plugin registration order
Saving in the admin fixes it, but it returns the next day Something periodically overwrites .htaccess or the rules

The last row is easy to miss. If a fix keeps reverting, find what is rewriting the rules or .htaccess rather than flushing again and again.

Nginx

Nginx does not read .htaccess, so “rewrite .htaccess on a hard flush” has no meaning there. The server configuration needs its own directive (such as try_files) that passes requests to index.php. Stage 2 works the same on Apache and Nginx, so the habit of separating stage 1 from stage 2 applies to both.

A check procedure for maintenance

  1. Before the work, note key URLs: home, a representative post, and the archive and single pages of custom post types.
  2. After the update, open the same URLs while logged out.
  3. If something returns 404, check wp rewrite list for the expected rule.
  4. If the rule is missing, run wp rewrite flush; use --hard only when needed.
  5. Make sure a backup exists first (see the 3-2-1 rule article).

A flush does not change your content, but a hard flush rewrites .htaccess, so if you have added your own rules to .htaccess, save a copy beforehand. WordPress manages only the section between # BEGIN WordPress and # END WordPress; lines outside it are kept.

Summary

  • Rewrite rules are WordPress’s URL-to-query map, stored in the rewrite_rules option. Changing registrations does not update it automatically.
  • Separating stage 1 (server) from stage 2 (WordPress rules) narrows down a 404.
  • Flush after changes to the permalink structure, post types, taxonomies, or plugins that add rules.
  • Do not call flush_rewrite_rules() on every request. Call it in the activation hook, after registering.
  • For recurring 404s, look for what keeps rewriting the rules or .htaccess instead of flushing repeatedly.