Skip to content

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:

MethodRunsGood for
Redirect pluginIn PHP, after WordPress has loadedEditors without server access, single URLs, 404 monitoring
.htaccess / nginx configIn the web server, before PHP startsWhole domains, HTTP to HTTPS, www, large rule sets
PHP (wp_redirect())In your theme or plugin codeLogic-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 .htaccess or 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=123 to 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:

  1. Pass the status code. Both functions default to 302. For a permanent move, pass 301 as the second argument.
  2. Call exit. The functions only set the Location header; they don't stop execution. Without exit, WordPress keeps rendering the page.
  3. Prefer wp_safe_redirect(). It only allows the site's own host and hosts added via the allowed_redirect_hosts filter, 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 .htaccess on Apache, to the nginx configuration or use wp_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 302 by default. Pass the status as the second argument, e.g. wp_safe_redirect($url, 301);, and always call exit; right after it.

Why does my WordPress redirect not work?

The most common causes are a page cache serving an old copy, .htaccess rules placed below the WordPress block, or a missing exit after wp_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 on X-Forwarded-Proto in wp-config.php, or use "Full (strict)" SSL on Cloudflare. Also check that the site URL and your server rules agree on www and HTTPS.

  • .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.