How to do a 301 redirect, step by step

·4 min read

To do a 301 redirect, list every old URL with its new address, add a permanent redirect rule for each one on your server, CDN or framework, then update your internal links and sitemap so nothing points at the old URLs. Finish by checking each old URL returns one 301 straight to a page that answers 200. Most broken migrations skip the first step or the last one.

If you are still deciding whether the move is permanent, read 301 vs 302 redirects first. This post assumes the old URL is gone for good.

Step 1. Map every old URL to its new URL

A 301 redirect is only as good as the map behind it. Google's site move guide tells you to list your old URLs and decide where each one should go before you touch any config.

Pull the old URLs from your current sitemap, your analytics landing pages and the pages with backlinks. Put them in a two-column sheet:

/blog/2019/redirect-guide    /blog/redirects
/services/seo-audit          /services/audit
/old-pricing                 /pricing

Three rules for filling in the second column:

  • Send each old URL to its closest real equivalent, not the home page. Google warns that pointing many old URLs at one irrelevant page can be treated as a soft 404.
  • Point at the final URL, with the right protocol, host and trailing slash, so the redirect is one hop.
  • If nothing matches, leave the URL out and let it return 404 or 410.

Step 2. Set the 301 redirect on your server or platform

Put the rule where it runs before your page code. Google prefers server-side permanent redirects and calls client-side ones a last resort.

Nginx

Use return 301 inside the right server block. For a long map, nginx's map directive keeps the list in one place.

map $uri $new_uri {
    /old-pricing                 /pricing;
    /blog/2019/redirect-guide    /blog/redirects;
}

server {
    listen 443 ssl;
    server_name example.com;

    if ($new_uri) {
        return 301 https://example.com$new_uri;
    }
}

The rewrite module docs note that return with only a URL sends a 302, so always write the 301. The same goes for rewrite, which needs the permanent flag to send a 301.

On Apache, the rules go in .htaccess. Our .htaccess redirect guide covers Redirect, RedirectMatch and mod_rewrite.

Cloudflare

If the site is proxied through Cloudflare, you can redirect at the edge without touching the origin. In the dashboard go to Rules, choose Create rule, then Redirect Rule. Match the old URL, enter the target, pick 301 as the status code and decide whether to keep the query string. Then select Deploy.

Redirect Rules handle patterns. For hundreds of fixed old-to-new pairs, Bulk Redirects work from a list instead, but they don't support regular expressions or string replacement.

WordPress

WordPress already sends a 301 when you change a published post's slug. Its wp_old_slug_redirect function looks up the old slug and calls wp_redirect( $link, 301 ), per the WordPress code reference. For anything else, such as a deleted category or a changed permalink structure, use a redirect plugin or a server rule. Server rules are faster because PHP never loads.

Next.js and Vercel

In Next.js, add the pair to redirects() in next.config.ts. Note that permanent: true sends a 308, not a 301. On Vercel without Next.js, the redirects array in vercel.json defaults to permanent: true, which is also a 308, per the vercel.json reference.

{
  "redirects": [
    { "source": "/old-pricing", "destination": "/pricing", "statusCode": 301 },
    { "source": "/blog/2019/:slug", "destination": "/blog/:slug" }
  ]
}

Google treats 308 the same as 301, so the default is fine for search. Set statusCode: 301 only when an older client or a partner integration expects a 301.

A redirect catches old links. It shouldn't be how your own site gets around. Google's site move guide asks you to change internal links from the old URLs to the new ones.

Search your templates, navigation, footer and body copy for the old paths and replace them. A CMS search-and-replace on the database handles most of it. Then regenerate your XML sitemap so it lists only the new URLs. A sitemap full of redirecting URLs is how a page with redirect row in Search Console keeps growing after a migration.

Keep the redirects in place. Google's guide says at least a year, and longer is better for visitors who arrive from old bookmarks and backlinks.

Step 4. Verify each 301 redirect

Test with curl, not your browser. Browsers cache 301 responses, so a mistake you already fixed can keep showing.

curl -sI https://example.com/old-pricing

You want HTTP/2 301 and a location header with the final URL. Then add -L to follow the whole chain and confirm it ends on a 200 after one hop. If you see two or three hops, an older rule is in the way. Fix it as shown in the redirect chain guide.

Check the four host versions too. http://example.com, http://www.example.com, https://www.example.com and https://example.com should all reach one origin. For a full migration list, the bulk HTTP status checker shows every old URL's status and redirect target in one run.

Check a 301 redirect on your own site

Our redirects, headers and host checker follows one URL hop by hop, up to 10 redirects, and shows each hop's status code, whether it is permanent or temporary, the Location target and how long it took. It flags loops, chains, a redirect with no Location header, HTTPS falling back to HTTP and temporary redirects that change host.

The same run requests the http and https versions of your bare domain and www and reports whether they all end on one origin. It also flags an X-Robots-Tag noindex on the final page, and each finding names the change to make. A run costs 10 credits. It follows HTTP redirects only, so a meta refresh or JavaScript redirect after a 200 is not traced.

Keep reading