RevealTheme logo
Back to Blog

WordPress Permalinks: The Structure Decision That Sticks

WordPress Permalinks: The Structure Decision That Sticks
The RevealTheme Team

By

··Updated May 27, 2026·4 min read

Open Settings > Permalinks in any WordPress install and you are looking at one of the most consequential screens in the dashboard. The radio buttons look trivial, but the structure you pick becomes the address of every post you ever publish. Most advice stops at "use Post name and move on" — which is correct, but it skips the part that actually bites people later: how these URLs are generated, what happens to them when you switch, and where the wheels come off. This article is about the machinery, not just the recommendation.

What "pretty permalinks" actually are under the hood

WordPress does not store a file called /my-post/ anywhere. Every front-end request hits a single PHP entry point, and WordPress decides what to render by parsing the URL against a set of rewrite rules — an array of regular expressions mapping URL patterns to query variables. A request for /my-post/ gets matched and internally rewritten to something like index.php?name=my-post. The "ugly" query string never disappears; it is just hidden from the visitor.

The piece that makes the pretty URL reach index.php in the first place lives at the web-server layer, and it differs by stack:

  • Apache uses mod_rewrite and an .htaccess file in your web root. When you save a non-default permalink structure, WordPress writes a small block between # BEGIN WordPress and # END WordPress markers that funnels any request not matching a real file or directory into index.php.
  • nginx has no .htaccess. The equivalent is a try_files $uri $uri/ /index.php?$args; directive in the server block. This is why managed hosts like Kinsta, WP Engine, and Cloudways (all nginx-based) ship this rule preconfigured — and why a plugin that claims it can "fix your permalinks by editing .htaccess" does nothing on those hosts. There is no .htaccess for it to touch.

So pretty permalinks are a two-layer system: the server hands every clean URL to WordPress, and WordPress's internal rewrite rules turn that URL back into a query. Understanding this split explains nearly every permalink bug you will ever hit.

The single most common breakage: stale rewrite rules

WordPress caches the compiled rewrite rules in the rewrite_rules row of the wp_options table. It does not rebuild them on every request — that would be wasteful. It rebuilds (flushes) them when something forces it to, most notably when you visit Settings > Permalinks and click Save Changes. That is the entire reason the folk remedy "just go to Permalinks and hit Save" fixes so many 404s after a migration or a plugin change: you are flushing the rule cache.

This matters most when you register a custom post type or a custom taxonomy. Their rewrite rules only exist after a flush. If you add a CPT and its archive throws a 404, you have not broken anything — you simply have not flushed. In code the call is flush_rewrite_rules(), and the correct place for it is the plugin or theme activation hook, never on every page load, because flushing is expensive.

The structure tokens, and the ones that quietly cost you

The custom structure field accepts tokens like %postname%, %year%, %monthnum%, %day%, %category%, %author%, and %post_id%. Two are worth a specific warning:

  • %category% embeds the category slug in the URL. The operational cost is not abstract: when a post belongs to multiple categories, WordPress picks one (lowest ID) for the URL, and recategorizing a post changes its permalink. On a site that reorganizes its taxonomy as it grows, this generates a steady trickle of broken URLs that you must redirect.
  • The "never put a token before %postname%" warning you may have read is legacy. Before WordPress 3.3 (released in 2011), a structure starting with %postname% forced a slow, verbose rewrite ruleset because WordPress could not distinguish a post URL from a page URL cheaply. That performance penalty was fixed over a decade ago. In 2026 you can safely lead with %postname%; treat the old warning as historical, not current guidance.

The trailing slash is not a style choice you make

WordPress decides the trailing slash for you based on whether your structure ends in a slash, and then it 301-redirects the other form to the canonical one. If your structure is /%postname%/, a request to /my-post redirects to /my-post/. This is handled by the canonical redirect function and is good for SEO because it prevents two URLs serving identical content. The practical takeaway: do not fight it, and do not hand-build internal links in the non-canonical form, or every one of them eats a needless redirect hop.

Custom post types: control the URL at registration, not after

For sites with multiple content types — a recipe site, a course platform, a case-study library — the clean URLs come from the rewrite argument in register_post_type(). Two keys do the heavy lifting:

  • 'slug' => 'recipes' sets the base segment, producing /recipes/chocolate-cake/.
  • 'with_front' => false strips your global permalink prefix from the CPT URL. If your blog runs on /blog/%postname%/ and you leave with_front at its default true, your recipes inherit the prefix and become /blog/recipes/chocolate-cake/ — almost never what you want.

Choose these slugs deliberately, because changing a CPT slug later invalidates every URL for that type at once. And remember the flush: after registering or changing a CPT's rewrite args, you must flush rewrite rules (re-save Permalinks, or run the activation flush) or the new URLs will 404.

Migrating an existing site without bleeding traffic

Changing the permalink structure on a published site is genuinely expensive, which is why "pick it correctly on day one" is the real advice. If you must migrate, the work is concrete and tool-specific:

  1. Map old to new and create 301s. The cleanest path is the free Redirection plugin, which can store thousands of rules in the database and even import a CSV of mappings. Yoast SEO Premium and Rank Math ship their own redirect managers if you already run one. For a deterministic pattern change — say /%year%/%monthnum%/%postname%/ to /%postname%/ — a single regex RewriteRule in .htaccess can cover the whole set at the server layer, which is faster than per-URL database lookups but unforgiving if your old URLs were not perfectly uniform.
  2. Rewrite internal links. Old absolute links pointing at the previous structure will redirect rather than break, but every redirect is latency and lost link equity. A search-and-replace tool such as Better Search Replace fixes these in the database in one pass. Mind serialized data — use a tool that unserializes safely rather than a raw SQL UPDATE.
  3. Resubmit your sitemap in Google Search Console and watch the Coverage report as the index turns over. A re-crawl of a large site is not instant; expect weeks, not days, before the new URLs fully replace the old ones in the index.

Expect a temporary dip while search engines reprocess the URLs, and budget real hours for QA — broken internal links and missed redirects are where these projects actually go wrong, not the permalink setting itself.

The honest recommendation

For the overwhelming majority of sites, /%postname%/ remains the right call in 2026: it is clean, it decouples the URL from dates and categories, and it survives editorial reorganization without generating redirects. Reach for a date-based structure only when publication date is part of the content's identity (a news desk), and reach for %category% only when your taxonomy is genuinely stable and meaningful in the address bar. Whatever you choose, set it before you publish, register your custom post types with intentional slugs and with_front => false, and remember that almost every "my URLs are 404ing" panic is solved by flushing the rewrite rules.