
How to Set Up 301 Redirects in WordPress?
URLs on a WordPress site change more often than most owners realize. A post gets a better slug, a category is renamed, a service page is merged into another, or the whole site moves from http to https or to a new domain. Every one of those changes leaves behind an old URL that search engines have indexed, other sites link to, and visitors have bookmarked. Without a redirect, all of that traffic lands on a 404 page, and the ranking signals the old URL earned are lost.
A 301 redirect tells browsers and search engines that a page has moved permanently and where it went. Setting one up in WordPress is easy. Setting them up so they stay fast, maintainable, and free of chains and loops takes a little more care.
This article covers what a 301 redirect is and how it differs from 302, 307, and 308, what WordPress already redirects for you, how to manage redirects with a plugin, how to add them in PHP, .htaccess, and Nginx, how to handle HTTPS and www canonicalization, and how to test and maintain redirects so they keep working.
What Is a 301 Redirect?
When a browser requests a URL, the server answers with an HTTP status code. A 301 status, Moved Permanently, comes with a Location header pointing to the new address. The browser follows it automatically, and search engines treat the new URL as the replacement for the old one, consolidating ranking signals onto it.
Here is what a 301 response looks like:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-page/
301 vs 302, 307, and 308
| Status | Meaning | Permanent? | Method preserved? | Typical use |
|---|---|---|---|---|
| 301 | Moved Permanently | Yes | Not guaranteed | Changed slugs, merged pages, domain moves |
| 302 | Found | No | Not guaranteed | Short-term moves, A/B tests, login redirects |
| 307 | Temporary Redirect | No | Yes | Temporary moves where POST must stay POST |
| 308 | Permanent Redirect | Yes | Yes | Permanent moves of form or API endpoints |
For ordinary page moves, 301 is the right choice. Google treats 301 and 308 the same way for indexing, and 302 and 307 as signals that the original URL should stay in the index. Use a temporary code only when the original URL really will come back.
One property of 301 matters during setup: browsers cache permanent redirects aggressively, sometimes indefinitely. If you publish a wrong 301, visitors who already followed it may keep being redirected even after you fix it. Test new rules with a 302 or in a private window, then switch to 301 once you are sure.
What WordPress Already Redirects for You
Before adding rules, know what WordPress handles on its own, so you do not duplicate it.
Changed post slugs
When you change the slug of a published post, WordPress stores the previous slug in the _wp_old_slug post meta field. If someone requests the old URL, the wp_old_slug_redirect() function finds the post and sends a 301 to the current URL. This works for posts and custom post types, and it survives multiple renames because each old slug is stored.
You can see stored old slugs with WP-CLI:
# Terminal
wp post meta list 123 --keys=_wp_old_slug
Limitations worth knowing:
- It only works while the post still exists. Deleting a post removes its old slugs.
- It does not cover pages moved to a different parent in every case, category or tag renames, or changes to your permalink structure.
- It does not apply to URLs that never belonged to a WordPress post, such as files or pages from a previous platform.
Canonical redirects
WordPress also runs redirect_canonical() on front-end requests. It normalizes many variations to a single canonical URL, for example adding or removing the trailing slash to match your permalink structure, redirecting ?p=123 to the pretty permalink, and correcting the host to match the Site Address (URL) setting. If you change your permalink structure, WordPress can often redirect old date-based or ?p= URLs on its own, but you should still test the URLs that matter most.
If you are unsure how your permalinks are built, start with what a permalink is in WordPress.
Choosing Where to Add Redirects
You can create redirects at several layers. The higher the layer, the faster the redirect, because the request never reaches PHP.
| Method | Speed | Who can manage it | Best for |
|---|---|---|---|
| Redirect plugin | Good | Editors in wp-admin | Day-to-day redirects, 404 monitoring |
| PHP in a plugin | Good | Developers | Logic-based rules, small fixed maps |
.htaccess (Apache) | Fastest | Developers with access | Site-wide patterns, HTTPS, large maps |
| Nginx config | Fastest | Server administrators | Site-wide patterns, HTTPS, domain moves |
| CDN or edge rules | Fastest | Whoever manages the CDN | Domain moves, bulk rules before the origin |
A practical split for most sites: handle protocol, host, and domain-level rules at the server or CDN, and handle individual page redirects with a plugin so editors can manage them without deployments.
Method 1: Using the Redirection Plugin
Redirection by John Godley is the most widely used free redirect manager for WordPress. It stores redirects in its own database tables, supports regular expressions, logs redirects and 404 errors, and can import and export rules.
Installing and setting up
- Go to Plugins → Add New Plugin, search for Redirection, then install and activate it.
- Go to Tools → Redirection. The first time, a setup screen walks you through basic options, including whether to monitor permalink changes and keep logs.
- Complete the setup so the plugin creates its database tables.
Adding a redirect
- On the Redirects tab, enter the old path in Source URL, for example
/old-service-page/. - Enter the new path or full URL in Target URL, for example
/services/web-design/. - Open the advanced options if you need a different HTTP code. The default is 301.
- Click Add Redirect.
Use relative paths for internal redirects. They keep working if you later move from a staging domain to production.
Regex redirects for whole sections
If you renamed a URL prefix, a single regular expression can replace hundreds of individual rules. For example, to move every post from /blog/ to /articles/:
Source URL: ^/blog/(.*)$
Target URL: /articles/$1
Regex: enabled
Anchor the pattern with ^ and $, and test it against a few real URLs before saving. A loose pattern can catch URLs you did not intend.
Using the 404 log
The 404s tab lists URLs that visitors requested but your site could not find. Review it weekly after a redesign or migration. Each entry can be turned into a redirect with a click, and grouping by URL shows which broken links get the most traffic.
Most SEO plugins have redirect managers too, including Rank Math and the premium version of Yoast SEO. Use one tool for redirects, not several, so there is one place to look when a rule misbehaves.
Method 2: Adding Redirects with PHP
If you have a small, stable list of redirects, or you need logic a plugin cannot express, add them in code. Put the code in a small custom plugin or a must-use plugin, not in a theme's functions.php, so the redirects survive theme changes.
<?php
// wp-content/mu-plugins/site-redirects.php
/**
* Plugin Name: Site Redirects
* Description: Permanent redirects for moved pages.
*/
add_action( 'template_redirect', function () {
$redirects = array(
'/old-service-page/' => '/services/web-design/',
'/about-us/team/' => '/about/',
'/2023/spring-offer/' => '/offers/',
);
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
$path = wp_parse_url( $request_uri, PHP_URL_PATH );
if ( ! is_string( $path ) ) {
return;
}
$path = trailingslashit( strtolower( $path ) );
if ( isset( $redirects[ $path ] ) ) {
$query = wp_parse_url( $request_uri, PHP_URL_QUERY );
$target = home_url( $redirects[ $path ] );
if ( $query ) {
$target .= '?' . $query;
}
wp_safe_redirect( $target, 301, 'Site Redirects' );
exit;
}
} );
How this works:
template_redirectfires after WordPress has parsed the request but before it loads a template, which is the standard hook for front-end redirects.wp_safe_redirect()only redirects to your own host or hosts allowed through theallowed_redirect_hostsfilter. If the target is not allowed, it falls back to the admin URL instead of sending visitors to an arbitrary site. Usewp_redirect()only when you deliberately redirect to an external domain you control.exitis required. Without it, WordPress keeps running and renders the page after sending the redirect header.- The query string is preserved, so campaign parameters such as
utm_sourcesurvive the redirect. - The third argument sets the
X-Redirect-Byheader, which makes it easy to see in DevTools which code issued the redirect.
Redirecting deleted posts to a related page
If you delete a post, its old slug data goes with it. To send that URL somewhere useful instead of returning a 404, add it to the map above, or catch it with a conditional:
<?php
// wp-content/mu-plugins/site-redirects.php (continued)
add_action( 'template_redirect', function () {
if ( ! is_404() ) {
return;
}
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
$path = (string) wp_parse_url( $request_uri, PHP_URL_PATH );
// Old single event pages were removed; send them to the events archive.
if ( str_starts_with( $path, '/events/' ) && '/events/' !== $path ) {
wp_safe_redirect( home_url( '/events/' ), 301 );
exit;
}
}, 20 );
Use this pattern sparingly. Redirecting many unrelated URLs to the home page or a generic archive is treated by Google as a soft 404, so it does not preserve rankings. Redirect to the most relevant replacement, or let the URL return 404 or 410 if nothing replaces it.
Method 3: Adding Redirects in .htaccess (Apache)
On Apache and LiteSpeed servers, .htaccess rules run before WordPress loads, so they are the fastest option without server-level access. The file is in your site's root folder, next to wp-config.php.
Back up .htaccess before editing. A syntax error takes the whole site down with a 500 error.
Always place your rules above the # BEGIN WordPress block. WordPress rewrites everything between its markers when you save permalinks, and anything below its catch-all rule will never run.
# .htaccess
# Single page redirects (mod_alias)
Redirect 301 /old-service-page/ https://example.com/services/web-design/
Redirect 301 /about-us/team/ https://example.com/about/
# Pattern redirects (mod_rewrite)
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^blog/(.*)$ /articles/$1 [R=301,L]
</IfModule>
# BEGIN WordPress
# (leave the WordPress-generated rules here untouched)
# END WordPress
A few rules for reliable .htaccess redirects:
Redirectmatches prefixes.Redirect 301 /blog /articlesalso redirects/blog-tips/to/articles-tips/. UseRedirectMatchwith anchors or aRewriteRulewhen you need an exact match.- Do not mix
RedirectandRewriteRulefor the same URLs. They are handled by different Apache modules that run in a different order than the file suggests, which causes confusing results. RewriteRulepatterns have no leading slash in.htaccesscontext, so write^blog/rather than^/blog/.- Query strings are passed through by
RewriteRuleunless you add theQSDflag to discard them.
Method 4: Adding Redirects in Nginx
Nginx does not read .htaccess. Add rules to the server block for your site, usually in /etc/nginx/sites-available/, then test and reload.
# /etc/nginx/sites-available/example.com
server {
listen 443 ssl;
http2 on;
server_name example.com;
# Exact-match single redirects
location = /old-service-page/ {
return 301 /services/web-design/;
}
location = /about-us/team/ {
return 301 /about/;
}
# Pattern redirect for a whole section
location ~ ^/blog/(.*)$ {
return 301 /articles/$1$is_args$args;
}
# ... the rest of your WordPress configuration
}
For long lists, a map block is cleaner and faster than dozens of location blocks:
# /etc/nginx/conf.d/redirects.conf
map $uri $redirect_target {
default "";
/old-service-page/ /services/web-design/;
/about-us/team/ /about/;
/2023/spring-offer/ /offers/;
}
# /etc/nginx/sites-available/example.com (inside the server block)
if ($redirect_target) {
return 301 $redirect_target$is_args$args;
}
Test and reload after every change:
# Terminal
sudo nginx -t && sudo systemctl reload nginx
Canonicalizing HTTPS and www
Every page on your site should have exactly one address. Requests for http:// and the non-preferred host (www or no www) should 301 straight to the canonical version in a single hop.
First, set Settings → General → WordPress Address (URL) and Site Address (URL) to the canonical form, for example https://example.com. Then enforce it at the server.
On Apache, above the WordPress block:
# .htaccess
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]
</IfModule>
If your site sits behind a load balancer or CDN that terminates SSL, %{HTTPS} is always off at the origin and this rule loops. In that case, check %{HTTP:X-Forwarded-Proto} instead, or handle HTTPS redirects at the CDN.
On Nginx, use separate server blocks so the redirect happens before any WordPress processing:
# /etc/nginx/sites-available/example.com
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name www.example.com;
# ssl_certificate lines for www.example.com
return 301 https://example.com$request_uri;
}
Redirecting an entire domain to another domain works the same way at the server level. DNS on its own cannot issue an HTTP redirect, which is explained in how to redirect a domain using DNS records.
Redirects During a Site Migration
Domain changes and redesigns create the most redirects at once. A short process prevents most problems:
- Crawl the old site before changing anything, and export every indexable URL. Add URLs from Google Search Console's pages report and your analytics landing pages, which catch URLs a crawl misses.
- Map each old URL to its closest new equivalent in a spreadsheet with two columns, old path and new path.
- Import the map into Redirection, or convert it to an Nginx
mapor.htaccessrules for large sites. - Update internal links to point to the new URLs directly. On a domain move,
wp search-replace 'https://old.example' 'https://new.example' --all-tables --dry-runshows what would change before you run it for real. - Recrawl the old URL list after launch and confirm each one returns a single 301 to a 200 page.
- Keep the redirects for at least a year, and in practice indefinitely, because external links never fully disappear.
Testing Redirects
Check redirects from the command line, which shows the exact status code and target without browser caching getting in the way:
# Terminal
curl -sI https://example.com/old-service-page/ | grep -iE '^(HTTP|location|x-redirect-by)'
To see every hop in a chain:
# Terminal
curl -sIL http://www.example.com/old-service-page/ | grep -iE '^(HTTP|location)'
The ideal result is one 301 followed by one 200. Two or more 301s in a row is a redirect chain. Each hop adds latency, and search engines may stop following long chains.
Common Problems and Fixes
- "Too many redirects" error. Two rules point at each other, or an HTTPS rule loops behind a proxy. Check each hop with
curl -sIL, look at theX-Redirect-Byheader to identify which layer sent it, and confirm the WordPress Address and Site Address settings match your server rules. - The redirect works in a private window but not in your normal browser. Your browser cached an earlier 301. Clear the cache for that site, or test with
curl. - The
.htaccessrule is ignored. It is below the WordPress block, the server does not run Apache, orAllowOverrideis disabled. Move the rule above# BEGIN WordPressand confirm the server software. - Redirect chains after a migration. Old rules point to URLs that were later redirected again. Update every rule to point straight at the final destination.
- Query strings are lost. Your PHP code or Nginx rule rebuilt the URL without them. Append
$is_args$argsin Nginx, or reattach the query in PHP as shown above. - Rules disappear after saving permalinks. They were placed inside the WordPress markers. Move them above
# BEGIN WordPress. - Everything redirects to the home page. This is treated as a soft 404 and does not pass rankings. Map each URL to a relevant replacement instead.
WordPress 301 Redirects FAQ
No. A 301 redirect to a relevant replacement passes ranking signals to the new URL. Problems come from redirect chains, loops, or redirecting many unrelated pages to the home page, which search engines treat as soft 404 errors.
Yes, for published posts. WordPress saves the previous slug in the _wp_old_slug field and redirects the old URL to the new one with a 301. This does not cover deleted posts, renamed categories, or URLs from a previous platform.
Keep them for at least a year so search engines fully transfer signals, and preferably indefinitely, because old links in emails, bookmarks, and other websites keep sending visitors for years.
Use a plugin such as Redirection for individual page redirects that editors manage, and use .htaccess or Nginx rules for site-wide patterns such as HTTPS and www canonicalization or domain moves, where speed matters most.
Both are permanent. A 308 guarantees that the request method is preserved, so a form POST stays a POST, while a 301 does not. For normal page moves, search engines treat them the same, so 301 remains the common choice.
Usually two rules point at each other, or an HTTPS rule runs behind a proxy that terminates SSL so the origin never sees HTTPS. Trace each hop with curl, check the X-Redirect-By header, and remove or fix the conflicting rule.
Conclusion
301 redirects protect the traffic and search visibility your old URLs have earned. WordPress already handles changed post slugs and many canonical variations, so start by knowing what it covers. For everything else, use a plugin such as Redirection for page-level rules that editors manage, PHP for small code-managed maps or logic, and .htaccess or Nginx for site-wide rules where speed matters.
Whatever method you choose, keep rules in one place, point every redirect straight at its final destination, preserve query strings, and test with curl so browser caching does not mislead you. Check your 404 log regularly after any URL change, and you will catch broken links before they cost you visitors.
Here are some useful references for going deeper on redirects in WordPress:
- WordPress Developer Resources: wp_safe_redirect() — function reference, including the allowed hosts check.
- WordPress Developer Resources: template_redirect — the hook used for front-end redirects.
- WordPress.org Plugins: Redirection — redirect management, 404 logging, and import and export.
- Google Search Central: Redirects and Google Search — how Google treats permanent and temporary redirects.
- Apache HTTP Server Docs: mod_rewrite — the full RewriteRule and RewriteCond reference.


