A website migration keeps its search traffic when every old URL that earns visits or links redirects permanently, in one hop, to its new equivalent, when the new pages carry the same content, titles and structured data, and when Search Console is watched for weeks after launch.
That is the short version. The long version below covers the four moves behind nearly every migration brief: changing hosts, switching HTTP to HTTPS, replatforming to a new CMS (with or without new URLs) and changing domains. It is written for the owner, marketer or developer who has to run one, and it stays practical: the redirect rules, the DNS steps, the test script and the monitoring routine. If the rebuild is a move to a headless architecture, the headless CMS guide covers the platform questions; this post covers keeping the search side intact. For how sites are built, launched and maintained in the first place, the web development guide is the companion.
Pick the migration you are actually doing
"Website migration" covers several different jobs with very different risk. Google itself splits the work into two guides: moves with URL changes (domain, protocol, paths, merges) and hosting moves without URL changes. Start by naming yours:
| Migration | URLs change | Main search risk | The rule that matters most |
|---|---|---|---|
| Hosting or CDN change | No | Downtime and slow responses during cutover | Keep both hosts live until the old one goes quiet |
| HTTP to HTTPS | Yes, the scheme | Mixed signals and certificate mistakes | Permanent redirects plus a valid certificate |
| Replatforming, URLs kept | No | Content, metadata and rendering drift | A parity diff of old and new before launch |
| Replatforming with new URLs | Yes | Unmapped URLs and redirect chains | A complete, tested redirect map |
| Domain change | Yes | Signals split between two domains | Redirects plus the Change of Address tool |
| Redesign with rewritten content | Partly | Google must relearn the pages | Do not combine it with a domain move |
| Merging two sites | Yes | Confused consolidation | Move one site at a time |
The risk column is judgment, not a Google rating; the rules on the right come from the sources linked below.
Two planning rules pay for themselves before anything technical starts:
- Change one thing at a time. Google's own example: if you want a new domain, a new CMS and a new layout, move the domain first, then change the layout, as separate steps. A migration that changes everything at once makes every later symptom ambiguous.
- Launch into a traffic dip. If traffic dips on weekends or in a season, move then: fewer people hit any problem, and more of the server's capacity is free for the crawler that is about to recheck everything.
Expect a visible dip anyway. Google says ranking fluctuation during a move is normal: a medium-sized site can take a few weeks or more before the new URLs replace the old ones in results, longer for large sites, because the move is processed URL by URL. The move is done when the crawler has visited every old and new URL at least once, not when the last redirect goes in. For the mechanics behind that, how the Google search engine works is the short version.
Benchmark the site before you change anything
A migration without a benchmark cannot be verified. You need a "before" picture of what earned traffic and links, so that every later wobble can be compared against it. Capture five things:
- Search Console performance data. Export the queries and pages tables for the full window. Google's traffic-drop debugging guide works with the "Last 16 months" date filter, and suggests the Search Analytics API or bulk data exports to keep more. The UI export tops out at 1,000 rows, according to Google's 2022 post on performance data filtering and limits; for a bigger site, the Search Analytics API returns up to 25,000 rows per request (page through them with
startRow), within a cap of 50,000 rows per day per site per search type. If you want history older than 16 months later, start a bulk data export now: its first export covers the day of the export, and Google points to the API or the reports for data that precedes the setup. - The Links report. Save the top linked pages and the list of linking sites; you will ask the valuable ones to update after the move. The report is a sample rather than a complete list: its tables are limited to 1,000 rows, and the export goes up to 100,000 rows.
- Analytics. Organic landing pages by sessions and conversions for at least the last year, so the seasonal shape is visible.
- A full crawl of the old site. Google's guide points readers at Screaming Frog's migration guide; any crawler that exports status codes, titles, canonicals and links works. Cross-check with server logs for URLs visited at least once recently, and include embedded content: images, videos, JavaScript and CSS files are URLs that move too.
- A restore-tested backup of files and database. Migrations are exactly the day a backup earns its keep; what to copy and how to test a restore is a checklist of its own.
Build the redirect map
The redirect map is the deliverable the migration stands on: one row per old URL, with its decision.
Inventory first. Google lists the sources: the XML sitemaps, the CMS's list of content URLs, server logs or analytics for the URLs with traffic, and the Links report for URLs with external links. Pick a log window that covers your seasonal variation. Every URL found becomes a row.

Then decide. Each old URL gets one of four outcomes:
- Keep. The URL still fits; its content, title, canonical and structured data must match or improve.
- Redirect, one to one. The content moved; a permanent redirect to the single equivalent page.
- Merge. Several thin or competing pages become one stronger page, and all the old URLs redirect to it.
- Retire. No successor worth sending anyone to; the URL returns 404 or 410.
The status codes, from RFC 9110 and Google's redirect documentation:
- 301 Moved Permanently and 308 Permanent Redirect both say the resource has a new permanent URI. The difference is in the client: for historical reasons, RFC 9110 lets a user agent change a POST into a GET after a 301 or 302, and it points to 308 (or 307 for a temporary move) when that is unwanted. For search, Google's crawler documentation calls 308 equivalent to 301, and the redirects guide says Google uses a permanent redirect as a signal that the target should be canonical; the site move guide adds that permanent redirects do not cause a loss in PageRank. A 301 is the conservative default: RFC 9110 notes that 308 is much younger (June 2014) and might not be recognized everywhere, and nginx did not treat 308 as a redirect until version 1.13.0.
- 302 Found and 307 Temporary Redirect are for moves you will revert. Google keeps showing the source page in results (its crawler documentation calls a 302 only a weak signal that the target should be processed), and Bing's guidelines reserve 302 for very short-term changes, less than two days.
Server-side beats browser-side. Google recommends server-side redirects wherever possible; an instant meta refresh counts as permanent and a delayed one as temporary, and JavaScript redirects are the documented last resort, because rendering can fail and Google might never see the redirect. On Apache that means configuration or .htaccess rules; on a CMS, the platform's own redirect function.
One hop, not chains. Google's crawlers follow up to ten redirect hops, per the same crawler documentation, but the site move guide advises redirecting to the final destination directly and, when that is not possible, keeping the chain short: ideally no more than three hops and fewer than five. Chains add latency for users, and not all user agents and browsers support long ones. Migrations breed chains by accident (old URL, slash variant, new section, final page), so collapse them inside the map.

Do not mass-redirect to the home page. Google warns that redirecting many old URLs to one irrelevant destination, such as the new home page, can confuse users and might be treated as a soft 404. The exception it names is consolidated content: pages merged into one can all redirect to the merged page.
Warning
Do not send a whole section to the home page; Google may treat that as a soft 404. Map every kept page to its real equivalent, and let retired pages answer honestly with 404 or 410.
Retirement deserves the same care as redirection. RFC 9110 defines 410 Gone as access to the resource being no longer available, a condition likely to be permanent, and says 404 is the right code when the server cannot tell. Google's crawler documentation adds that it does not index URLs that return a 4xx and removes already indexed ones, and Bing's guidelines ask for a 404 on permanently removed content. The Page indexing report is blunt about the tail: there is no way to tell Googlebot to forget a URL permanently, so it keeps trying a 404 for some time while crawling it less and less often. That is why a correct status code matters more than hoping the old URL fades.
Store the map as data, not as a pile of handwritten rules: a two-column CSV that every platform in the platform section below can be generated from.
old_url,expected
https://www.example.com/services/web-design/,https://www.example.com/services/web-design-development/
https://www.example.com/promo-2022/,410
https://www.example.com/blog/2019/05/widgets,https://www.example.com/blog/widgets/
Match content, metadata and signals
Redirects carry the old URLs' signals; parity is what keeps the new pages worthy of them. Check the new site against the old, page for page:
- Primary content answers the same query as the page it replaces. A merged page must serve both of the pages it absorbs.
- Title and meta description carry over unless the change is deliberate.
- Canonicals: every new URL gets a self-referencing
rel="canonical"with an absolute URL; the canonicalization guide prefers absolute paths because relative ones can cause problems in the long run. In the same guide, redirects andrel="canonical"are strong signals, sitemap inclusion is a weak one, and the methods stack when combined. - Hreflang, if the site is multilingual: update every annotation to the new URLs. Google's localized versions guide says each language version must list itself and all the others, with fully-qualified URLs, and that annotations without return links may be ignored.
- Structured data carries over to the equivalent pages, verified on staging rather than in production.
- Internal links point at the final new URLs, never at URLs that redirect. Updating internal links is its own item on Google's checklist, and the pragmatic reason is simple: redirects are slow for users, and each hop is another crawl request.
- A new XML sitemap of the new URLs, built and ready to submit. Per Google's sitemap documentation, one sitemap file holds at most 50,000 URLs and 50MB uncompressed; larger sites split it and submit a sitemap index.
Stage and test the new site
Protect staging with a password or an IP allowlist, not robots.txt. Google's hosting guide suggests an IP-restricted testing environment, or a temporary hostname with noindex in the HTML or HTTP headers. The introduction to robots.txt states plainly that robots.txt is not a mechanism for keeping a page out of Google: a disallowed URL can still be indexed from links elsewhere, and the way to keep a page out is noindex or a password. Combining robots.txt with noindex defeats the purpose: per Google's noindex documentation, the crawler never sees a noindex rule on a page that robots.txt blocks.
Then test, in this order:
- Crawl staging and diff it against the old crawl: titles, descriptions, canonicals, structured data, status codes. Every difference is a decision that someone should be able to explain. This is a technical SEO audit scoped to a single release.
- Measure performance before launch. A rebuild is the easiest place to regress page experience, and the time to catch it is while the old site still serves; the Core Web Vitals guide covers the thresholds and how to measure them.
- Test the redirect map against the new server before DNS moves.
curl --resolvesupplies a custom address for a host and port pair, much like an/etc/hostsentry on the command line, so the production URLs can be tested against the new host while the world still sees the old one:
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' \
--resolve www.example.com:443:203.0.113.10 \
https://www.example.com/services/web-design/
(The address is a placeholder; use your new host's. The status and redirect target it prints are exactly what the launch-day script checks for the whole map.)
- Get the certificate in place. The new host needs a valid certificate covering every host variant (the bare domain and
wwwat minimum) before the cutover: Google's canonicalization guide warns that bad TLS certificates and HTTPS-to-HTTP redirects make it prefer the HTTP version very strongly. With Let's Encrypt, the HTTP-01 challenge puts a file on the web server behind the domain's own address on port 80, which mid-migration still points at the old server; the DNS-01 challenge proves control with a TXT record instead, so the certificate can be issued before DNS changes. Renewing and installing certificates is covered step by step in its own guide. - Check robots.txt on the new site with fresh eyes. If the file does not exist, the server must answer 404: Google's list of common migration mistakes asks for a proper 404, and its robots.txt specification treats any 4xx other than 429 as no crawl restrictions. A 5xx is the dangerous answer: per that specification, Google stops crawling the site for the first 12 hours while it keeps trying to fetch the file.
Plan the DNS cutover
For a hosting move, the cutover is a DNS change, and its one control knob is the TTL. RFC 1035 defines the TTL as the time interval, in seconds, that a record may be cached before it should be discarded. It also describes TTLs of cached data as counting down while the data in the zone keeps a constant TTL, which is why the same question gets two different answers:
$ dig +noall +answer shop.example.com # your caching resolver
shop.example.com. 241 IN A 203.0.113.10
$ dig +noall +answer shop.example.com @ns1.provider.example # the authoritative server
shop.example.com. 300 IN A 203.0.113.10
(Illustrative output with a placeholder host name; the pattern comes from real dig runs. 241 is the seconds the resolver's cache has left, 300 the configured TTL.)
RFC 2181 pins the semantics down: a TTL is a maximum time to live, not a mandatory one. So plan for the worst case: a resolver that cached the record one second before your change may serve the old address for the entire previous TTL. You cannot flush the world's caches; you can only shrink the number ahead of time. Google's hosting guide tells you how: consider lowering the TTL to a conservative low value, for example a few hours, at least a week before the move.

Create new records early, too. Negative answers cache as well: RFC 2308 lets a resolver remember that a name does not exist, for a TTL derived from the zone's SOA record, which is why a record added at the last minute can look broken to some users right after you add it.
The cutover itself:
- A week out, lower the TTL on every record that will change.
- Deploy and test the site on the new host (the previous section), and if the move is part of a bigger infrastructure program, the cloud migration strategy guide covers the sequencing of the rest.
- T minus zero: update the A, AAAA or CNAME records.
- Watch propagation with public resolvers (
dig @1.1.1.1, or any public DNS checker) and with the server logs on both hosts: the old one declines as the new one grows. - Expect a crawl-rate wobble: Google describes a temporary drop in Googlebot's crawl rate right after a hosting change, followed by a steady increase over the next few days.
- Shut the old host down only when its logs show zero traffic. Until then it is the rollback: point DNS back and the move is undone. That is how our hosting and maintenance service runs a host move: rehearsed on a staging address, cut over with lowered TTLs, and the previous host kept warm as the rollback.
If the domain itself is changing, DNS is the small part. The old domain must keep answering, because the redirects live there: the Change of Address help page suggests continuing to pay for the old domain for at least a year, and both domains stay verified in Search Console.
Launch day, in order
Launch day is a fixed sequence: the blocks come off first, the switch flips second, and the checks run before anyone declares the move done.
- Remove the safety blocks. The first item on Google's list of common migration mistakes is
noindexrules or robots.txt blocks that were only needed for the migration. Check the live HTML and fetch/robots.txtyourself. - Flip the switch: the DNS change for a hosting move, or the redirect rules for URL and domain moves. Google recommends small and medium-sized sites move all URLs at once; a large site can go section by section, starting with a section that changes infrequently.
- Confirm the certificate and HTTPS on every live hostname variant.
- Test the whole redirect map with a script. This one checks each old URL for a permanent status and a direct hit on the expected target, using curl's
http_code,redirect_url,num_redirectsandurl_effectivewrite-out variables:
#!/usr/bin/env bash
# Usage: ./check-redirects.sh map.csv
# map.csv rows: old_url,expected (expected is the final URL, or a status like 410)
fetch() { local fmt=$1; shift; curl -s -o /dev/null -w "$fmt" "$@"; }
bad=0
while IFS=, read -r old expected; do
expected=${expected%$'\r'}
[[ -z $old ]] && continue
read -r code target < <(fetch '%{http_code} %{redirect_url}' "$old")
if [[ $expected =~ ^[0-9]{3}$ ]]; then
[[ $code == "$expected" ]] && result=OK || result="WANT-$expected"
elif [[ $code != 301 && $code != 308 ]]; then
result=NOT-PERMANENT
elif [[ $target != "$expected" ]]; then
read -r hops final < <(fetch '%{num_redirects} %{url_effective}' -L --max-redirs 10 "$old")
[[ $final == "$expected" ]] && result="CHAIN-$hops-HOPS" || result=WRONG-TARGET
elif [[ $(fetch '%{http_code}' "$expected") != 200 ]]; then
result=TARGET-NOT-200
else
result=OK
fi
[[ $result == OK ]] || bad=1
printf '%-16s %s %s\n' "$result" "$code" "$old"
done < "$1"
exit "$bad"
Its output against a deliberately flawed map, from a run against a local test server with its address replaced by example.com:
OK 301 https://example.com/services/web-design/
OK 410 https://example.com/promo-2022/
CHAIN-2-HOPS 301 https://example.com/blog/2019/05/widgets
NOT-PERMANENT 302 https://example.com/pricing/
WRONG-TARGET 301 https://example.com/case-studies/acme/
TARGET-NOT-200 301 https://example.com/products/blue-widget/
WANT-410 404 https://example.com/old-news/
OK 308 https://example.com/team/
Every non-OK row names its failure: a chain to collapse, a 302 to make permanent, a wrong or broken target, a retired page returning the wrong status. The script exits 1 when anything fails, so it can gate a deployment.
- Spot-check the money URLs in Search Console's URL Inspection tool; Google's guide suggests it for individual URLs, with command line tools or scripts for large numbers of them.
- Submit the sitemap of new URLs, and keep the old-URL sitemap submitted as well: Google's monitoring advice is to submit both, so the indexed counts drain from one and fill the other, and it says the warnings about redirecting URLs in the old sitemap are normal.
- Domain change only: submit the Change of Address for the old property, after the redirects are live. The requirements: you own both properties and manage them with the same Google account, and you work at the domain level (example.com, not example.com/shop); the tool moves all protocols but no subdomains, so submit a request for every verified subdomain variant, www and non-www included, even ones you no longer use. It is not for HTTP to HTTPS, www versus non-www, or path-only moves. It runs pre-move checks (including for 301s), keeps its notifications up for 180 days, and can be cancelled within that window. Two more rules from the same page: do not chain moves (after a request from A to B you cannot immediately submit B to C), and avoid moving several sites into one location at the same time.
- Tell Bing. Bing once had a Site Move tool in Webmaster Tools, and a 2020 guest post on the Bing Webmaster Blog still tells readers to use it. The help page that post links to returns a 404 as of 9 October 2026, and the current Bing Webmaster Guidelines describe no such tool. They ask instead for 301 redirects on permanent moves, sitemaps that list only canonical URLs with redirected URLs removed promptly, and IndexNow notifications for URLs that are added, updated or removed. IndexNow takes up to 10,000 URLs per POST behind a key file hosted on your domain, and an HTTP 200 response means only that the search engine has received the URLs; Bing's guidelines prefer streaming submissions to batches where possible.
- Housekeeping. Re-upload any disavow file on the new property, as Google recommends, and confirm the Search Console verification file or meta tag survived the move.
- Update what you control: social profiles, ad landing pages, and the highest-traffic sites linking in from the benchmark's list. Redirects carry the rest.
Watch the first twelve weeks
Week 1: errors before rankings. Read the Page indexing report daily. The statuses that matter now are "Redirect error" (a chain that was too long, a loop, a bad or empty URL), "Soft 404", "Not found (404)", "URL marked 'noindex'" and "URL blocked by robots.txt". Old URLs should drift into "Page with redirect", which is not an error: redirect URLs are not indexed, only the redirect target. Export the 404 list early, because it only covers URLs that returned 404 in the past month. When a fix ships, use "Validate fix"; validation typically takes up to about two weeks, and can take much longer. In the Crawl Stats report (root-level properties only, and Google says sites with fewer than a thousand pages will not need it), watch host status: robots.txt availability, DNS resolution and host connectivity. Note that each hop of a redirect chain is counted there as a separate request, so chains inflate crawl load exactly when Google is crawling the new site hardest. Keep tailing both servers' logs. For the mechanics behind these reports, web crawling and indexing, explained covers the pipeline.
Tip
Export the 404 list in week 1 and weekly after that. It only ever shows the past month, so the record of what broke ages out while you are fixing it.
Month 1: compare against the benchmark. Impressions and clicks for the exported pages and queries, old property draining while the new one fills. Fluctuation is expected; what matters is the shape: a new URL that stays at zero while its old twin also reads zero is probably a page Google has not recrawled yet, and URL Inspection will show what Google last saw.
Months 2 to 3: settle the tail. A medium site should have most URLs moved; large sites take longer. Old URLs occasionally appearing in results is normal: Google keeps the old URL as an "alternate name" of the canonical for a while, and it fades without intervention. Keep the redirects up for as long as possible, at least a year by the site move guide (the Change of Address page's floor is 180 days, longer if they still get traffic; the 2020 Bing Webmaster Blog guest post suggests one to two years or more), and keep the old domain registered for at least a year. Ask the top linking sites to update their links: redirects are slow for users, and every updated link is one less dependency on the redirect layer.
We ran this site's own move off WordPress by the same rules (blog URLs unchanged, every other change a recorded decision). The same URL map, pre-launch verification and post-launch monitoring are what our SEO service and web design and development service apply to a launch.
Redirect rules on your platform
Wherever the rules live, prefer a layer that answers before the CMS or application code runs (the web server, the CDN or the framework's routing): a redirect that never reaches the CMS cannot fail with it.
WordPress
Rewrite URLs inside content with WP-CLI rather than raw SQL: wp search-replace handles PHP serialized data and supports --dry-run, while the WordPress handbook warns that a plain search and replace over the whole database can break serialized values that some themes and widgets store. The Migrating WordPress guide adds two blunt rules: never change the guid column (feed readers use it to recognize posts they have already seen), and back up .htaccess before touching it, which it calls a requirement, not a recommendation.
wp search-replace 'https://old.example.com' 'https://www.example.com' --dry-run
wp search-replace 'https://old.example.com' 'https://www.example.com' --skip-columns=guid
Run separate passes for scheme and host variants, re-check the two address fields in Settings, then regenerate and review the permalinks. One more launch check: the Settings, Reading, "Discourage search engines from indexing this site" box makes WordPress output a noindex, nofollow meta tag since version 5.3, per its documentation; make sure it is unchecked before launch. Redirects themselves can live in a plugin or in server rules. A plugin keeps the map editable in the admin but runs only after PHP and WordPress have loaded; rules in .htaccess or the web server answer before WordPress starts and keep working if the plugin is switched off. The settings beyond the move are in the WordPress SEO checklist.
Shopify
Shopify's built-in URL redirects (with CSV import and export) come with hard edges, as of October 2026: a redirect only works from a URL that currently returns 404; the prefixes /apps, /application, /cart, /carts, /orders, /services and /shop cannot be redirected, and neither can the fixed paths /products, /collections and /collections/all; the cap is 100,000 redirects (20 million on Plus), and for a large number the page itself suggests a third-party app; URLs with query strings may not work as expected; and the page is not consistent about market subfolders (one section says a redirect applies to all of them, another says it does not apply automatically and that redirecting specific locales needs a redirect per subfolder), so test each market. Two consequences: retire or rename the old page before creating its redirect, and accept that /products and /collections are fixed paths, so a move onto Shopify means mapping every old product and collection URL onto them. Shopify also warns that browsers and search engines cache 301s, so a removed redirect can keep working from cache until it expires. The rest of the platform's search surface is in the Shopify SEO checklist, and product and filter URLs more broadly are in the e-commerce SEO guide.
Next.js
Redirects live in next.config: permanent: true returns a 308 and permanent: false a 307 (Next.js uses those codes explicitly to preserve the request method), and statusCode can replace permanent when a specific code is needed. The redirects reference notes they are checked before the filesystem, query strings pass through to the destination, and sources support wildcards and regex parameters:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
trailingSlash: true, // /about redirects to /about/
async redirects() {
return [
{
source: "/services/web-design",
destination: "/services/web-design-development/",
statusCode: 301,
},
{
// Dated URLs collapse to flat slugs: /2021/05/12/my-post -> /my-post/
source: "/:year(\\d{4})/:month(\\d{2})/:day(\\d{2})/:slug",
destination: "/:slug/",
statusCode: 301,
},
];
},
};
export default nextConfig;
With trailingSlash: true, a request for the slashless URL redirects to the slashed one (files with extensions and paths under .well-known/ are exempt), so write destinations with the trailing slash or every mapped URL earns a second hop. Platform caps apply: the Next.js redirecting guide notes that redirects may have platform limits, for example 1,024 on Vercel, and suggests a custom solution in Proxy once a map reaches 1,000 or more. This site's own redirect map uses the same regex rule to flatten dated blog URLs, with trailingSlash on and 301 status codes throughout.
nginx
One map, one return. The map module builds a lookup from $uri, which nginx defines as the current URI, normalized, as opposed to $request_uri, the full original URI with arguments. String keys in a map are matched ignoring case, and a map variable is evaluated only when it is used:
# http block: the map lives in its own file, one pair per line
map $uri $new_url {
default "";
include /etc/nginx/redirect-map.conf;
}
server {
listen 443 ssl;
server_name old.example.com www.old.example.com;
if ($new_url) {
return 301 https://www.example.com$new_url;
}
# ... the rest of the old-site server config
}
# /etc/nginx/redirect-map.conf
/services/web-design/ /services/web-design-development/;
/blog/2019/05/widgets /blog/widgets/;
/team/ /about/team/;
return with a URL supports 301, 302, 303, 307 and, from nginx 1.13.0, 308. A map with thousands of entries may need map_hash_max_size raised past its 2048 default; nginx -t will tell you before a reload does. (Configuration verified against the nginx documentation; test on your own staging before trusting it.)
Apache
Start with mod_alias: Apache's own guide to when not to use mod_rewrite calls it a last resort, for when the alternatives fall short. RedirectMatch takes a regular expression, so ^ and $ pin an exact path, while plain Redirect is a prefix match that appends the remainder of the path (right for whole trees, wrong for single pages), and the gone status returns a 410 with no target. mod_alias is built for simple URL manipulation, and Apache's documentation sends tasks such as manipulating the query string to mod_rewrite, so a URL identified by its query string needs a RewriteCond:
# One-to-one moves (mod_alias)
RedirectMatch permanent "^/services/web-design/?$" "https://www.example.com/services/web-design-development/"
# Retired for good: 410, no target
RedirectMatch gone "^/promo-2022(/.*)?$"
# An old URL identified by its query string (mod_rewrite)
RewriteEngine On
RewriteCond %{QUERY_STRING} ^id=42$
RewriteRule ^/product\.php$ "https://www.example.com/products/blue-widget/?" [R=301,L]
The mod_rewrite reference says to end the substitution with just a question mark to erase the old query string; without it, a redirect to an absolute URL carries the requested query string along. The RewriteRule flags page confirms that [R] without a code means 302, so write [R=301,L]. In an .htaccess file (per-directory context) the pattern matches the path without its leading slash: ^product\.php$. This configuration passed httpd -t on Apache 2.4.67, which checks the syntax, not the behaviour; test it on staging.
Symptom, cause, fix
The week after launch, these are the failures that actually show up, mapped to their fixes:
| Symptom | Likely cause | Fix |
|---|---|---|
| New pages absent; report says "URL marked 'noindex'" | Staging noindex, or the CMS "discourage" option, shipped to production | Remove it, then request indexing on the key URLs |
| "URL blocked by robots.txt" across the site | Staging robots.txt copied to production | Restore the real robots.txt; a missing file must return 404, and a 5xx stops crawling |
| "Redirect error" entries climbing | Chains too long, loops, or empty targets | Point every rule at the final URL and re-run the map script |
| "Soft 404" across whole sections | Mass redirect to the home page or another irrelevant page | Map to real equivalents, or return 404/410 |
| "Not found (404)" keeps growing | URLs nobody mapped | Mine the report and the server logs, add the missing rules; Google retries old URLs for some time |
| Crawl Stats host status red, crawl rate falling | New server overloaded, or a firewall blocking Googlebot | Add capacity and let Googlebot through; Google crawls the new site more heavily for a while |
| Traffic down, indexing clean | Content changed with the URLs, so Google is relearning the pages | Expected when a redesign rides along with a move; compare per page against the benchmark |
| Old URLs still appear in results weeks later | Nothing broken: the old URL lingers as an alternate name | No action; it fades |
The website migration checklist
Two weeks out:
- Benchmarks exported: 16 months of performance data, the Links report, analytics, and the bulk export enabled.
- Full crawl and log-based URL inventory, assets included.
- Redirect map finished: one decision per URL, no chains, no mass redirects.
- Backup of files and database, restore-tested.
Before launch:
- New site on staging behind authentication, crawled and diffed against the old crawl.
- Canonicals self-referencing, hreflang updated, structured data carried over, internal links rewritten to final URLs.
- New XML sitemap built; robots.txt reviewed; every noindex and crawl block listed for removal.
- Certificate valid on the new host for every hostname variant (DNS-01 lets you issue it before the cutover).
- Performance of the new build measured, not assumed.
- Redirect map tested on the new server with the script, zero non-OK rows.
- TTL lowered; old host committed to stay up; Search Console verified for every property variant, old and new.
Launch day:
- noindex off, crawl blocks removed, robots.txt answering 200 or 404, never 5xx.
- DNS updated, or redirects enabled.
- Certificate and HTTPS confirmed on the live hostnames.
- The map test re-run against production.
- New sitemap submitted, old-URL sitemap kept, disavow file re-uploaded.
- Change of Address submitted for every verified variant (domain moves only).
- IndexNow notification sent for the new URLs.
- Profiles, ads and top referrers updated.
After launch:
- Week 1: Page indexing and Crawl Stats read daily, both servers' logs watched, fixes validated.
- Month 1: rankings and clicks compared per page against the benchmark.
- Months 2 to 3: top linking sites asked to update, the 404 list mined weekly.
- Long term: redirects stay at least a year and the old domain stays registered at least a year; the old host goes only once its logs read zero.


