Hosting Guides ⭐ Editor's Pick

Hosting Migration Mistakes That Kill Websites — And How to Avoid Every One

Most hosting migrations go wrong in the same predictable ways. After migrating hundreds of sites over 20 years, here are the mistakes I have seen cost people data, revenue, and sleep — and exactly how to avoid them.

Khaled S. Khaled S. · Software Architect & Web Hosting Expert · Jun 10, 2026 · 14 min read

Quick Answer

Test on a staging subdomain first, reduce your DNS TTL to 5 minutes 48 hours before switching, and migrate during your lowest-traffic window. Once you flip DNS, take the old site fully offline — maintenance mode, not a redirect — so no writes split across two databases. Keep the old hosting account active for 30 days as an emergency rollback option, then cancel once you are confident the new server is stable.

I have migrated hundreds of websites over 20 years. The migrations that go wrong almost always fail for the same reasons — and almost none of those reasons are technical. They are procedural. Someone skipped a step, moved too fast, or cancelled the old account before they should have.

This is not a generic "how to migrate" guide. This is a list of the specific ways migrations fail, what the failure looks like in practice, and exactly what to do to prevent each one.

Mistake 1 — Not Having a Plan for the Switch

Most migration disasters are not caused by technical errors. They are caused by switching at the wrong time, without testing first, and without a clear understanding of what happens in the minutes and hours after DNS changes.

Here is the complete, practical approach — five rules that eliminate most migration emergencies before they happen.

Rule 1: Test on staging before you touch DNS

Set up the new server with a staging subdomain — staging.yourdomain.com or direct access via the server's IP address. Copy your files and database there, configure everything, and test thoroughly in the real environment. Check every page, every form, every checkout flow, every admin function. Take as long as you need.

This separates "is the new server ready?" from "should I switch DNS?" — which are two different questions that most people conflate. By the time you switch DNS, you should already know the answer to the first question with certainty. Unexpected emergencies during migration almost always happen because someone skipped this step and discovered problems under live traffic pressure instead of in a calm staging environment.

Protecting your staging subdomain from Google

A publicly accessible staging subdomain creates two problems: Google indexes it as duplicate content, and anyone can find it. Protect it with one of the following — ideally both together.

Option A — IP restriction (strongest)

Whitelist only the IPs that need access. Google's crawler and everyone else gets a connection refused before reading a single byte.

In Apache .htaccess:

Order Deny,Allow
Deny from all
Allow from 203.0.113.42
Allow from 198.51.100.7

In Nginx server block:

location / {
    allow 203.0.113.42;
    allow 198.51.100.7;
    deny all;
}

At the hosting firewall level (cPanel, DigitalOcean, Hetzner dashboard) — block all inbound traffic to the staging subdomain except from your IPs. This is the strongest option: the connection is dropped before the web server sees it.

If your home IP is dynamic and changes, a VPN with a dedicated IP — offered by providers like NordVPN — gives you a fixed source IP you can whitelist permanently. See our dedicated IP guide for the full explanation of why this matters.

Option B — HTTP Basic Auth (simple fallback)

If you cannot restrict by IP — for example, your team is distributed across multiple dynamic IPs — password-protect the staging environment at the server level.

First, generate a .htpasswd file:

# Generate credentials (run on the server or locally with htpasswd installed)
htpasswd -c /home/user/.htpasswd staginguser
# Enter password when prompted

Then add to your staging .htaccess:

AuthType Basic
AuthName "Staging — Authorised Access Only"
AuthUserFile /home/user/.htpasswd
Require valid-user

Google's crawler cannot authenticate and will not index the site. Neither can anyone who finds the URL without the credentials.

Best practice: use both. IP restriction blocks everything at the network layer. Basic Auth is the fallback if someone accesses staging from an unlisted IP — a teammate working from a different location, for example. Together they cover all cases without complexity.

Rule 2: Reduce TTL 48 hours before switching

DNS TTL (Time To Live) controls how long resolvers cache your DNS records. Most hosting accounts default to 3,600–86,400 seconds. Change your A record TTL to 300 seconds (5 minutes) at least 48 hours before you plan to switch. See Mistake 2 for the full details.

With a 5-minute TTL, the propagation window after your DNS switch shrinks from hours to minutes. This is the single most impactful preparation step you can take.

Rule 3: Switch at your lowest-traffic moment

Check your analytics and find when your site receives the least traffic — typically Tuesday or Wednesday between 2 AM and 5 AM in the timezone of your primary audience. Do the DNS switch then.

Even with a short propagation window, there is always a brief period of uncertainty. Fewer users in that window means fewer people who experience any disruption, fewer orders that could be affected, and a much quieter environment for you to monitor and react to anything unexpected. Never migrate on a Friday afternoon, before a sale, or during peak business hours.

Rule 4: Take the old site down immediately after switching — but keep the account

Once you flip the DNS, take the old site offline immediately. Not a redirect — actually down. Put it into maintenance mode or disable the application entirely.

For Apache, replace your .htaccess with a maintenance page:

# Replace old server .htaccess content after DNS switch
# Returns 503 to any remaining traffic — nothing runs, nothing writes
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/maintenance\.html$
RewriteRule ^(.*)$ /maintenance.html [R=503,L]
ErrorDocument 503 /maintenance.html

For WordPress, drop a .maintenance file in the root:

touch /home/user/public_html/.maintenance

For any PHP application, a single line at the top of your index.php:

<?php http_response_code(503); die('Site migrated. Please clear your DNS cache.');

Why down rather than redirect? A redirect still means the old server is running, still capable of processing requests if the redirect fails or is bypassed. Taking it fully down eliminates the split-brain problem entirely — no request reaches the old application, no writes reach the old database. Any user whose DNS resolver is still pointing at the old server sees a 503, clears their cache, and retries — at which point they reach the new server.

The old server is now frozen in time. Its database is intact, its files are untouched. That is exactly what you want.

Rule 5: Keep the account active for 30 days as your emergency rollback

Do not cancel the old hosting account. Keep it paid and active for at least 30 days after the migration.

This is not about DNS propagation — with a 5-minute TTL (Rule 2) and low-traffic timing (Rule 3), propagation is mostly complete within an hour. The reason to keep the account is the emergency rollback scenario.

Three days after migration you discover something seriously broken on the new server — a payment gateway that stopped working, a database corruption, a PHP incompatibility you missed in testing. If the old account is still active, the old database is intact, the files are there, and nothing has been written to the old environment since you took it down. You can bring it back up and point DNS back in minutes. The damage is limited.

If you cancelled the account, that option is gone. You are now debugging a broken new server with no fallback, under pressure, with live traffic affected.

Thirty days of overlap costs the price of one month of hosting. That is cheap insurance. After 30 days of stable operation on the new server, cancel the old account with confidence.

Mistake 2 — Not Reducing DNS TTL Before the Migration

DNS TTL (Time To Live) is the number of seconds resolvers are told to cache your DNS records. Most hosting accounts have a default TTL of 3,600 to 86,400 seconds — meaning resolvers can cache your old IP for up to 24 hours after you change it.

The fix is simple and takes 10 minutes, but it needs to happen 24–48 hours before you start the migration:

  1. Log into your domain registrar's DNS management panel
  2. Change the TTL on your A record (and AAAA if using IPv6) from the default to 300 seconds (5 minutes)
  3. Wait 48 hours for all resolvers to flush their cache of the old high TTL
  4. Perform the migration and switch the DNS A record to the new IP
  5. With a 5-minute TTL, propagation takes minutes instead of hours
  6. After propagation is confirmed, set TTL back to 3,600

Skipping this step means you are at the mercy of however long your old TTL was. If it was set to 86,400 seconds, some resolvers will continue serving your old IP for a full day after you switched.

Mistake 3 — Forgetting Email

Email is the migration step that most developers forget, and the one that causes the most confusion for clients. A website migration and an email migration are two separate operations — they use different DNS records (A record vs MX record) and they propagate independently.

A common failure scenario:

  1. Website migrated successfully — A record updated, site loads on new server
  2. MX records still pointing to old hosting's mail server
  3. Incoming email continues arriving at old host for days
  4. Old hosting cancelled
  5. All email sent during the transition window is lost

Before touching any DNS records, document your complete DNS configuration: A record, AAAA, MX records (all of them, in priority order), SPF, DKIM, DMARC, and any custom TXT records. After migration, recreate all of them. Update MX records only after the new mail server is fully configured and tested.

If you are moving to a host that does not provide email (a VPS running only web services, for example), set up email through a dedicated email provider — Google Workspace, Zoho, or Proton Business — and configure those MX records on the new DNS before you cancel the old account.

Mistake 4 — Testing Only the Homepage

After migrating, most people load the homepage. It works. They declare success.

What breaks is never the homepage. It is:

  • The checkout page that had a different PHP extension requirement
  • The contact form that used a mail function the new server has disabled
  • The admin panel that writes to a directory that now has incorrect permissions
  • The scheduled task (cron job) that runs at 3 AM and was never recreated on the new server
  • The image upload feature that expected a specific ImageMagick version
  • The PDF generation that requires wkhtmltopdf to be installed on the server

A proper post-migration verification covers all user-facing paths, all form submissions, all file operations, all scheduled tasks, and all third-party integrations. This cannot be done in five minutes.

Mistake 5 — PHP Version Mismatch

Your old server runs PHP 7.4. Your new server defaults to PHP 8.2. These are not compatible. Deprecated functions removed in PHP 8.x will throw fatal errors. Code that was working silently will break loudly.

Always check the PHP version on both servers before migrating. Run php -v on both, or check your hosting control panel. If your application requires a specific PHP version, confirm the new host supports it before you commit to the move.

Use this quick compatibility check before migrating:

<?php
// Run this on the NEW server before pointing DNS there
// Place at a temporary URL like /migration-check.php (delete after use)

$checks = [];

// PHP version
$checks['PHP version'] = [
    'value'  => PHP_VERSION,
    'status' => version_compare(PHP_VERSION, '8.1.0', '>=') ? 'ok' : 'warn',
];

// Required extensions
$required = ['pdo', 'pdo_mysql', 'mbstring', 'openssl', 'curl', 'gd', 'zip', 'intl', 'xml'];
foreach ($required as $ext) {
    $checks["ext: {$ext}"] = [
        'value'  => extension_loaded($ext) ? 'loaded' : 'MISSING',
        'status' => extension_loaded($ext) ? 'ok' : 'error',
    ];
}

// Disabled functions (common blocking culprits)
$disabled = array_map('trim', explode(',', ini_get('disable_functions')));
$blocking = array_intersect(['exec', 'shell_exec', 'proc_open', 'passthru'], $disabled);
$checks['Disabled functions'] = [
    'value'  => $blocking ? implode(', ', $blocking) . ' are disabled' : 'none blocking',
    'status' => $blocking ? 'warn' : 'ok',
];

// Memory limit
$mem = ini_get('memory_limit');
$checks['Memory limit'] = [
    'value'  => $mem,
    'status' => (intval($mem) >= 256 || $mem === '-1') ? 'ok' : 'warn',
];

// Max upload size
$checks['Upload max filesize'] = [
    'value'  => ini_get('upload_max_filesize'),
    'status' => 'info',
];

// Write permissions
$writeable = is_writable(sys_get_temp_dir());
$checks['Temp dir writable'] = [
    'value'  => $writeable ? sys_get_temp_dir() : 'NOT WRITABLE',
    'status' => $writeable ? 'ok' : 'error',
];

// Database connectivity (fill in your new DB credentials)
$dbHost = 'localhost';
$dbName = 'your_new_db';
$dbUser = 'your_db_user';
$dbPass = 'your_db_password';
try {
    $pdo = new PDO("mysql:host={$dbHost};dbname={$dbName}", $dbUser, $dbPass);
    $checks['Database connection'] = ['value' => 'Connected', 'status' => 'ok'];
} catch (Exception $e) {
    $checks['Database connection'] = ['value' => 'FAILED: ' . $e->getMessage(), 'status' => 'error'];
}

// Output
header('Content-Type: text/plain');
foreach ($checks as $label => $check) {
    $icon = $check['status'] === 'ok' ? '✓' : ($check['status'] === 'error' ? '✕' : '⚠');
    echo "{$icon}  {$label}: {$check['value']}\n";
}
echo "\nDelete this file after use.\n";

Upload this file to the new server before switching DNS and access it directly by IP to verify the environment before a single user hits it.

Mistake 6 — Hardcoded Absolute Paths

Old server: /home/johnsmith/public_html/uploads/
New server: /home/johnsmith2/public_html/uploads/

Any code that hardcodes the absolute filesystem path will break on the new server. This is most common in older WordPress installations, CodeIgniter applications, and any CMS that stored the upload path in the database during installation.

Before migrating, search your codebase and database for the old server's absolute path:

# Search codebase for hardcoded paths
grep -r "/home/oldusername" /path/to/your/site/ --include="*.php" --include="*.env"

# Search WordPress database for hardcoded paths
# Run in phpMyAdmin or via WP-CLI:
# SELECT * FROM wp_options WHERE option_value LIKE '%/home/oldusername%'

For WordPress specifically, run a search-replace on the database after migration to update any stored absolute paths:

# Using WP-CLI on the new server
wp search-replace '/home/oldusername/public_html' '/home/newusername/public_html' --all-tables

Mistake 7 — Not Migrating Cron Jobs

Cron jobs are invisible — they do not break loudly. Your site loads, everything looks fine, and three days later someone notices that the weekly report was not sent, the expired sessions were not cleared, or the sitemap was not regenerated. By the time you trace it to the missing cron job, the damage is done.

Before migrating, document every cron job on the old server:

# List all crontabs on old server
crontab -l

# For shared hosting (cPanel), export from Cron Jobs section before migrating

Recreate all of them on the new server and verify they run successfully before cancelling the old account.

Mistake 8 — Migrating the Database Separately From the Files

A WordPress database exported at 14:00 and the files copied at 16:00 will be out of sync. Two hours of user activity — comments, orders, form submissions, password resets — will be in the files but not the database, or in the database but referencing files that do not exist.

The correct approach for any database-driven site:

  1. Put the site in maintenance mode on the old server
  2. Export the database
  3. Copy the files
  4. Import the database on the new server
  5. Verify the new server works
  6. Switch DNS
  7. Remove maintenance mode

The maintenance window is typically 15–30 minutes. That is acceptable. Losing two hours of database writes because you did not coordinate the timing is not.

Mistake 9 — Not Verifying SSL Before Switching DNS

SSL certificates are not automatic on all hosting environments. On some VPS setups, you need to provision Let's Encrypt manually. On cPanel hosting, it is usually automatic but may need to be triggered.

If DNS switches to your new server before SSL is provisioned:

  • Browsers display a security warning for HTTPS URLs
  • HTTP Strict Transport Security (HSTS) violations block returning users entirely
  • Any mixed-content issues on the new server become visible to users

Before switching DNS, access the new server directly via IP (or via a temporary subdomain) and confirm SSL is provisioned and valid. For Let's Encrypt on Apache or Nginx:

# Check certificate status on new server before DNS switch
sudo certbot certificates

# If not yet provisioned (replace with your domain)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

The Pre-Migration Checklist

Before touching anything:

  • ☐ Reduce DNS TTL to 300 seconds (do this 48h in advance)
  • ☐ Document all DNS records: A, AAAA, MX, SPF, DKIM, DMARC, TXT
  • ☐ Document all cron jobs
  • ☐ Note PHP version and required extensions on old server
  • ☐ Identify any hardcoded absolute paths in code or database
  • ☐ Full backup of files AND database (same timestamp)

On the new server before switching DNS:

  • ☐ Run environment verification script (PHP version, extensions, DB connectivity)
  • ☐ SSL provisioned and valid
  • ☐ All cron jobs recreated and tested
  • ☐ Database imported and verified
  • ☐ File permissions correct
  • ☐ Test all major site functions via direct IP access

After switching DNS:

  • ☐ Monitor DNS propagation (dnschecker.org)
  • ☐ Test all site functions once propagation is confirmed
  • ☐ Verify email is sending and receiving on new server
  • ☐ Keep old hosting account active for 30 days minimum
  • ☐ Set DNS TTL back to 3,600 after propagation confirms
  • ☐ Only cancel old account after 30-day buffer period with no issues

Which Hosting Providers Include Free Migration?

If you would rather have the migration handled for you, several providers include free migration assistance with all plans. The quality varies significantly — "free migration" can mean anything from a full hands-on transfer by a technical team to an automated tool that moves files without touching configuration.

Providers that consistently deliver genuine, technically capable migration assistance: SiteGround (hands-on team, handles WordPress and custom PHP apps), InMotion Hosting (free migration for all plans, dedicated migration team), and Kinsta (for managed WordPress — full migration handled by their team with staging verification before go-live).

Whatever approach you choose — DIY or assisted — the buffer period rule applies regardless. Keep the old account alive. The 30 days of overlap cost almost nothing. The alternative, when it goes wrong, costs significantly more.

Khaled S.

Written by

Khaled S.

Software Architect & Web Hosting Expert

Khaled S. is a software architect with over 20 years of hands-on experience in web development and web hosting infrastructure. He ran his own web development agency, where he helped clients across industries select, configure, and manage their hosting environments — from shared hosting for small businesses to VPS and d...

More about this site →
Share X LinkedIn

Related articles

Hosting Guides

Hostinger Pricing Explained: Plans, Renewals, and Hidden Costs

Hostinger offers competitive pricing across its hosting plans, but understanding the full cost involves more than just the initial promo rates. This article breaks down Hostinger's pricing structure, renewal rates, and potential hidden costs to help you make an informed decision.

Khaled S. 8 min read