Pre-Migration Planning: Inventory Everything

Migrations fail not because the transfer itself is hard, but because something gets forgotten. Before you touch a single file, create a complete inventory of your current server.

File and Application Inventory

# List all websites and applications
ls -la /var/www/

# Find all document roots in Nginx
grep -r "root " /etc/nginx/sites-enabled/

# Or in Apache
grep -r "DocumentRoot" /etc/apache2/sites-enabled/

# Check for cron jobs (ALL users)
for user in $(cut -d: -f1 /etc/passwd); do
  crontab -l -u "$user" 2>/dev/null | grep -v '^#' | grep -v '^$' && echo "--- User: $user ---"
done

# List running services
systemctl list-units --type=service --state=running

# Check disk usage per directory
du -h --max-depth=2 /var/www/ | sort -rh | head -20

Database Inventory

# List all databases
mysql -e "SHOW DATABASES;"

# List database sizes
mysql -e "SELECT table_schema AS 'Database', 
  ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Size (MB)' 
  FROM information_schema.tables GROUP BY table_schema ORDER BY 
  SUM(data_length + index_length) DESC;"

# List database users
mysql -e "SELECT user, host FROM mysql.user;"

DNS and SSL Inventory

# Record current DNS settings (do this for EVERY domain)
dig yourdomain.com A +short
dig yourdomain.com MX +short
dig yourdomain.com TXT +short
dig yourdomain.com CNAME +short
dig www.yourdomain.com A +short

# Check SSL certificate details
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null | \
  openssl x509 -noout -dates -issuer

# List all Let's Encrypt certificates
certbot certificates

Save this entire inventory in a document. You will reference it throughout the migration.

Step 1: Lower DNS TTL (48 Hours Before Migration)

This step is critical and must happen well before the actual migration. DNS records have a TTL (Time To Live) that tells resolvers how long to cache the record. If your TTL is 86400 (24 hours), changing the DNS record means some users will still reach the old server for up to 24 hours.

# Log into your DNS provider and set TTL to 300 seconds (5 minutes)
# for these records:
# - A record for yourdomain.com
# - A record for www.yourdomain.com
# - A record for any other subdomains (mail, api, etc.)
#
# Wait at least 48 hours for the old TTL to expire everywhere
# before proceeding with the migration

# Verify the new TTL is propagated
dig yourdomain.com +noall +answer
# Look for the TTL value (second column) - should show 300

Do not skip this step. A 48-hour wait with low TTL means your DNS cutover will propagate in minutes instead of hours or days.

Step 2: Choose Your Destination Tier

Your migration is an opportunity to right-size your hosting. Where you are coming from determines the best destination:

From Shared Hosting → Cloud VPS

If you are leaving shared hosting (GoDaddy, Bluehost, HostGator, etc.) because you have outgrown the resource limits, a MassiveGRID Cloud VPS starting at $1.99/mo is the natural next step. You get root access, your choice of OS, and resources that scale independently. The performance improvement over shared hosting will be immediate and dramatic.

From Another VPS Provider → Cloud VDS

If you are leaving DigitalOcean, Vultr, Hetzner, or Linode because of noisy neighbors, inconsistent performance, or lack of high availability, a MassiveGRID Cloud VDS from $8.30/mo gives you dedicated CPU and RAM on Proxmox HA clusters with Ceph NVMe storage. No more shared resource lottery.

From Self-Managing → Managed Dedicated

If you are migrating because you are tired of being the sysadmin, MassiveGRID Managed Cloud Dedicated Servers handle everything. You hand over the operational burden and focus on your business. The migration itself can be assisted by MassiveGRID's support team.

Step 3: Set Up the Destination Server

Deploy your new server and configure the base environment before transferring any data:

# After deploying your MassiveGRID VPS/VDS, connect via SSH
ssh root@new-server-ip

# Update the system
apt update && apt upgrade -y

# Install your web stack (example: Nginx + PHP + MySQL)
apt install -y nginx mysql-server php8.3-fpm php8.3-mysql \
  php8.3-curl php8.3-gd php8.3-mbstring php8.3-xml php8.3-zip

# Configure the firewall
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow 'Nginx Full'
ufw enable

# Set the timezone
timedatectl set-timezone UTC

# Configure fail2ban
apt install -y fail2ban
systemctl enable fail2ban

Match the software versions on your new server to the old one as closely as possible. PHP version mismatches are the most common cause of post-migration issues.

Step 4: Transfer Files with rsync

rsync is the best tool for migration because it is incremental—it only transfers changes after the initial sync. This means you can do a first sync while the old server is still live, then a quick final sync during cutover.

Initial Sync (While Old Server Is Still Live)

# From the NEW server, pull files from the old server
# Dry run first to see what will be transferred
rsync -avz --dry-run --progress \
  --exclude '.cache' \
  --exclude 'tmp/' \
  --exclude '*.log' \
  --exclude 'node_modules/' \
  --exclude '.git/' \
  root@old-server-ip:/var/www/ /var/www/

# If the dry run looks correct, run the actual transfer
rsync -avz --progress \
  --exclude '.cache' \
  --exclude 'tmp/' \
  --exclude '*.log' \
  --exclude 'node_modules/' \
  --exclude '.git/' \
  root@old-server-ip:/var/www/ /var/www/

# Also transfer configuration files
rsync -avz root@old-server-ip:/etc/nginx/sites-available/ /etc/nginx/sites-available/
rsync -avz root@old-server-ip:/etc/nginx/sites-enabled/ /etc/nginx/sites-enabled/
rsync -avz root@old-server-ip:/etc/php/ /etc/php/

Important: The trailing slash on source paths matters in rsync. /var/www/ copies the contents of the directory. /var/www (no slash) copies the directory itself.

Transfer Cron Jobs

# Export cron jobs from old server
ssh root@old-server-ip 'crontab -l' > /tmp/old-crontab.txt

# Review and install on new server (check paths first!)
cat /tmp/old-crontab.txt
# Edit paths if needed, then:
crontab /tmp/old-crontab.txt

Step 5: Database Migration

Database migration requires care to avoid data loss. For MySQL/MariaDB:

Export from Old Server

# Export all databases (on old server)
mysqldump --all-databases --routines --triggers --events \
  --single-transaction --quick --lock-tables=false \
  > /tmp/all-databases.sql

# Or export specific databases
mysqldump --single-transaction --quick --lock-tables=false \
  --routines --triggers \
  database_name > /tmp/database_name.sql

# Check the dump file size
ls -lh /tmp/*.sql

# Transfer to new server
rsync -avz --progress /tmp/all-databases.sql root@new-server-ip:/tmp/

Import on New Server

# Import the database
mysql < /tmp/all-databases.sql

# Or for a specific database
mysql database_name < /tmp/database_name.sql

# Recreate database users with their passwords
# (check the inventory you made earlier)
mysql -e "CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'secure-password';"
mysql -e "GRANT ALL PRIVILEGES ON database_name.* TO 'appuser'@'localhost';"
mysql -e "FLUSH PRIVILEGES;"

# Verify tables were imported correctly
mysql -e "USE database_name; SHOW TABLES;" | wc -l

For Large Databases

If your database is larger than 1 GB, use compression and pipe the transfer directly:

# Compressed direct transfer (no intermediate file)
ssh root@old-server-ip \
  "mysqldump --single-transaction --quick database_name | gzip" | \
  gunzip | mysql database_name

Step 6: Update Application Configuration

After transferring files and databases, update configuration files that reference the old server:

# Common changes needed:
# - Database host (if changed from remote to localhost)
# - File paths (if directory structure differs)
# - Domain URLs (in WordPress wp-config.php, .env files, etc.)

# WordPress example
# wp-config.php - usually just verify these are correct:
# define('DB_HOST', 'localhost');
# define('DB_NAME', 'wordpress');
# define('DB_USER', 'wpuser');
# define('DB_PASSWORD', 'password');

# Laravel/PHP example
# .env file:
# DB_HOST=127.0.0.1
# DB_DATABASE=myapp
# DB_USERNAME=appuser
# DB_PASSWORD=password
# APP_URL=https://yourdomain.com

# Fix file permissions
chown -R www-data:www-data /var/www/yourdomain.com/
find /var/www/yourdomain.com/ -type d -exec chmod 755 {} \;
find /var/www/yourdomain.com/ -type f -exec chmod 644 {} \;

Step 7: SSL Certificate Setup

Set up SSL on the new server before the DNS cutover so HTTPS works immediately:

# Install Certbot
apt install -y certbot python3-certbot-nginx

# Option 1: Get a certificate using DNS validation (works before DNS cutover)
certbot certonly --manual --preferred-challenges dns \
  -d yourdomain.com -d www.yourdomain.com

# Option 2: If you want to use HTTP validation after DNS cutover
# (do this AFTER changing DNS, or use the hosts file trick below)
certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Set up auto-renewal
systemctl enable certbot.timer
certbot renew --dry-run

Step 8: Test Before DNS Cutover

This is the most important step. Verify everything works on the new server before sending real traffic to it.

The Hosts File Trick

# On your LOCAL computer (not the server), edit your hosts file
# to point your domain at the new server IP

# macOS/Linux: /etc/hosts
# Windows: C:\Windows\System32\drivers\etc\hosts
# Add these lines:
# NEW_SERVER_IP yourdomain.com
# NEW_SERVER_IP www.yourdomain.com

# Now open yourdomain.com in your browser
# You are viewing the new server while everyone else still sees the old one

Testing with curl

# Test HTTP response from the new server specifically
curl -H "Host: yourdomain.com" http://NEW_SERVER_IP/ -I

# Test HTTPS with the --resolve flag (bypasses DNS)
curl --resolve yourdomain.com:443:NEW_SERVER_IP https://yourdomain.com/ -I

# Check for correct content (look for your site title or specific text)
curl --resolve yourdomain.com:443:NEW_SERVER_IP https://yourdomain.com/ 2>/dev/null | \
  grep -i ""</code></pre>

<h3>Pre-Cutover Verification Checklist</h3>

<ul>
<li>Homepage loads correctly with all styles and images</li>
<li>Key internal pages work (test 5–10 important URLs)</li>
<li>Forms submit successfully</li>
<li>Login/authentication works</li>
<li>Database queries return correct data</li>
<li>SSL certificate is valid and trusted</li>
<li>Email delivery works (if the server sends email)</li>
<li>Cron jobs are configured and running</li>
<li>File upload functionality works</li>
<li>Third-party integrations respond (payment gateways, APIs)</li>
</ul>

<h2>Step 9: DNS Cutover</h2>

<p>Once testing confirms everything works, perform the DNS cutover:</p>

<pre><code># 1. Do a final rsync to capture any changes since the initial sync
rsync -avz --progress --delete \
  --exclude '.cache' \
  --exclude 'tmp/' \
  --exclude '*.log' \
  root@old-server-ip:/var/www/ /var/www/

# 2. Do a final database sync
# On old server: export latest data
ssh root@old-server-ip "mysqldump --single-transaction --quick database_name | gzip" | \
  gunzip | mysql database_name

# 3. Update DNS A records to point to the new server IP
# (do this in your DNS provider's control panel)
# Change: yourdomain.com A -> NEW_SERVER_IP
# Change: www.yourdomain.com A -> NEW_SERVER_IP

# 4. Monitor both old and new servers
# On OLD server: watch for diminishing traffic
tail -f /var/log/nginx/access.log

# On NEW server: watch for incoming traffic
tail -f /var/log/nginx/access.log</code></pre>

<p>Because you lowered the TTL to 300 seconds 48 hours ago, most DNS resolvers will pick up the change within 5–10 minutes. Some edge caches may take up to an hour.</p>

<h3>Post-Cutover: Keep the Old Server Running</h3>

<p>Do not shut down the old server immediately. Keep it running for at least 48–72 hours as a safety net. Some DNS resolvers may cache the old IP longer than expected, and you may need to fall back if an issue is discovered.</p>

<h2>Step 10: Post-Migration Verification</h2>

<p>After DNS has propagated (give it at least 2 hours), verify from multiple locations:</p>

<pre><code># Verify DNS propagation
dig yourdomain.com +short
# Should return your NEW server IP

# Check from multiple DNS resolvers
dig @8.8.8.8 yourdomain.com +short     # Google
dig @1.1.1.1 yourdomain.com +short     # Cloudflare
dig @208.67.222.222 yourdomain.com +short  # OpenDNS

# Verify SSL
curl -vI https://yourdomain.com 2>&1 | grep -E "subject:|expire date:|issuer:"

# Check Google's view
curl -s "https://dns.google/resolve?name=yourdomain.com&type=A" | python3 -m json.tool

# Run a full site crawl to check for broken links
# (install linkchecker)
apt install linkchecker
linkchecker https://yourdomain.com/ --check-extern --no-robots</code></pre>

<h3>Post-Migration Checklist</h3>

<table>
<thead>
<tr><th>Check</th><th>How to Verify</th><th>Status</th></tr>
</thead>
<tbody>
<tr><td>DNS resolves to new IP</td><td><code>dig yourdomain.com +short</code></td><td>□</td></tr>
<tr><td>HTTPS works correctly</td><td>Browser padlock, <code>curl -I https://</code></td><td>□</td></tr>
<tr><td>All pages load without errors</td><td>Browser console, link checker</td><td>□</td></tr>
<tr><td>Database queries work</td><td>Test application functionality</td><td>□</td></tr>
<tr><td>Forms and submissions work</td><td>Submit a test form</td><td>□</td></tr>
<tr><td>Email delivery works</td><td>Send a test email from the server</td><td>□</td></tr>
<tr><td>Cron jobs are running</td><td><code>crontab -l</code>, check log output</td><td>□</td></tr>
<tr><td>Backups are configured</td><td>Verify backup schedule and test restore</td><td>□</td></tr>
<tr><td>Monitoring is active</td><td>Check Netdata/Uptime Kuma dashboards</td><td>□</td></tr>
<tr><td>Old server still accessible (fallback)</td><td>SSH to old server IP</td><td>□</td></tr>
<tr><td>Google Search Console updated</td><td>Verify property, check for crawl errors</td><td>□</td></tr>
<tr><td>CDN/Cloudflare updated (if applicable)</td><td>Verify origin IP in CDN settings</td><td>□</td></tr>
</tbody>
</table>

<h2>You Just Upgraded to High Availability</h2>

<p>If you migrated from a standard VPS provider (DigitalOcean, Vultr, Hetzner, Linode) to MassiveGRID, your hosting now includes something most providers charge extra for or do not offer at all: high availability.</p>

<p>On MassiveGRID's infrastructure:</p>

<ul>
<li><strong>Proxmox HA clusters</strong> automatically restart your VM on a healthy node if the host server fails. No manual intervention, no ticket needed.</li>
<li><strong>Ceph 3x replicated NVMe storage</strong> means your data exists on three separate storage nodes simultaneously. A disk failure or entire storage node going offline does not affect your data.</li>
<li><strong>12 Tbps DDoS protection</strong> filters malicious traffic before it reaches your server, included at no extra cost.</li>
<li><strong>100% uptime SLA</strong> backed by credits—MassiveGRID is confident enough in the infrastructure to guarantee it.</li>
</ul>

<p>On your previous provider, a single hardware failure could mean hours of downtime while support migrates your VM. On MassiveGRID, failover is automatic and transparent. Your server restarts on a new node in minutes, with all data intact.</p>

<p>This is a fundamental improvement in reliability, and it happened as a side effect of your migration. No additional configuration needed.</p>

<h2>After the Migration: What Comes Next</h2>

<p>With the migration complete, take time to optimize your new environment. The performance characteristics of MassiveGRID's infrastructure (NVMe storage, modern CPUs, uncongested network) may differ significantly from your previous provider. You may find that database queries run faster, page load times drop, and you have more headroom than expected.</p>

<p>Consider setting up proper monitoring, implementing automated backups, and tuning your application for the new environment. And as your project grows, remember you can independently scale CPU, RAM, and storage without migrating again—whether you are on a <a href="https://www.massivegrid.com/vps/">Cloud VPS</a>, <a href="https://www.massivegrid.com/vds/">Cloud VDS</a>, or <a href="https://www.massivegrid.com/high-availability-cloud-dedicated-servers/">Managed Cloud Dedicated</a>.</p>

<blockquote>
<p><strong>MassiveGRID Ubuntu VPS includes:</strong> Ubuntu 24.04 LTS pre-installed · Proxmox HA cluster with automatic failover · Ceph 3x replicated NVMe storage · Independent CPU/RAM/storage scaling · 12 Tbps DDoS protection · 4 global datacenter locations · 100% uptime SLA · 24/7 human support rated 9.5/10</p>
<p>→ <a href="https://www.massivegrid.com/vps/">Deploy a self-managed VPS</a> — from $1.99/mo<br>
→ <a href="https://www.massivegrid.com/vds/">Need dedicated resources?</a> — from $8.30/mo<br>
→ <a href="https://www.massivegrid.com/high-availability-cloud-dedicated-servers/">Want fully managed hosting?</a> — we handle everything</p>
</blockquote>
        </article>

        <div class="post-bottom-nav">
            <a href="../" class="back-to-blog">
                <svg aria-hidden="true" viewBox="0 0 24 24"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
                Back to Blog
            </a>
        </div>
    </main>

    <footer>
        <div class="footer-top">
            <div class="footer-brand">
                <a href="../../" class="logo"><img loading="lazy" src="../../MG_logo.svg" alt="MassiveGRID" width="257" height="42"></a>
                <span class="footer-tagline">The Secure High-Availability Cloud Provider</span>
                <div class="footer-social">
                    <a href="https://www.facebook.com/MassiveGRID/" target="_blank" rel="noopener noreferrer" aria-label="Facebook"><svg aria-hidden="true" viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg></a>
                    <a href="https://x.com/MassiveGRID" target="_blank" rel="noopener noreferrer" aria-label="X (Twitter)"><svg aria-hidden="true" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg></a>
                    <a href="https://www.linkedin.com/company/MassiveGRID/" target="_blank" rel="noopener noreferrer" aria-label="LinkedIn"><svg aria-hidden="true" viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg></a>
                </div>
                <div class="footer-badges">
                    <span class="footer-badge"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> ISO 9001</span>
                    <span class="footer-badge"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> GDPR</span>
                </div>
            </div>
            <div class="footer-col"><div class="footer-col-title">Products</div>
                <a href="../../cloud-servers/">Cloud Servers</a><a href="../../vps/">Cloud VPS</a><a href="../../vds/">Dedicated VPS</a><a href="../../high-availability-cloud-servers/">Managed Cloud Servers</a><a href="../../high-availability-cpanel-hosting/">cPanel Hosting</a><a href="../../paas/">PaaS</a></div>
            <div class="footer-col"><div class="footer-col-title">Infrastructure</div>
                <a href="../../overview-security/">Security</a><a href="../../our-technology/">Technology</a><a href="../../data-centers/">Data Centers</a><a href="../../network/">Network</a><a href="../../high-availability/">High Availability</a><a href="../../compare/">Compare</a></div>
            <div class="footer-col"><div class="footer-col-title">Company</div>
                <a href="../../the-company/">About Us</a><a href="../../contact-us/">Contact</a><a href="../">Blog</a><a href="../../partners-resellers/">Partners</a><a href="../../support-plans/">Support</a></div>
            <div class="footer-col"><div class="footer-col-title">Legal</div>
                <a href="../../terms-of-service/">Terms of Service</a><a href="../../data-privacy-cookies-policy/">Privacy Policy</a><a href="../../gdpr-compliance/">GDPR Compliance</a></div>
        </div>
        <div class="footer-bottom">
            <div class="footer-copy">© 2026 MassiveGRID. All rights reserved.</div>
        </div>
    </footer>

    <script defer src="../../js/common.js"></script>
</body>
</html>