Redirects in WordPress
WordPress gives you several ways to redirect a URL: a plugin, a rule in .htaccess or a few lines of PHP. This guide shows when to use which, and how to avoid the typical pitfalls with caching and HTTPS.
Which method should you use?
All methods below can send a proper 301 or 302 response. The difference is where the redirect happens and who maintains it:
| Method | Runs | Good for |
|---|---|---|
| Redirect plugin | In PHP, after WordPress has loaded | Editors without server access, single URLs, 404 monitoring |
.htaccess / nginx config | In the web server, before PHP starts | Whole domains, HTTP to HTTPS, www, large rule sets |
PHP (wp_redirect()) | In your theme or plugin code | Logic-based redirects, e.g. depending on user role or post meta |
Server-level redirects are the fastest because WordPress never boots. Plugin redirects are the most convenient. Pick one place for each type of rule, so you don't end up with the same URL redirected twice in two different layers.
Redirect plugins
Three widely used plugins cover most needs. They all store rules in the database and send the redirect from PHP:
- Redirection (free): a dedicated redirect manager. You can choose the status code per rule, use regular expressions, match on login status, browser or referrer, and log 404 errors so you can see which old URLs still get traffic. It can also export rules to
.htaccessor nginx format. - Yoast SEO Premium: the redirect manager is part of the paid version. When you change a post slug or delete a post, it offers to create a redirect right away. Rules can be stored in the database or written to a server config file.
- Rank Math: the free version includes a Redirections module with 301, 302, 307, 410 and 451 responses, regex support and optional auto-redirects when a slug changes.
If you already use Yoast or Rank Math for SEO, their built-in manager is usually enough. If you need detailed logs or conditions, Redirection is the more specialized tool. Avoid running two redirect managers at the same time.
What WordPress does on its own
WordPress already redirects some URLs without any plugin:
- When you change the slug of a published post, WordPress remembers the old slug and redirects it to the new URL with a 301 (the
wp_old_slug_redirect()function). This does not apply to pages in every case, and it does not cover changes to the permalink structure. - The canonical redirect (
redirect_canonical()) sends?p=123to the pretty permalink, adds or removes the trailing slash according to your structure and fixes the host name if it doesn't match the site URL.
Changing the permalink structure
Changing Settings → Permalinks, for example from /2024/05/my-post/ to /my-post/, changes every post URL at once. WordPress tries to guess the right post for some old URLs, but you shouldn't rely on that. Add an explicit pattern redirect instead. In .htaccess it looks like this:
RewriteEngine On
RewriteRule ^[0-9]{4}/[0-9]{2}/([^/]+)/?$ /$1/ [R=301,L]
In the Redirection plugin you can enter the same pattern as a regex rule: source ^/\d{4}/\d{2}/([^/]+)/?$, target /$1/. Afterwards, test a few old URLs with the redirect checker and make sure each one lands on the new URL in a single hop.
Redirects in .htaccess
On Apache hosting, WordPress writes its own block into .htaccess. Put your rules above # BEGIN WordPress: WordPress rewrites everything between the markers when you save the permalink settings, and its catch-all rule sends every request to index.php, so rules below it may never be reached.
# Custom redirects
RewriteEngine On
RewriteRule ^old-page/?$ /new-page/ [R=301,L]
RewriteRule ^shop/(.*)$ https://shop.example.com/$1 [R=301,L]
# 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
More patterns are in the .htaccess redirect guide, and the .htaccess generator builds the rules for you. On nginx there is no .htaccess; use return 301 in the server block as described in the nginx guide.
Redirects with PHP: wp_redirect() and wp_safe_redirect()
For logic-based redirects, use the template_redirect hook in a small plugin or your theme's functions.php. It runs after WordPress has figured out which content is requested, but before any output is sent.
add_action('template_redirect', function () {
if (is_page('old-contact')) {
wp_safe_redirect(home_url('/contact/'), 301);
exit;
}
});
Three details matter:
- Pass the status code. Both functions default to
302. For a permanent move, pass301as the second argument. - Call
exit. The functions only set theLocationheader; they don't stop execution. Withoutexit, WordPress keeps rendering the page. - Prefer
wp_safe_redirect(). It only allows the site's own host and hosts added via theallowed_redirect_hostsfilter, which protects you from open redirects when the target comes from user input.wp_redirect()accepts any URL.
To redirect to an external domain with wp_safe_redirect(), allow it first:
add_filter('allowed_redirect_hosts', function (array $hosts): array {
$hosts[] = 'shop.example.com';
return $hosts;
});
Changing the site URL or moving to a new domain
The site URL is set in Settings → General (WordPress Address and Site Address) or, taking precedence, in wp-config.php:
define('WP_HOME', 'https://example.com');
define('WP_SITEURL', 'https://example.com');
Changing it does not update URLs stored in post content, menus or widgets. Replace them in the database, for example with WP-CLI (run with --dry-run first and make a backup):
wp search-replace 'https://old.example.com' 'https://example.com' --skip-columns=guid
Then redirect the old domain with a 301 at server level, path by path, so that old.example.com/blog/post/ goes to example.com/blog/post/ and not to the home page. The domain migration checklist covers the full process, including Search Console.
Common pitfalls
Caching plugins
Page caches such as WP Super Cache, W3 Total Cache or WP Rocket, and server caches like Varnish or a CDN, may serve a stored copy of a page before PHP runs. A redirect you add in a plugin is then ignored until the cache for that URL is cleared. Purge the cache after adding redirects, and check the response headers in the checker: if you still see a 200 with a cache header, the old copy is being served.
Redirect loops with SSL behind a proxy
When a load balancer, reverse proxy or Cloudflare terminates HTTPS and talks to your server over HTTP, WordPress thinks the request is insecure. If the site URL starts with https://, WordPress redirects to HTTPS, the proxy sends the next request over HTTP again, and you get an endless loop (ERR_TOO_MANY_REDIRECTS). Tell WordPress about the original protocol in wp-config.php, above the line /* That's all, stop editing! */:
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
$_SERVER['HTTPS'] = 'on';
}
Only do this if your proxy sets that header reliably. With Cloudflare, the cleaner fix is to switch the SSL/TLS mode from "Flexible" to "Full (strict)" and install a certificate on the origin. See the HTTP to HTTPS guide and the Cloudflare guide.
Chains after several changes
If a post has been renamed twice, you may have /a/ → /b/ → /c/. Update old rules so they point straight to the final URL. How to find and fix them is explained in redirect chains.
Frequently asked questions
- Do I need a plugin for redirects in WordPress?
No. You can add rules to
.htaccesson Apache, to the nginx configuration or usewp_safe_redirect()in PHP. A plugin is convenient if editors without server access need to manage redirects or if you want a 404 log.- Does wp_redirect() send a 301 or a 302?
A
302by default. Pass the status as the second argument, e.g.wp_safe_redirect($url, 301);, and always callexit;right after it.- Why does my WordPress redirect not work?
The most common causes are a page cache serving an old copy,
.htaccessrules placed below the WordPress block, or a missingexitafterwp_redirect(). Your browser may also have cached an older 301. Test the URL with the redirect checker, which does not use your browser cache.- How do I fix ERR_TOO_MANY_REDIRECTS in WordPress?
Usually WordPress does not detect HTTPS because a proxy or CDN terminates it. Set
$_SERVER['HTTPS'] = 'on'based onX-Forwarded-Protoinwp-config.php, or use "Full (strict)" SSL on Cloudflare. Also check that the site URL and your server rules agree on www and HTTPS.
Related guides
-
.htaccess redirects on Apache
Everything you need to redirect pages and whole domains with .htaccess on Apache: mod_alias vs. mod_rewrite, copy-ready rules for the common cases, and how to avoid loops and chains.
-
nginx redirects
Copy-ready nginx configuration for 301 redirects: single pages, patterns, whole domains, HTTPS and www in a single hop, and hundreds of URLs with map.
-
Redirects on Cloudflare
Cloudflare can answer redirects at the edge before a request ever reaches your server. Here is how Single Redirects, Bulk Redirects and Always Use HTTPS work, and how to keep them from fighting with your origin.
-
Redirects in Next.js
Next.js has four places where you can redirect: the config file, middleware, server code in the App Router and the trailingSlash option. Each one sends different status codes by default, so it pays to know which is which.
-
Redirects on Vercel and Netlify
On Vercel and Netlify you don't touch a web server config. Redirects live in a file in your repository or in the dashboard, and the platform's edge network sends them. Here is how both work and where they differ.
-
Redirects on Microsoft IIS
IIS gives you two ways to redirect: the built-in HTTP Redirect feature and the URL Rewrite module. Here is when to use which, with web.config examples you can copy.