WordPress guide
WordPress Redirect Guide
Choose the right status code, pick the right place to put each redirect, and keep URL changes from costing you traffic — with working .htaccess, Nginx and PHP examples.
A redirect tells browsers and crawlers that the content at one URL now lives at another. Done well, visitors never notice and search engines transfer ranking signals to the new address. Done badly, you get 404s, redirect loops, slow chains and lost traffic. This guide covers which status code to use, where to implement redirects on a WordPress site, and how to verify them.
Choosing the status code
| Code | Meaning | Method preserved? | Use for |
|---|---|---|---|
| 301 Moved Permanently | Permanent | No (POST may become GET) | Slug changes, migrations, HTTPS and www canonicalisation |
| 302 Found | Temporary | No (POST may become GET) | Short-term moves, A/B tests, maintenance, login redirects |
| 307 Temporary Redirect | Temporary | Yes | Temporary redirects of form submissions or API calls |
| 308 Permanent Redirect | Permanent | Yes | Permanent moves of API endpoints that receive POST/PUT |
For ordinary page URLs, 301 is the standard choice for permanent moves. Google treats 301 and 308 as strong signals that the target should become the canonical URL, and 302/307 as weak signals that keep the original indexed. The semantics are defined in RFC 9110. Browsers cache 301 and 308 responses aggressively, sometimes indefinitely — so test with 302 while you're experimenting, then switch to 301.
When you need redirects
- Slug or permalink changes — renaming a post, changing the permalink structure, removing
/category/bases. - Content consolidation — merging several thin posts into one comprehensive page.
- Deleted content with a close replacement. If there is no relevant replacement, return a 404 or 410 instead of redirecting everything to the homepage; Google treats that as a soft 404.
- Domain migrations — every old URL to its equivalent on the new domain, path for path.
- HTTP to HTTPS and www vs non-www — one canonical host for every request.
Where to put redirects
The earlier in the request a redirect happens, the cheaper it is. A server-level rule responds before PHP loads; a WordPress-level redirect boots the whole application first.
Apache: .htaccess
On Apache and LiteSpeed hosts, place rules in .htaccess above the # BEGIN WordPress block, because WordPress rewrites that section when permalinks are saved.
# Single URL (mod_alias)
Redirect 301 /old-page/ https://example.com/new-page/
# Force HTTPS and non-www in one hop (mod_rewrite)
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [L,R=301]
</IfModule>
# BEGIN WordPress
# ...WordPress rules...
# END WordPressDon't mix Redirect (mod_alias) and RewriteRule (mod_rewrite) for overlapping URLs: the modules run independently and the results are hard to predict. If your host terminates SSL at a proxy or CDN, %{HTTPS} may always be off; check X-Forwarded-Proto instead or handle HTTPS at the CDN. The .htaccess generator builds these blocks, and the redirect generator converts lists of old and new URLs into rules.
Nginx
Nginx ignores .htaccess. Redirects go in the server configuration (or your host's panel if you don't have access):
# Canonical host: separate server block, cheapest possible redirect
server {
listen 443 ssl;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
# Inside the main server block
location = /old-page/ {
return 301 /new-page/;
}
# Pattern-based
rewrite ^/blog/(\d{4})/(\d{2})/(.+)$ /$3 permanent;return is faster and clearer than rewrite for exact matches. Use rewrite (with permanent for 301) only when you need capture groups.
PHP: wp_redirect and wp_safe_redirect
For redirects that depend on WordPress data — the current user, post meta, a custom field holding the target — hook into template_redirect, which runs after the query is resolved but before any output:
<?php
add_action( 'template_redirect', function () {
// Send a retired landing page to its replacement.
if ( is_page( 'spring-sale-2025' ) ) {
wp_safe_redirect( home_url( '/sale/' ), 301 );
exit;
}
// Per-post redirect stored in a custom field.
if ( is_singular() ) {
$target = get_post_meta( get_queried_object_id(), 'redirect_to', true );
if ( $target ) {
wp_safe_redirect( esc_url_raw( $target ), 301 );
exit;
}
}
} );- Always call
exitafter redirecting. Otherwise WordPress keeps rendering the page. wp_redirect()sends any location and defaults to 302. Pass301explicitly.wp_safe_redirect()only allows hosts in theallowed_redirect_hostslist (the site's own host by default) and falls back to the admin URL otherwise. Use it whenever the target could come from user input, to avoid open redirects.- Redirecting on
initor in a template file is a common mistake: oninitconditional tags likeis_page()don't work yet, and in templates headers may already be sent.
Plugins and host-level tools
The Redirection plugin stores rules in the database, logs 404s, and supports regex. Yoast SEO Premium and Rank Math include redirect managers that can create a redirect automatically when you change a slug. These are ideal for editors who need to manage redirects without server access. The trade-off is that every redirect requires PHP and a database lookup, and rules are lost if the plugin is removed.
Many managed hosts (WP Engine, Kinsta, Cloudways and others) and CDNs such as Cloudflare offer redirect rules that run at the edge. They are the fastest option and survive theme or plugin changes, which makes them a good home for large migration maps and host canonicalisation.
WordPress's built-in old slug redirects
When you change the slug of a published post, WordPress stores the previous slug in the _wp_old_slug post meta. If a request 404s, wp_old_slug_redirect() looks up that meta and issues a 301 to the current URL. Since WordPress 4.9.3 it also tracks old publish dates (_wp_old_date) for date-based permalinks.
Know its limits:
- It works for posts and non-hierarchical custom post types — not for pages, terms or changes to the permalink structure.
- It only runs when the request would otherwise 404, so a new post that takes the old slug wins.
- Deleting the post or importing to another site loses the meta and the redirect.
For anything important, add an explicit redirect rather than relying on this fallback.
Avoiding chains and loops
A chain is A → B → C. Each hop adds latency, and Google follows only up to 10 hops before giving up. Chains build up over the years: HTTP → HTTPS → www → trailing slash → renamed slug. Fix them by pointing every old URL directly at the final destination, and by combining host and protocol fixes into one rule as in the .htaccess example above.
A loop is A → B → A, and browsers stop with “too many redirects”. Common causes on WordPress:
- The Site Address in Settings → General disagrees with a server rule (one adds www, the other strips it).
- SSL terminates at a proxy or Cloudflare (“Flexible” mode) while WordPress keeps redirecting to HTTPS because it sees HTTP.
- A plugin redirect and a server redirect that point in opposite directions.
After a migration, update internal links in content to the final URLs too. Redirects should catch external links and bookmarks, not your own navigation.
Regex redirects
Regular expressions let one rule handle a whole family of URLs, such as removing dates from permalinks:
# .htaccess: /2024/03/my-post/ → /my-post/
RewriteRule ^\d{4}/\d{2}/([^/]+)/?$ /$1/ [L,R=301]
# Redirection plugin (regex enabled)
# Source: ^/category/(.*)
# Target: /topics/$1- Anchor patterns with
^and$so they don't match more than intended. - In
.htaccess, the path has no leading slash; in Nginx and the Redirection plugin, it does. - Test every pattern against real URLs, including ones that should not match, before deploying. The regex tester makes this quick.
Testing with curl
Browsers cache redirects, so test from the command line. -I fetches headers only:
curl -I https://example.com/old-page/
# HTTP/2 301
# location: https://example.com/new-page/
# Follow the whole chain and show each hop
curl -sIL https://example.com/old-page/ | grep -iE '^(http|location)'
# Check the http and www variants too
curl -I http://www.example.com/old-page/You want exactly one 3xx followed by a 200, with the expected status code. Some servers respond differently to HEAD requests; if results look odd, use curl -sL -o /dev/null -w '%{http_code} %{url_effective}\n' to make a normal GET.
SEO considerations
- Redirect to the most relevant equivalent page, not the homepage.
- Keep permanent redirects in place for at least a year; leaving them indefinitely is safest because external links persist.
- Update canonical tags, XML sitemaps and internal links to use final URLs only. The canonical URL generator and the sitemap generator help here.
- Don't block redirected URLs in robots.txt; crawlers must fetch the old URL to see the redirect. See the robots.txt guide.
- After a migration, monitor Search Console's Pages report and your 404 logs for URLs you missed.