A website migration is half measurement and half judgment. Here is which half is which
Written: October 2026
Moving a website from one host to another sounds like copying files. The copy is the easy part. A migration fails in the things nobody counted before the move and the decisions nobody made on purpose.
At Grafica we have come to think of it as two jobs done by the same people. One is science: measure the old site, measure the new one, and compare. The other is art: decide what the numbers cannot decide. This post takes them in turn, with examples from a store move we have published.
The science: count it on both sides
Start with an inventory, written down. Pages, posts, products, media, menus, forms and their stored entries, redirects, scheduled jobs, the plugins and the code that exists only on the server. On the store we moved, three must-use plugins and four WordPress drop-in files lived only on the server, with no copy anywhere else. An inventory is how you learn that before the move, not after.
Freeze, then prove the freeze held. Between the copy and the switch, nobody should edit the old site. People forget. So before the switch we ask the old site what changed since the copy, for every content type. The answer we want is zero, and we do not accept a zero until the same question, asked with an earlier date, returns the changes we know were made. A query that always says zero is not evidence.
Compare the things that matter to the business, exactly. For a store, that is orders and product addresses. On the WooCommerce move linked from our Work page, we hashed all 1,494 published product addresses on both hosts and compared the two sets, and reconciled the orders exactly. The result was 0 orders lost and 1,494 addresses unchanged. For a site with forms, the same comparison is made on stored form entries: equal on both servers.
Replace addresses the way the database stores them. During a move the copy lives at a temporary address, and that address ends up written into the database in many places. A search for the plain form finds some of them. Page builders also store addresses in an escaped form, with a backslash before each slash, and a plain search-and-replace walks straight past those. Run the search first as a dry run, in every form, and after the replace search again for the temporary address. The pass is «no results». Then clear the page builder’s own stored HTML too. A site with a clean database can still serve the builder’s saved copy of a page with the temporary address in its links, so check the rendered links, not only the database.
Read the new site as a visitor, every page. Logged out, every published page and post: the status, the canonical address, the robots instruction, and whether any trace of the temporary address is left in the HTML. Then compare the text of every page with the old server’s.
Expect a comparison to lie to you once. A first pass can show most pages «differing» between the two servers when nothing a visitor reads has changed. A common cause is a form’s hidden anti-spam field, which carries a random value on every page load. Exclude that one field and the pages match. It is a useful failure: it shows the comparison can see a difference when there is one.
Test every redirect, not a sample. Each enabled rule, fetched logged out, must land on its target and the target must answer. Then two controls: a live page must not redirect, and a made-up address must return «not found».
The art: what no count decides
What to leave behind. An old hosting account collects things: old backup archives, and plugins nobody remembers configuring. Copying the whole account carries all of it to the new host, and deleting it on the old one is a permanent act on someone else’s property. The clean answer is usually a copy that excludes what the site does not need and deletes nothing.
When to switch. A Friday switch puts the first 48 hours, when problems surface, on a weekend, when the people who hold the domain and the mail accounts are hardest to reach. We plan for a Monday morning, with the previous Friday as a buffer and rehearsal day. The price is a longer freeze, and that has to be agreed with the client before the copy, not after.
How to switch back. Before the DNS record is touched we write down its current value and its time-to-live, and the exact records we are about to add. A rollback that has to be worked out during an incident is not a rollback.
What to tell people about the first hour. After the switch, a visitor’s device or network can keep the old address for as long as the record’s time-to-live allows. For that time, someone with the site already open may still be looking at the old server. A bug report filed inside that window can be accurate and still describe the old server. Look for the request in the new host’s logs before changing anything.
What to rebuild instead of move. On the store migration, the rehearsal showed that a mail plugin’s stored key was tied to the site’s security values, and we carried those values across to keep it working. Two days after the move a routine security action regenerated them and the mail stopped again. Only then did we move transactional mail to the host’s own mailer, which holds no key of that kind. The better moment to drop a part like that is during the move, the first time it shows you how it fails.
What to say when a figure is old. Every number in a migration report is true on a date. We write the date beside the number, and we do not carry last month’s measurement into this month’s sentence.
If you are an agency moving a client’s site: we work white-label by default, and the Agencies page says how.
A short version you can use
Before: inventory, backup, freeze agreed, rollback written, rehearsal on a copy.
During: dry-run every replace, switch inside a window, keep the old server untouched.
After: every page logged out, every redirect, a test submission through each form, orders or entries reconciled, and 48 hours of checks before anyone calls it finished.
Before you move yours
Book a 30-minute call and bring three things: the site’s address, the name of the current host, and the date you need to be off it.