Use a two-column CSV of old and expected new URLs, request each old URL, follow its redirects, and compare the final URL with the map. The Python checker below reports the HTTP status, redirect chain, destination, and any errors or mismatches. Run it against a faithful staging setup before launch, then against production afterward; a matching URL map is useful evidence, not proof that the whole migration or search indexing is complete.
What the redirect-map test checks
For every row, the script requests the old URL and follows redirects. It passes a mapping only when the request succeeds, the final response is not an HTTP error, and the final URL matches the expected destination. It also reports the response status and number of redirect hops so you can review temporary or unexpected responses and chains.
Google Search Central recommends testing individual URLs with URL Inspection, or using command-line tools or scripts for larger groups. It does not prescribe Python or a specific HTTP library. This script is intended for a small map you can run repeatedly.
Prepare a useful redirect map
The checker can only validate URLs in the CSV. Build the inventory from sources that reflect the URLs people and crawlers may still use: old sitemaps, analytics, server logs, CMS exports, and pages with inbound links. Include moved assets such as images, videos, scripts, and stylesheets when they matter to the site.
#1 Best Overall
Map each old URL to the most relevant new destination. Avoid sending many unrelated old URLs to one generic page: Google cautions that this can confuse visitors or result in a soft 404. For a permanent move, use a server-side permanent redirect such as 301 or 308 where possible. Temporary status codes express different intent, so review them rather than treating every response as equivalent.
Create the CSV
Save a UTF-8 file named redirects.csv with these exact column names. Use absolute URLs, including the scheme and hostname:
Rank #2
old_url,expected_url
https://www.example.com/old-page,https://www.example.com/new-page
https://www.example.com/old-photo.jpg,https://www.example.com/images/new-photo.jpg
Each expected_url should be the destination you expect a browser to reach after all redirects. If your site intentionally normalizes hostnames, trailing slashes, or URL casing, put the final canonical form in this column.
Install the dependency and save the checker
The example uses Python 3 and the third-party requests package. Install it in the Python environment you will use to run the script:
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →python -m pip install requests
Save the following as check_redirects.py in the same folder as redirects.csv:
import csv
import sys
from urllib.parse import urlsplit, urlunsplit
import requests
CSV_FILE = "redirects.csv"
TIMEOUT_SECONDS = 15
MAX_REDIRECTS = 10
def normalize_url(url):
"""Ignore URL fragments; servers do not receive them in HTTP requests."""
parts = urlsplit(url.strip())
return urlunsplit((parts.scheme.lower(), parts.netloc.lower(), parts.path or "/", parts.query, ""))
def main():
try:
with open(CSV_FILE, newline="", encoding="utf-8-sig") as csvfile:
rows = list(csv.DictReader(csvfile))
except OSError as exc:
print(f"Could not read {CSV_FILE}: {exc}", file=sys.stderr)
return 2
required = {"old_url", "expected_url"}
if not rows or not required.issubset(rows[0].keys()):
print("CSV must contain rows and the columns old_url, expected_url.", file=sys.stderr)
return 2
failures = 0
print("statusthopstresulttold_urltfinal_urltexpected_urltdiagnostic")
for row in rows:
old_url = (row.get("old_url") or "").strip()
expected_url = (row.get("expected_url") or "").strip()
if not old_url or not expected_url:
failures += 1
print(f"-t-tFAILt{old_url}t-t{expected_url}tMissing URL")
continue
try:
response = requests.get(
old_url,
allow_redirects=True,
timeout=TIMEOUT_SECONDS,
max_redirects=MAX_REDIRECTS,
headers={"User-Agent": "RedirectMapChecker/1.0"},
)
final_url = response.url
hops = len(response.history)
diagnostics = []
if response.status_code >= 400:
diagnostics.append("final response is an HTTP error")
if normalize_url(final_url) != normalize_url(expected_url):
diagnostics.append("final URL does not match expected URL")
if hops > 1:
diagnostics.append("redirect chain; prefer a direct redirect")
if response.status_code not in (200, 301, 302, 303, 307, 308):
diagnostics.append("review unexpected status")
passed = response.status_code < 400 and normalize_url(final_url) == normalize_url(expected_url)
result = "PASS" if passed else "FAIL"
if not passed:
failures += 1
diagnostic = "; ".join(diagnostics) if diagnostics else "OK"
print(f"{response.status_code}t{hops}t{result}t{old_url}t{final_url}t{expected_url}t{diagnostic}")
except requests.RequestException as exc:
failures += 1
print(f"-t-tFAILt{old_url}t-t{expected_url}t{type(exc).__name__}: {exc}")
print(f"n{len(rows)} mappings checked; {failures} failed.")
return 1 if failures else 0
if __name__ == "__main__":
raise SystemExit(main())
The output is tab-separated so it can be read in a terminal or copied into a spreadsheet. A row can show PASS while still flagging a multi-hop chain or a status for review: the pass/fail result tests the final destination and whether the final response is an HTTP error. The diagnostic column surfaces additional cleanup work. A request exception, mismatch, or final HTTP error fails the row.
The hop count is the number of followed redirects recorded by requests. One hop means the old URL redirected once; zero means it returned without redirecting. Google advises avoiding chains; where they cannot be avoided, keep them low—ideally no more than three and fewer than five—because chains add latency and may not work for all user agents.
Run the test before launch
- Deploy the planned redirect rules to staging or another test environment.
- Confirm that it faithfully represents production behavior. If the staging hostname or configuration changes the redirect targets, the result will not validate production rules.
- Run
python check_redirects.pyfrom the folder containing the script and CSV. - Review every failed row and every diagnostic. Fix broken rules, unexpected final URLs, HTTP errors, and avoidable chains, then run the checker again.
The output includes a custom user-agent. If your staging setup treats that differently from ordinary visitors or crawlers, investigate the discrepancy rather than assuming the test reflects their experience.
Best Value
Run it again after launch
Once the production rules are live, run the same command against the production URLs. This catches differences between staging and live configuration, including rules that were omitted or altered during deployment. Keep the report as a record of the tested map and use it to repair server rules or correct mistaken mappings.
For a large migration, Google recommends checking individual URLs with Search Console's URL Inspection tool or testing groups with scripts or command-line tools. A crawler or redirect-audit service may be useful when you need broader discovery, exportable results, or chain reporting across many more URLs; choose based on those capabilities and the size of your site rather than assuming a product is required for a small map.
What a passing report does not establish
- It verifies the tested old URLs' HTTP behavior at the time of the request. It does not verify that the new page is relevant or that the content is correct.
- It does not check canonical annotations, internal links, or sitemap contents. Update those to point to the new URLs as part of the migration.
- It does not show that Google has crawled or indexed every old and new URL. Google says it processes a move URL by URL as Googlebot visits the old and new URLs; timing depends in part on URL volume and server speed.
Keep monitoring after the redirects work
Update internal links and sitemaps to use the new URLs, and monitor traffic, Search Console reports, indexing, and crawl errors for both old and new URLs. Google advises keeping permanent redirects in place as long as possible, generally for at least one year. Search visibility can fluctuate temporarily during a move; Google says that medium-sized sites may take a few weeks or more for most URLs to shift in search results, while larger sites can take longer. There is no fixed recovery date.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
Recommended Free Tools




