You migrated from old.com to new.com. You set up a redirect from old.com to new.com, tested the homepage, and it worked. Two weeks later Search Console shows hundreds of 404s on the new domain, traffic dropped, and external backlinks now go nowhere.
Fastest fix: you almost certainly redirected the root instead of every URL path. A root-only redirect sends old.com/blog/article to new.com/ (or to a path that no longer exists), which 404s. You need a path-preserving catch-all plus explicit rules for any URLs whose structure changed, all returning a 301, all deployed on the old domain’s hosting. Run curl -sI "https://old.com/some-deep-url" and check the Location: header: if it points at the bare root or a dead path, that is your bug.
This guide walks through building a complete redirect map and verifying it after deploy.
Which bucket are you in?
Run one curl against a real deep URL from the old site and read the result:
curl -sI https://old.com/blog/real-post shows | Diagnosis | Jump to |
|---|---|---|
301 + Location: https://new.com/ (bare root) | Only the root is redirected | Cause 1 |
301 + Location: https://new.com/blog/real-post but that 404s on new site | URL structure changed, map didn’t follow | Cause 2 |
200 (no redirect at all) on some paths only | Pattern/wildcard rule isn’t matching | Cause 3, 4 |
TLS/cert error on the https:// request | Old domain has no valid cert | Cause 6 |
Connection refused / timeout | Old hosting is fully decommissioned | Cause 5 |
Common causes
Ordered by hit rate, highest first.
1. Only the root domain is redirected, deep URLs unhandled
old.com to new.com redirects the bare root. old.com/blog/article-name follows the redirect and lands at new.com/ (most domain-forwarding rules drop the path), or at new.com/blog/article-name which may not exist. Either way: 404.
How to spot it:
curl -sI "https://old.com/some-deep-url" | head -3
If you see Location: https://new.com/, only the root is redirected. It should read Location: https://new.com/some-deep-url.
2. URL structure changed but the redirect map doesn’t follow
You restructured from /blog/article-name to /articles/article-name during the migration. Even with a full path-preserving redirect, requests land at the old path on the new domain, which 404s.
How to spot it: check the redirect destinations. If they preserve old paths but the old paths don’t exist on the new site, you need a path rewrite as part of the redirect (see Cause 2’s rule in the Vercel/Netlify examples below).
3. Wildcard / pattern redirect not working
You wrote a pattern redirect but the platform doesn’t support that exact syntax, so it silently falls back to no redirect. The three major static hosts use different capture syntax: Vercel uses :slug and :path*, Netlify uses * and :splat, Firebase uses :slug* glob captures. Copy-pasting a rule from one platform into another is a common cause of silent failure.
How to spot it: curl a URL that should match the pattern. A 200 (instead of 301) means the pattern never matched.
4. Hosting platform redirect-count limit (as of June 2026)
Static hosts cap how many redirect rules a config file can hold:
| Platform | File | Limit (June 2026) | Beyond the limit |
|---|---|---|---|
| Vercel | vercel.json redirects | 1,024 rules | Use Bulk Redirects (up to 1,000,000/project) or Edge Middleware |
| Vercel | total routes/deploy | 2,048 routes | Move static redirects to Edge Middleware / Bulk Redirects |
| Netlify | _redirects / netlify.toml | No hard rule count; deploy fails if serialized output is too large | Use wildcards/placeholders; Netlify advises this past ~10,000 rules; Edge Functions for complex cases |
| Firebase | firebase.json redirects | Practical glob limit | Use glob captures instead of one rule per URL |
The fix is almost never “add more rules” — it is to collapse hundreds of 1:1 rules into a handful of wildcard captures plus a catch-all.
How to spot it: count the rules in your config. On Vercel, anything past 1,024 in vercel.json is dropped.
5. Old domain’s hosting is fully decommissioned
If you completely shut down old.com’s hosting (not just changed DNS), the redirect can never fire. Old URLs return connection refused or a timeout, and Google can’t follow a redirect that doesn’t respond.
How to spot it: curl -I https://old.com/anything returns a network error. You need to keep a redirect-only deployment running.
6. HTTPS old domain has no certificate
You set up an HTTP redirect, but an HTTPS request to old.com fails with a cert error before the redirect can fire. Modern browsers default to HTTPS, and any domain that ever sent an HSTS header forces HTTPS for the max-age window, so the HTTP version is unreachable.
How to spot it: curl -I https://old.com/ returns a cert error while curl -I http://old.com/ works. The redirect host must serve a valid cert for the old domain (Vercel, Netlify, and Firebase all auto-provision one once DNS points at them).
Shortest path to fix
Step 1: Export ALL old URLs
Sources, roughly in order of completeness:
old.com/sitemap.xml(if the old site or an archive still serves it)- Search Console (old domain still verified) → Performance → Pages → Export
- Server access logs from the old hosting (the only source that captures URLs Google never indexed but humans still hit)
- Wayback Machine for unindexed pages
- Backlink data (Ahrefs, Majestic, or Search Console Links → Top linked pages) — these are the URLs whose link equity you most need to preserve
Deduplicate into a single list of paths.
Step 2: Build the redirect map
For each old URL, decide its destination:
- Direct equivalent — map it:
old.com/blog/footonew.com/articles/foo - No exact match — redirect to the nearest category:
old.com/topic-x/*tonew.com/category/x/ - Truly orphaned — redirect to the homepage only as a last resort (a homepage redirect passes far less link equity than a topical one, and Google may treat a mass of root-redirects as soft-404s)
A CSV keeps the map reviewable and in source control:
old_path,new_path
/blog/article-name,/articles/article-name
/topics/ai,/category/ai
/old-broken-page,/
Step 3: Convert to platform redirect syntax
Specific rules first, catch-all last — both Vercel and Firebase apply the first matching rule, so order matters.
Vercel — vercel.json (:slug captures one segment, :path* captures the rest):
{
"redirects": [
{ "source": "/blog/:slug", "destination": "https://new.com/articles/:slug", "permanent": true },
{ "source": "/topics/:topic", "destination": "https://new.com/category/:topic", "permanent": true },
{ "source": "/:path*", "destination": "https://new.com/:path*", "permanent": true }
]
}
"permanent": true emits a 308 (the modern permanent redirect that preserves the request method); Google treats 301 and 308 the same for site moves. Use "permanent": false only for temporary moves.
Netlify — _redirects (* matches, :splat is the captured tail; force a 301! if a real file might shadow the rule):
/blog/* https://new.com/articles/:splat 301!
/topics/* https://new.com/category/:splat 301!
/* https://new.com/:splat 301!
Firebase — firebase.json (glob captures use :segment*):
{
"hosting": {
"redirects": [
{ "source": "/blog/:slug", "destination": "https://new.com/articles/:slug", "type": 301 },
{ "source": "/:path*", "destination": "https://new.com/:path*", "type": 301 }
]
}
}
Step 4: Deploy the redirects on the OLD domain’s hosting
This is the step most people miss. Keep old.com’s hosting running with only the redirect rules — you don’t need the full app, just a tiny static deployment whose entire job is the redirect config. Vercel, Netlify, and Firebase can all host a “site” that is nothing but a vercel.json / _redirects / firebase.json.
Then confirm two things on the old domain:
- DNS for
old.com(andwww.old.com) still points at this redirect host. - The redirect host has a valid SSL cert for the old domain — without it, Cause 6 bites.
Step 5: Verify with curl
Sample 50+ random old URLs and confirm every one returns a permanent redirect to a live new URL:
while read -r url; do
# -L follows redirects, -o /dev/null discards the body, then prints the final status + redirect chain
echo "$url -> $(curl -sIL -o /dev/null -w '%{http_code} via %{num_redirects} hop(s)' "$url")"
done < <(shuf old_urls.txt | head -50)
Every line should end in 200 via 1 hop(s) (one clean 301/308 landing on a live page). Watch for:
... via 2 hop(s)or more — a redirect chain (e.g. http→https→new domain); collapse it so the old URL hits the new URL in a single hop.404final status — the destination doesn’t exist; fix the map entry.- A redirect to
https://new.com/for a non-root URL — Cause 1 again.
Step 6: Submit a Change of Address in Search Console
In the old domain’s property: Settings → Change of address → Update, then select the new domain. As of June 2026 the tool requires:
- Both the old and new domains verified in Search Console under the same Google account.
- Domain-level properties (
example.com, notexample.com/path). - Working
301s already in place — the tool runs pre-move checks and blocks on critical failures.
Google now advises running Change of Address for every variant of the old domain — www and non-www, plus any subdomains — even ones you weren’t actively using. The tool tells Google to migrate ranking signals from the old property to the new one.
Step 7: Keep redirects in place — longer than you think
Google’s own guidance (June 2026) is to keep the redirects live for at least 180 days, and longer if you still see any Search traffic hitting the old URLs. In practice, external backlinks persist for years, so keep the redirect-only deployment running indefinitely. Don’t take it down after the migration “feels done.”
How to confirm it’s fixed
- The
curlloop in Step 5 shows200 via 1 hop(s)for every sampled URL. - In Search Console (new domain) → Indexing → Pages, the “Not found (404)” count stops climbing within a few crawl cycles and then declines.
- In the old domain’s property, the Change of Address banner reads as an active/completed move rather than “not started.”
- Spot-check 5 of your highest-value backlinks in a browser — each should land on the correct new-domain page, not a 404.
Easy to misdiagnose as
People assume Google “figures it out.” Without explicit 301s, link equity is lost. Google does not guess that old.com/foo equals new.com/foo once the URL structure has changed — it needs the redirect to connect the two. A pile of homepage redirects is also a trap: Google can treat path→root redirects as soft-404s and drop them, so map to topical equivalents wherever one exists.
Prevention
- Build the redirect map before launching the new domain, not as cleanup afterward.
- Keep redirect rules in source control (
vercel.json,_redirects,firebase.json). - Maintain the old-domain redirect deployment indefinitely; never take it down.
- After every deploy, sample-test 50+ redirects with the
curlloop. - For large sites, design the map around wildcard captures from the start so you stay under platform rule limits (Vercel’s 1,024) and avoid redirect chains.
FAQ
- How long do I have to keep the redirects? Google says at least 180 days, and longer while old URLs still get Search traffic. Because backlinks outlive that window, keep them indefinitely.
- Should I use 301 or 302 for a permanent move? Use a permanent redirect (
301, or308on Vercel’s"permanent": true). A302/307is temporary and signals Google to keep indexing the old URL, so ranking signals don’t fully transfer. - Do I really need to keep the old domain’s hosting alive? Yes. A redirect only fires if a server responds on the old domain with a valid cert. If you fully decommission the old host, every old URL — and every backlink to it — dies.
- My site has 50,000 URLs and I hit Vercel’s 1,024 limit. Now what? Collapse 1:1 rules into wildcard captures (
/blog/:slugcovers every blog post in one rule). For genuinely irregular maps, use Vercel Bulk Redirects (up to 1,000,000/project) or Edge Middleware that looks up the destination from a list. - Do query strings and trailing slashes survive the redirect? Not automatically. Vercel and Netlify preserve query strings by default but you must match trailing-slash behavior yourself; test both
/blog/fooand/blog/foo/in yourcurlsample.
Related
- Indexing coverage drop after redesign
- Canonical still uses old domain
- Sitemap references old domain
- Search Console property mismatch new domain
Tags: #Domain #DNS #SSL #Troubleshooting #Redirect