Google's guidance for moving URLs: map every old URL to its new URL, use server-side permanent redirects (301 or 308), keep them for at least a year, submit the new sitemap, and expect temporary ranking fluctuation, with small to medium sites taking a few weeks. The step to add is testing: follow every old URL and flag temporary redirects, chains, 404s and redirects to the homepage.
Key takeaways
- Map every old URL to its new equivalent. Google says it is important to map the old site's URLs to the new site's, and to use server-side permanent redirects, such as 301 or 308.
- Keep the redirects for at least a year. Google's guidance is as long as possible, generally at least one year.
- Permanent and temporary redirects are treated differently. A permanent redirect is a signal that the target should be canonical; a temporary one is not used as that signal.
- Expect fluctuation. Google says a small to medium site can take a few weeks for most pages to move, and rankings may fluctuate temporarily.
- The common failures are testable. A 302 left in place, a two-hop chain, a 404, a loop, and a redirect to the homepage all show up in one pass of a script.
- Do not block the old URLs in robots.txt. A crawler that is not allowed to fetch an old URL cannot see the redirect on it.
What Google documents
| Guidance | Status | Basis |
|---|---|---|
| Map the old site's URLs to the new site's URLs | Documented | Google's site-move guide |
| Use server-side permanent redirects (301, 308) | Documented | Same guide |
| Keep redirects as long as possible, generally at least 1 year | Documented | Same guide |
| Submit the new sitemap in Search Console | Documented | Same guide |
| The Change of Address tool is only for moving between domains or subdomains, not HTTP to HTTPS or www changes | Documented | Same guide |
| A small to medium site can take a few weeks; expect temporary fluctuation | Documented | Same guide |
| A permanent redirect is a canonical signal; a temporary one is not used as that signal | Documented | Google's redirect documentation |
| JavaScript redirects are a last resort, since rendering can fail | Documented | Same documentation |
| Redirecting removed pages to the homepage is a mistake; use the closest equivalent or a 404 | Practice | Common advice; Google's redirect page does not address it |
The last row is flagged because it is practice, not documentation, and so is the hop count: Google's redirect page does not give a limit on chains, so the sensible rule is simply to avoid them, since every hop is another request that can fail.
Building the map
- Inventory the old URLs. Combine the sitemap, the Search Console Performance export (pages with impressions), your analytics landing pages, a backlink report, and the server logs. A URL that only appears in one of those is easy to forget and still carries links or traffic.
- Map one to one. Each old URL goes to its closest equivalent on the new site. A page that no longer has an equivalent should return a 404 or 410 instead of redirecting to the homepage.
- Implement it on the server. A rules file or a redirect table at the host or CDN, not JavaScript and not a meta refresh.
- Update everything that points at the old URLs. Internal links, canonical tags and the sitemap should use the new URLs directly; redirects are for outside links and old bookmarks, not for your own navigation.
- Change as little else as possible. A move that also rewrites content and redesigns the site leaves you unable to tell what caused any ranking change.
Testing the map
Put the old URLs in a text file, one per line, optionally followed by a comma and the URL it should end up at. This script follows each one hop by hop and classifies the result:
import sys, urllib.request, urllib.parse, urllib.error
MAX_HOPS = 10
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *args, **kwargs):
return None
opener = urllib.request.build_opener(NoRedirect)
def hop(url):
req = urllib.request.Request(url, headers={'User-Agent': 'redirect-check/1.0'})
try:
with opener.open(req, timeout=20) as r:
return r.status, None
except urllib.error.HTTPError as e:
return e.code, e.headers.get('Location')
except Exception:
return 0, None
def follow(url):
chain, seen = [], set()
while len(chain) <= MAX_HOPS:
status, loc = hop(url)
chain.append((status, url))
if status in (301, 302, 303, 307, 308) and loc:
nxt = urllib.parse.urljoin(url, loc)
if nxt in seen:
return chain, 'LOOP'
seen.add(url)
url = nxt
else:
return chain, None
return chain, 'TOO MANY HOPS'
def same_page(a, b):
def n(u):
p = urllib.parse.urlparse(u)
path = p.path[:-len('index.html')] if p.path.endswith('index.html') else p.path
return (p.netloc.lower().removeprefix('www.'), path.rstrip('/'))
return n(a) == n(b)
def verdict(old, chain, problem, home, expected):
final_status, final_url = chain[-1]
redirects = chain[:-1]
if problem:
return problem
if final_status != 200:
return 'BROKEN (%s)' % final_status
if any(s in (302, 303, 307) for s, _ in redirects):
return 'TEMPORARY redirect'
if same_page(final_url, home) and not same_page(old, home):
return 'REDIRECTS TO HOME'
if expected and final_url.rstrip('/') != expected.rstrip('/'):
return 'WRONG TARGET (expected %s)' % expected
if len(redirects) >= 2:
return 'CHAIN (%d hops)' % len(redirects)
return 'ok' if redirects else 'ok (no redirect)'
home = sys.argv[2] # e.g. https://example.com/
for line in open(sys.argv[1]):
line = line.strip()
if not line or line.startswith('#'):
continue
old, _, expected = [p.strip() for p in line.partition(',')]
chain, problem = follow(old)
print('%-22s %s' % (verdict(old, chain, problem, home, expected), old))
print(' ' + ' -> '.join('%s %s' % c for c in chain))
Run it with the command below. Here is its output on a local test server with eight planted cases, one of each kind:
python3 check_redirects.py old_urls.txt https://example.com/
| Case | What happened | Verdict |
|---|---|---|
| Correct redirect | 301, then 200 | ok |
| Temporary redirect left in place | 302, then 200 | TEMPORARY redirect |
| Two-hop chain | 301, 301, then 200 | CHAIN (2 hops) |
| Page that no longer exists | 404 | BROKEN (404) |
| Old page sent to the homepage | 301 to / | REDIRECTS TO HOME |
| Redirect loop | 301 between two URLs | LOOP |
| Redirect to the wrong page | 301, but not to the expected URL | WRONG TARGET |
| URL that does not redirect | 200 | ok (no redirect) |
The same test on this site
Run against this site's live URLs on 30 October 2026, it gave these results. The host
strips .html and /index.html with permanent redirects, and sends
HTTP to HTTPS:
| URL requested | What it did | Verdict |
|---|---|---|
http:// home page | 301 to https | ok |
/seo/seo-audit-checklist.html over https | 301 to the clean URL | ok |
/hosting/index.html | 301 to /hosting/ | ok |
/seo/seo-audit-checklist.html over http:// | 301, 301, then 200 | CHAIN (2 hops) |
https://www. home page | 200, no redirect | Duplicate host |
Two findings. An old HTTP link to an article takes two hops, because the protocol fix and
the .html fix are separate rules. And the www host serves the page
itself rather than redirecting to the main domain, which leaves two hosts serving the same
pages; the canonical tags are what keep them from competing, and a redirect would be
a clearer single signal. The script's "ok (no redirect)" does not mean that is fine, only that it is not a
redirect problem. The canonical guide
covers what these mismatches do to the signals Google weighs.
What to avoid
- Blocking the old URLs in robots.txt. A crawler that cannot fetch an old URL never sees the redirect on it, the same trap as in the noindex versus disallow guide.
- Leaving 302s behind. A temporary redirect is not used as a canonical signal, so the old URL can stay in the picture.
- Redirecting everything to the homepage. The script flags it; a page with no equivalent should return a 404 or 410.
- Removing the redirects early. Google's guidance is at least a year.
- Relying on redirects for your own links. Point internal links, canonicals and the sitemap at the new URLs.
After the move
Submit the new sitemap and, only if you changed domain or subdomain, use the Change of Address tool. Then watch the Page indexing report and the Crawl stats report. If new URLs show up as Discovered, currently not indexed or Crawled, currently not indexed, those guides have the checks. For the infrastructure side of a move, see the zero-downtime VPS migration and switching hosts guides, which cover DNS and overlap windows. Expect a few weeks for a small or medium site and some temporary movement in the meantime.