Redirect Map for Website Migration: Build, Test and Verify It

Crawvix Editorial Team · Published November 4, 2026 · Last updated November 4, 2026 · 11 min read

A spreadsheet grid mapping old URLs to new ones with arrows on each row and one ticked row.

When a site changes URLs, through a redesign, a new platform or a new domain, every old address that people have bookmarked, linked to or found in search results needs somewhere to go. A redirect map decides where: a spreadsheet with one row per old URL and a column for the single page that replaces it. The server rules, tests and after-launch checks all come from that sheet, so its mistakes become production mistakes.

This guide is about the map itself: how to collect the old URLs, decide what happens to each one, write the rows so they cannot create chains or loops, turn them into server rules, test every row in bulk and check the result after launch. It is for owners, marketers and developers running a migration, and it complements rather than repeats the project-level Website Migration SEO Checklist. You will end with a tested sheet, a reusable test script and a record of what happened to each old URL.

Short answer: A redirect map for website migration is a spreadsheet pairing every old URL with the one new URL that replaces it, plus the status code to return. Build it from sitemaps, logs and a crawl, map each URL to its closest equivalent, flatten chains, test every row before launch, then recheck after launch.

Before you begin

You need the list sources (XML sitemaps, server logs, your CMS export, analytics, Search Console), a crawler or script that can request URLs, a staging copy of the new site, and access to where the redirects will live: server configuration, a CDN rule set or a CMS plugin. Allow a day or two for a small site; the mapping work is mostly human judgment.

The risk is launching the sheet unchecked. Keep a dated copy of each version. If the migration changes domains, read Change Domain Name Without Losing SEO first, because a domain move adds Search Console steps that this guide does not cover.

What columns does a redirect map need?

Keep the structure plain so that a script, a developer and a reviewer can all read it:

Column Purpose
old_url The full address that exists today, exactly as a client would request it.
new_url The one final address, which must itself return 200. Blank for retired pages.
status The code to return: 301 or 308 for moves, 410 or 404 for retired pages.
rule pattern if a general rule covers it, exception if it needs its own row.
priority High for pages with links, traffic or revenue. Decides what you test first.
notes Why it maps this way, or who agreed to retire it.

A few sample rows (shown as paths on example.com):

old_url new_url status rule priority
/blog/2023/05/dns-basics /guides/dns-basics 301 pattern high
/pricing-old /pricing 301 exception high
/team/jane-doe /about 301 exception low
/spring-sale-2022 (blank) 410 exception low

Step-by-step instructions

Step 1: Collect every old URL

Google's site-move guidance lists the places to look: sitemaps, your top-traffic pages, server logs, your CMS and Search Console's links report. It also says to include images, videos, JavaScript and CSS files. Combine the lists into one column, remove exact duplicates, and decide up front how you will treat variants:

  • Protocol and host: list the final canonical form, and handle http and www variants with one general rule so they do not become a second hop.
  • Trailing slash and letter case: decide the rule (see Trailing Slash vs No Trailing Slash) and write it down.
  • Query strings: decide which parameters change the page and which are tracking noise (see URL Parameters and SEO).

What success looks like: One sheet with a single row per distinct old URL, and a written rule for every variant type.

Step 2: Decide the fate of each URL

Not every old URL has a new home. Give each row one of four outcomes:

Outcome When What to put in the sheet
Moves A matching page exists on the new site. The new URL and status 301 or 308.
Consolidates Several old pages are now one page. The same new_url on each row.
Retires The content is intentionally gone. Blank new_url, status 404 or 410.
Stays The URL does not change. Leave it out; do not redirect it to itself.

Google's guide says to redirect each old URL to its final destination, not to send many old URLs to an unrelated page such as the new home page, and to return 404 or 410 for content you are not moving. 404 vs 410 explains the difference between the two retire codes, and Content Pruning helps with the retire decisions.

What success looks like: Every row has an outcome, and the "retire" list has been approved by whoever owns the content.

Step 3: Match each URL to its closest equivalent

Work from the highest priority down. For each old page, find the new page that answers the same need, open both, and confirm the purpose matches. Where the migration follows a pattern, such as /blog/YYYY/MM/slug becoming /guides/slug, write the pattern once, generate the rows from it, and review a sample. Everything the pattern does not fit becomes an exception row. Keep patterns and exceptions in separate tabs so you can see how much of the map depends on judgment.

For consolidation, send each old page to the page that now holds its content, not to a general index. A redirect that lands on something unrelated is a poor experience even when the status code is right.

What success looks like: Each high-priority row has been opened and compared by a person, and the pattern rules have been spot-checked.

Step 4: Flatten the map so there are no chains or loops

The map must send each old URL to a final answer in one step. Google's guidance says Googlebot can follow up to 10 hops but advises keeping chains short, with no more than three and fewer than five hops. Aim for one. Three checks catch most problems in a spreadsheet:

  1. Duplicates in old_url: a URL listed twice may have two destinations. Use a duplicate-highlighting rule or COUNTIF(A:A, A2) > 1.
  2. A new_url that is also an old_url: COUNTIF(A:A, B2) > 0 is true when a destination is itself redirected. Replace it with that row's destination.
  3. A new_url equal to its old_url: that is a loop.

Then check that every new_url really returns 200 on the staging or live new site. Redirecting to a page that redirects or errors only moves the problem. For the chain concept in detail see Redirect Chains, Loops, and Incorrect HTTP Status Codes.

What success looks like: The three formulas return no flagged rows, and a destination check shows 200 for each new_url.

Step 5: Turn the sheet into server rules

Google calls server-side redirects the most reliable method. Large maps work better as a lookup file than as thousands of individual rules. Keep the CSV as the master and regenerate the server file from it, so the sheet and the server cannot drift apart.

nginx reads a map file inside the http context. Its exact-string keys are matched ignoring case, and the $uri variable is the normalized, decoded path without the query string, so write keys as plain decoded paths and add the query string back yourself if it matters:

# http { } context
map $uri $redirect_target {
    default "";
    include /etc/nginx/redirect-map.conf;   # one line per row: /old-path  /new-path;
}

# server { } context
if ($redirect_target) {
    return 301 https://example.com$redirect_target;
}

Apache can read a text map, but RewriteMap must be declared in the server or virtual-host configuration, not in .htaccess; the map file holds one key value pair per line:

RewriteMap redirects "txt:/etc/apache2/redirect-map.txt"
RewriteCond "${redirects:%{REQUEST_URI}|NONE}" "!=NONE"
RewriteRule "^" "${redirects:%{REQUEST_URI}}" [R=301,L]

Rows that return 410 need their own rule, such as the [G] flag in Apache or return 410; in nginx. Platform-specific details are in nginx and Apache Redirects for SEO and ASP.NET Core Redirects and HTTPS. CMS and CDN redirect tools work too if they accept bulk import and let you choose the status code.

What success looks like: The server file is generated from the sheet, loads without errors on staging, and a handful of rows redirect by hand.

Step 6: Bulk-test every row on staging

Test the whole sheet, not a sample. The script below reads a CSV with the columns old_url,new_url,status (no commas inside URLs) and, for each row, checks the first-hop status, where it points, the final status and the number of hops:

#!/usr/bin/env bash
# Usage: ./check-redirects.sh redirect-map.csv
while IFS=, read -r old new want; do
  [ "$old" = "old_url" ] && continue
  read -r code target < <(curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' "$old")
  read -r final hops last < <(curl -sL -o /dev/null -w '%{http_code} %{num_redirects} %{url_effective}\n' "$old")
  problems=""
  [ "$code" != "$want" ] && problems+=" first-hop=$code(want $want)"
  [ "$want" = 301 ] || [ "$want" = 308 ] && [ "$target" != "$new" ] && problems+=" points-to=$target"
  [ "$final" != 200 ] && [ "$want" != 410 ] && [ "$want" != 404 ] && problems+=" final=$final"
  [ "$hops" -gt 1 ] 2>/dev/null && problems+=" hops=$hops"
  if [ -z "$problems" ]; then echo "OK   $old"; else echo "FAIL $old$problems"; fi
done < "$1"

Use full URLs with the staging host in the sheet for this run. Also test by hand the edge cases the sheet may not list: the same path in a different letter case, with and without a trailing slash, with a tracking parameter, over http, and a missing page that should still return a proper 404.

What success looks like: Every line prints OK, and the failures you did find are fixed in the sheet, not only on the server.

Step 7: Launch, then update everything that points to old URLs

Turn on the redirects at the same moment the new site goes live. Google's site-move guide then asks you to confirm that canonical tags and any noindex rules point at the right new URLs, to update hreflang annotations and internal links to the new addresses, and to submit a sitemap of the new URLs. Internal links should not rely on the redirect: Crawvix's link checks flag internal links that still pass through a redirect and chains longer than two hops, which is a quick way to find leftovers after a crawl.

The Search Console Change of Address tool is only for moving a domain or subdomain. It is not for HTTP to HTTPS, www changes or path changes, and its effect lasts 180 days from the day you start.

What success looks like: Production redirects respond as tested, and no template, canonical or sitemap still names an old URL.

Step 8: Verify on production, then keep the redirects

Rerun the script against production on launch day and again after a week. Check the server logs for 404s on old URLs missing from the map, and add rows for any that matter. In Search Console, a "Page with redirect" status for an old URL is expected when the redirect is intended, and Google says warnings in an old sitemap about redirected URLs are normal. Fix a "Redirect error" or a spike in "Not found" entries that point to redirects landing on nonexistent pages.

Keep the redirects for at least one year, the minimum in Google's site-move guidance, and longer if old URLs still receive visits. The same guidance expects temporary fluctuations after a move and gives no fixed timeline. Keep the final sheet as your record of what happened to every old URL.

What success looks like: The post-launch test passes on production, the log review is done, and the retention date is in someone's calendar.

Final verification checklist

  • The sheet has one row per distinct old URL, and no duplicate old_url values.
  • No new_url appears in the old_url column, and none equals its own old_url.
  • Every new_url returns 200 directly.
  • Retired pages are marked 404 or 410 and approved.
  • The server rules were generated from the sheet and the bulk test printed OK for every row.
  • Case, trailing slash, query string and protocol variants were tested by hand.
  • Canonicals, hreflang, internal links and the sitemap use the new URLs.
  • The post-launch test and a log review found no unmapped old URLs that matter.
  • The redirects will stay in place for at least a year.

Common mistakes to avoid

  • Mapping everything to the home page. Match each old page to its closest equivalent; retire the rest with 404 or 410.
  • Mapping to a URL that redirects again. Flatten with the COUNTIF check.
  • Testing a sample. Chains and typos hide in the rows nobody opened.
  • Using 302 for permanent moves. See 301 vs 302 Redirects.
  • Forgetting that nginx map keys ignore case. Two old URLs differing only by case cannot be told apart.
  • Treating the redirect as the fix for internal links. Update the links themselves.
  • Removing redirects after a few weeks. Keep them for at least a year.

Frequently asked questions

Can I use one pattern rule instead of a map?

Yes, when old and new URLs follow a regular transformation. Keep exceptions in a list and test a sample of pattern results by script.

Should I redirect to the home page when there is no equivalent?

Google's guidance advises against sending many old URLs to an unrelated page, and says to return 404 or 410 for content you are not moving.

What if I have thousands of URLs?

Use patterns for the regular parts and a lookup file for exceptions, both generated from the sheet, and test every row by script.

Do I need a map if only the domain changes?

If paths stay identical, one rule can send each path to the same path on the new host. A sheet is still useful for exceptions and for testing.

Do redirects keep my search visibility?

No promise is possible. Permanent redirects are a strong signal about the new URL, but Google gives no timeline, and fluctuations after a move are normal.

Official references


Next step: Try the free website sample to see how your own site does on a few of these checks. This guide provides technical information, not a guarantee of rankings, traffic or index inclusion.

In this category

All 14 Launch guides