How to Migrate From a Dedicated Server to Cloud Without Breaking Your Stack

Written by:

·

Last Updated on:

·

HostingGuider uses affiliate links. We may earn a commission if you purchase through them, at no extra cost to you.

Migrating from a shared host to a VPS is relatively straightforward. The configuration footprint is small.

Migrating from a dedicated server to cloud is a different challenge entirely.

Dedicated servers accumulate complexity over years. Configuration files from half a decade ago that nobody remembers the purpose of. Cron jobs added by three different developers. PHP extensions compiled from source. A mail server that sends 50,000 transactional emails per month. An application that hardcodes the server IP. A MySQL configuration tuned over hundreds of hours that nobody documented.

Moving all of this to cloud without breaking anything requires a methodical audit before a single file is transferred. Most migration failures happen because teams skip this phase and discover dependencies only after the old server is gone.

This guide starts with the audit. The audit takes longer than the migration itself. That is not a bug. That is the reason this migration succeeds.

Key Takeaways

  • The pre-migration audit is the most important phase. It takes longer than the actual migration
  • Dedicated servers accumulate years of undocumented configuration. You must discover all of it before moving
  • Match software versions exactly on the cloud server before transferring anything
  • Cloud networking is virtualised. IP addresses, routing, and firewall behave differently from dedicated hardware
  • Self-hosted mail is the hardest component to migrate. Consider external mail services instead
  • The dedicated server must stay live for at least 72 hours after DNS switch
  • Every dependency that is not documented before migration is a potential outage during it

Understanding the Architecture Shift

A dedicated server gives you one physical machine. You own every CPU cycle. You own every byte of RAM. Storage is local hardware. Networking is a physical NIC connected to physical switches.

Cloud gives you a virtualised slice of shared infrastructure. CPU, RAM, storage, and network are all abstracted. This abstraction brings flexibility but introduces differences that matter during migration.

FactorDedicated ServerCloud Instance
HardwareSingle physical machineVirtualised slice of shared hardware
CPUAll cores dedicated to youvCPUs allocated from a pool
RAMAll RAM dedicated to youRAM allocated from a pool
StorageLocal SATA/SAS/SSDLocal NVMe or network-attached block
NetworkPhysical NIC, fixed bandwidthVirtual NIC, variable bandwidth
IP addressesPersistent, tied to hardwarePersistent IPs must be explicitly reserved
RebootsHardware restart sequenceFast virtual machine restart
CPU stealNone (your hardware)Possible on oversubscribed hosts
MaintenanceProvider replaces failed hardwareInstance migrated to new hardware

The difference between cloud and traditional hosting explains these architectural differences in detail. Understanding them helps you anticipate what will behave differently after migration.

Dedicated server versus cloud instance architecture comparison infographic
Comparison of dedicated hardware and virtualized cloud infrastructure

Phase 0: The Pre-Migration Audit

This phase cannot be skipped or shortened. Every hour spent here prevents hours of incident response later.

Step 0.1: Document All Running Services

On your dedicated server, run:

sudo systemctl list-units --type=service --state=running

Save this output to a file:

sudo systemctl list-units --type=service --state=running > /root/migration/running-services.txt

Create the migration notes directory:

mkdir -p /root/migration

This list is your migration checklist. Every service running on the dedicated server must be accounted for on the cloud instance.

Common services found on dedicated servers that teams forget to migrate:

  • postfix or exim4 (mail transfer agent)
  • dovecot (mail delivery agent / IMAP server)
  • spamassassin or rspamd (spam filtering)
  • opendkim (email authentication)
  • fail2ban (intrusion prevention)
  • memcached (object caching)
  • redis-server (object caching or queuing)
  • elasticsearch (search engine)
  • varnish (HTTP cache)
  • cron (scheduled tasks)
  • proftpd or vsftpd (FTP server)
  • ntp or chrony (time synchronisation)
  • snmpd (monitoring)
  • monit or supervisor (process monitoring)

Step 0.2: Capture All Software Versions

Run this to capture exact versions of every critical piece of software:

cat > /root/migration/versions.txt << 'EOF'
=== OS ===
EOF
lsb_release -a >> /root/migration/versions.txt
uname -r >> /root/migration/versions.txt

cat >> /root/migration/versions.txt << 'EOF'

=== Web Server ===
EOF
nginx -v 2>> /root/migration/versions.txt
apache2 -v 2>> /root/migration/versions.txt

cat >> /root/migration/versions.txt << 'EOF'

=== PHP ===
EOF
php -v >> /root/migration/versions.txt
php -m >> /root/migration/versions.txt

cat >> /root/migration/versions.txt << 'EOF'

=== Database ===
EOF
mysql --version >> /root/migration/versions.txt 2>&1
mariadb --version >> /root/migration/versions.txt 2>&1

cat >> /root/migration/versions.txt << 'EOF'

=== Other ===
EOF
redis-server --version >> /root/migration/versions.txt 2>&1
memcached --version >> /root/migration/versions.txt 2>&1
cat /root/migration/versions.txt

Pay special attention to the PHP version and MySQL version. These two are the most common source of post-migration breakage.

The PHP version trap: Dedicated servers that have been running for years often have PHP 7.1, 7.2, or 7.4. Many plugins depend on deprecated PHP functions removed in PHP 8.x. If you upgrade PHP during the migration, plugins break. Match the PHP version first, then upgrade separately after the migration stabilises.

The MySQL version trap: Dedicated servers often run MySQL 5.7. Cloud defaults often install MySQL 8.0 or MariaDB 10.6. SQL mode differences between these versions can break applications that worked perfectly on the old setup.

Step 0.3: Capture All PHP Configuration

php -i > /root/migration/php-config.txt

Also capture the php.ini file:

php --ini | grep "Loaded Configuration File"

Copy that php.ini path and back it up:

cp /etc/php/7.4/apache2/php.ini /root/migration/php.ini.backup
cp /etc/php/7.4/cli/php.ini /root/migration/php-cli.ini.backup

Custom php.ini values that applications commonly depend on and that have non-default settings:

grep -E "upload_max_filesize|post_max_size|memory_limit|max_execution_time|max_input_vars|date.timezone|session.gc_maxlifetime|opcache" /etc/php/7.4/apache2/php.ini | grep -v "^;"

Step 0.4: Capture All Web Server Configuration

For Apache:

apache2ctl -S > /root/migration/apache-vhosts.txt
cp -r /etc/apache2/ /root/migration/apache-config/

For Nginx:

nginx -T > /root/migration/nginx-config-full.txt
cp -r /etc/nginx/ /root/migration/nginx-config/

Step 0.5: Capture All Cron Jobs

Cron jobs added over years are routinely forgotten during migrations. Run:

# Root cron jobs
sudo crontab -l > /root/migration/crontab-root.txt

# All user cron jobs
for user in $(cut -f1 -d: /etc/passwd); do
  crontab -u $user -l 2>/dev/null && echo "=== $user ===" >> /root/migration/crontab-all-users.txt
done

# System cron directories
ls -la /etc/cron.d/ > /root/migration/cron-d-files.txt
ls -la /etc/cron.daily/ >> /root/migration/cron-d-files.txt
ls -la /etc/cron.hourly/ >> /root/migration/cron-d-files.txt
ls -la /etc/cron.weekly/ >> /root/migration/cron-d-files.txt

Read every cron job carefully. Some will be critical (backup jobs, cache warmers, monitoring scripts). Some will be obsolete (jobs referencing software that no longer exists). Document what each one does before deciding whether to migrate it.

Step 0.6: Discover Hardcoded IP Addresses

Applications that hardcode the dedicated server IP address will break after migration.

Search your application code:

grep -r "YOUR.DEDICATED.IP" /var/www/ --include="*.php" -l
grep -r "YOUR.DEDICATED.IP" /var/www/ --include="*.conf" -l
grep -r "YOUR.DEDICATED.IP" /etc/nginx/ -l
grep -r "YOUR.DEDICATED.IP" /etc/apache2/ -l

Replace YOUR.DEDICATED.IP with your actual server IP. Any file that appears needs editing before or after migration.

Also check for hostname references:

hostname
hostname -f
grep -r "$(hostname)" /var/www/ --include="*.php" -l

Step 0.7: Capture Database Sizes and Engines

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;
"

Also check for non-InnoDB tables that may not transfer cleanly:

mysql -e "
SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE
FROM information_schema.TABLES
WHERE ENGINE != 'InnoDB'
AND TABLE_SCHEMA NOT IN ('mysql','performance_schema','information_schema')
ORDER BY TABLE_SCHEMA, TABLE_NAME;
"

Any MyISAM tables need attention. MyISAM does not support transactions. Conversion to InnoDB before migration reduces risk.

Step 0.8: Capture SSL Certificates

List all SSL certificates:

find /etc/ssl /etc/letsencrypt -name "*.crt" -o -name "*.pem" 2>/dev/null | head -30

For Let’s Encrypt:

sudo certbot certificates

For manually installed certificates:

find /etc -name "*.crt" -exec openssl x509 -noout -subject -dates -in {} \; 2>/dev/null

Capture which domains have certificates and when they expire.

Step 0.9: Capture Mail Server Configuration (If Applicable)

If your dedicated server sends or receives email:

postconf -n > /root/migration/postfix-config.txt
cat /etc/postfix/main.cf >> /root/migration/postfix-config.txt
ls -la /etc/postfix/ >> /root/migration/postfix-config.txt

Be honest about mail server migration complexity. Self-hosted mail is the hardest component to migrate. IP reputation takes months to build. A new cloud IP with no sending history goes straight to spam folders.

The strong recommendation for most migrations is to move email to an external service:

Moving mail to external services removes it from the scope of the server migration entirely.

Step 0.10: Document All Open Ports

sudo ss -tlnp > /root/migration/open-ports.txt
sudo netstat -tlnp 2>/dev/null >> /root/migration/open-ports.txt

Every open port represents a service. Every service must be either migrated or intentionally dropped.

Step 0.11: Establish Performance Baseline

Run performance benchmarks before migration. These are your reference numbers for verifying the cloud instance performs equivalently.

# CPU benchmark
sysbench cpu --threads=$(nproc) --time=30 run | grep "events per second" > /root/migration/benchmarks.txt

# Disk benchmark
fio --name=randread --ioengine=libaio --rw=randread --bs=4k --direct=1 --size=1G --numjobs=4 --runtime=30 --group_reporting | grep "IOPS" >> /root/migration/benchmarks.txt

# Record site response time
curl -w "TTFB: %{time_starttransfer}s | Total: %{time_total}s\n" -o /dev/null -s https://yourdomain.com >> /root/migration/benchmarks.txt

Save the benchmarks file. After migration, you run the same tests on the cloud instance and compare.

Phase 1: Choose the Cloud Architecture

Lift and Shift vs Re-Architecture

You have two approaches to this migration.

Lift and shift: Move exactly what you have to the cloud. Same OS. Same software versions. Same configuration. The cloud instance looks identical to the dedicated server when done.

Benefits: Lowest risk. Fastest migration. No application changes needed. Downsides: You carry forward all the technical debt. You miss the opportunity to modernise.

Re-architecture: Rebuild the server from scratch with modern software. Update PHP to the latest version. Migrate from MySQL 5.7 to MySQL 8.0. Move from Apache to Nginx. Use managed cloud services where appropriate.

Benefits: Clean slate. Modern software. Better performance. Security improvements. Downsides: Higher risk. Application compatibility must be tested. Longer timeline.

Recommendation: For most migrations, start with lift and shift. Get to a stable cloud environment first. Then upgrade software versions one at a time after migration. Doing both simultaneously multiplies risk.

Choosing the Right Cloud Provider

ProviderBest ForManagement Level
CloudwaysWordPress and PHP apps, managed experienceManaged cloud
ScalaHostingMultiple sites, managed VPSManaged cloud VPS
DigitalOcean, Linode, VultrTechnical users, full controlUnmanaged VPS
AWS EC2, GCP Compute EngineComplex architectures, enterprise scaleUnmanaged cloud
KinstaWordPress specifically, premium performanceFully managed WordPress

For teams migrating from dedicated server to cloud, Cloudways is often the best balance. You get cloud infrastructure without managing the server yourself. Their platform handles updates, security, and backups. You manage applications.

Sizing the Cloud Instance

Do not downsize during a migration. Match or exceed dedicated server resources.

Your dedicated server has fixed, guaranteed resources. Your cloud instance shares hardware. To match the same real-world performance, provision more vCPU and RAM than the dedicated server’s raw specs suggest.

Rule of thumb for lift and shift:

  • Match RAM exactly (4GB dedicated → 4GB cloud minimum)
  • Double the vCPU count versus dedicated physical cores (4 dedicated cores → 8 vCPUs minimum)
  • Match or exceed storage with NVMe

You can downsize after running for a month and observing actual resource utilisation. Downsizing during migration is a risk you do not need.

Phase 2: Build the Cloud Environment

Step 2.1: Provision the Cloud Instance

Provision your cloud instance before starting data transfer.

If using an unmanaged VPS, follow the complete VPS setup guide to establish the baseline configuration.

The instance needs these installed and configured before data arrives:

  • Same OS version as dedicated server (or one major version up if tested)
  • Same PHP version (exactly matching)
  • Same database version (MySQL 5.7 → MySQL 5.7, not 8.0)
  • Same web server and major version

Step 2.2: Install the Exact PHP Version

If your dedicated server runs PHP 7.4 and the cloud distro defaults to PHP 8.1, install 7.4 explicitly:

sudo apt install -y software-properties-common
sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install php7.4 php7.4-mysql php7.4-curl php7.4-gd php7.4-mbstring php7.4-xml php7.4-zip php7.4-intl php7.4-imagick php7.4-bcmath php7.4-soap -y

Install every PHP module you captured in Step 0.3 of the audit.

Verify the version:

php7.4 --version

Step 2.3: Install the Exact Database Version

MySQL 5.7 is not the default on modern Ubuntu. Pin it explicitly:

wget https://dev.mysql.com/get/mysql-apt-config_0.8.22-1_all.deb
sudo dpkg -i mysql-apt-config_0.8.22-1_all.deb

Select MySQL 5.7 from the interactive menu. Then:

sudo apt update
sudo apt install mysql-server-5.7 -y

For MariaDB, specify the exact version:

curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version=mariadb-10.5
sudo apt install mariadb-server -y

Step 2.4: Apply All PHP Configuration

On the cloud instance, create a custom PHP configuration file that matches your audit output:

sudo nano /etc/php/7.4/apache2/conf.d/99-migration.ini

Paste your captured PHP settings:

; Migrated from dedicated server
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 512M
max_execution_time = 300
max_input_vars = 5000
date.timezone = UTC
session.gc_maxlifetime = 1440
opcache.enable = 1
opcache.memory_consumption = 256

Adjust values to match what you captured in Step 0.3.

Step 2.5: Set Up the Database on Cloud

Create databases and users that match the dedicated server:

sudo mysql
CREATE DATABASE app_database CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'strong_unique_password';
GRANT ALL PRIVILEGES ON app_database.* TO 'app_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Use the same database name and username as the dedicated server. This avoids updating application config files.

Step 2.6: Configure the Firewall

sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable

Do NOT open port 3306 (MySQL) publicly. Database connections should always stay on localhost or internal network only.

Phase 3: Transfer the Data

Step 3.1: Set Up SSH Key Access Between Servers

From the dedicated server, generate an SSH key for the migration:

ssh-keygen -t ed25519 -f ~/.ssh/migration_key -C "dedicated-to-cloud-migration"

Copy the public key to the cloud instance:

ssh-copy-id -i ~/.ssh/migration_key.pub root@CLOUD_INSTANCE_IP

Test the connection:

ssh -i ~/.ssh/migration_key root@CLOUD_INSTANCE_IP echo "connection works"

Step 3.2: Run the Initial File Transfer

Use rsync for an efficient, interruptible transfer. Run it inside screen or tmux so it survives SSH disconnection:

sudo apt install screen -y
screen -S migration

Run the initial rsync:

rsync -avzP \
  --exclude="/proc/*" \
  --exclude="/sys/*" \
  --exclude="/dev/*" \
  --exclude="/run/*" \
  --exclude="/tmp/*" \
  --exclude="/var/log/*" \
  --exclude="/var/cache/*" \
  --exclude="/var/tmp/*" \
  --exclude="/root/migration/*" \
  -e "ssh -i ~/.ssh/migration_key" \
  /var/www/ \
  root@CLOUD_INSTANCE_IP:/var/www/

For a large site (50GB+), this initial sync takes hours. The screen session keeps it running even if your SSH connection drops.

Detach from screen: Press Ctrl+A then D.

Reconnect to screen anytime:

screen -r migration

Step 3.3: Transfer Web Server Configuration

Transfer Apache configuration:

rsync -avzP -e "ssh -i ~/.ssh/migration_key" \
  /etc/apache2/ \
  root@CLOUD_INSTANCE_IP:/etc/apache2/

Or Nginx:

rsync -avzP -e "ssh -i ~/.ssh/migration_key" \
  /etc/nginx/ \
  root@CLOUD_INSTANCE_IP:/etc/nginx/

On the cloud instance, test the transferred configuration:

sudo apache2ctl configtest

or:

sudo nginx -t

Fix any errors before proceeding. Configuration transfers sometimes include paths that exist on the dedicated server but not yet on the cloud instance.

Step 3.4: Export the Database From Dedicated Server

Run a full database dump:

mysqldump \
  --all-databases \
  --single-transaction \
  --quick \
  --lock-tables=false \
  --routines \
  --triggers \
  --events \
  --add-drop-database \
  --add-drop-table \
  -u root -p \
  | gzip > /root/migration/full-database-$(date +%Y%m%d).sql.gz

The --all-databases flag exports everything. Use this for the initial transfer. For the final sync on cutover day, you can export specific databases individually for speed.

Check the export file:

ls -lh /root/migration/full-database-*.sql.gz

The size should be roughly 10-30% of the total uncompressed database size.

Step 3.5: Transfer and Import the Database

Transfer to cloud:

rsync -avzP \
  -e "ssh -i ~/.ssh/migration_key" \
  /root/migration/full-database-*.sql.gz \
  root@CLOUD_INSTANCE_IP:/root/migration/

On the cloud instance, import:

gunzip /root/migration/full-database-*.sql.gz

mysql -u root -p < /root/migration/full-database-*.sql

This takes significant time for large databases. For a 10GB database, expect 10-30 minutes on fast NVMe.

Verify the import:

mysql -e "SHOW DATABASES;"
mysql -e "SELECT COUNT(*) FROM your_main_database.your_main_table;"

Compare the table count against the dedicated server.

Phase 4: Configure the Cloud Environment

Step 4.1: Restore Cron Jobs

On the cloud instance, add back all cron jobs from your audit:

sudo crontab -e

Paste the root cron jobs from /root/migration/crontab-root.txt, updating any hardcoded paths as needed.

For system cron directories:

# Check what was in /etc/cron.d/ on dedicated server
# Create matching files on cloud
sudo cp /root/migration-docs/cron-d/* /etc/cron.d/
sudo chmod 644 /etc/cron.d/*

Step 4.2: Configure Redis or Memcached

If your dedicated server ran Redis:

sudo apt install redis-server -y
sudo systemctl enable redis-server
sudo systemctl start redis-server

Copy the Redis configuration from your audit:

# Transfer Redis config from dedicated server
rsync -avzP \
  -e "ssh -i ~/.ssh/migration_key" \
  /etc/redis/redis.conf \
  root@CLOUD_INSTANCE_IP:/etc/redis/redis.conf

sudo systemctl restart redis-server

Step 4.3: Set Correct File Permissions

File permissions from rsync transfer should match the source. Verify web files are owned correctly:

sudo chown -R www-data:www-data /var/www/
sudo find /var/www -type d -exec chmod 755 {} \;
sudo find /var/www -type f -exec chmod 644 {} \;

For WordPress specifically:

sudo find /var/www/yourdomain.com -name "wp-config.php" -exec chmod 640 {} \;

Step 4.4: Configure SSL Certificates

For Let’s Encrypt certificates, get new ones on the cloud instance after DNS switch. Configure Certbot before the switch if using DNS verification.

For custom certificates transferred from the dedicated server:

rsync -avzP \
  -e "ssh -i ~/.ssh/migration_key" \
  /etc/ssl/custom/ \
  root@CLOUD_INSTANCE_IP:/etc/ssl/custom/

Update web server configuration to reference the transferred certificate paths.

Step 4.5: Update Application Configuration Files

Update any configuration files that contain the dedicated server IP address:

grep -r "OLD.DEDICATED.IP" /var/www/ --include="*.php" -l | while read file; do
  sed -i "s/OLD.DEDICATED.IP/CLOUD.INSTANCE.IP/g" "$file"
  echo "Updated: $file"
done

Replace OLD.DEDICATED.IP and CLOUD.INSTANCE.IP with actual IP addresses.

Also update any hostname references:

grep -r "old-server-hostname" /var/www/ --include="*.php" -l | while read file; do
  sed -i "s/old-server-hostname/new-cloud-hostname/g" "$file"
  echo "Updated: $file"
done

Phase 5: Testing Before Cutover

Do not change DNS until every test passes.

Step 5.1: Test All Sites via Hosts File

Add entries to your local machine’s hosts file:

CLOUD.INSTANCE.IP   yourdomain.com www.yourdomain.com
CLOUD.INSTANCE.IP   seconddomain.com www.seconddomain.com

Now browse to each domain. You see the cloud instance without changing DNS for anyone else.

Test every critical user journey:

  • Home page loads
  • Internal navigation works
  • Login/authentication works
  • Forms submit correctly
  • Database reads return correct data
  • File uploads work
  • Search functions return results
  • API endpoints respond correctly

Step 5.2: Test All Application Features

Create a checklist of every significant feature your application has. Test each one:

FeatureTestPass/Fail
User loginLog in with existing credentials
Content displayView 10 different pages
SearchSearch for a known keyword
Form submissionSubmit a contact or test form
File uploadUpload a test image
Database writeCreate a test record, verify it persists
Cron simulationRun cron jobs manually, verify output
Email sendingTrigger a test email, verify delivery
SSLVerify HTTPS works on all domains
RedirectsVerify all custom redirects work

Do not proceed to DNS change until every item passes.

Step 5.3: Performance Test

Run the benchmark suite from Phase 0:

sysbench cpu --threads=$(nproc) --time=30 run | grep "events per second"
fio --name=randread --ioengine=libaio --rw=randread --bs=4k --direct=1 --size=1G --numjobs=4 --runtime=30 --group_reporting | grep "IOPS"
curl -w "TTFB: %{time_starttransfer}s | Total: %{time_total}s\n" -o /dev/null -s http://CLOUD.INSTANCE.IP/

Compare against your dedicated server baseline from Phase 0.

CPU performance may be lower on cloud (due to vCPU sharing). IOPS should be comparable or higher if using NVMe. TTFB should match or improve.

If performance is significantly worse than the dedicated server baseline, investigate before switching DNS. The VPS benchmarking guide covers full performance validation methodology.

Step 5.4: Load Test

Simulate real traffic against the cloud instance:

wrk -t4 -c50 -d2m --latency http://CLOUD.INSTANCE.IP/

The server should handle your typical peak traffic volume without errors. Compare the results with an equivalent test against your dedicated server.

If you run Cloudways, their built-in monitoring shows resource utilisation during the load test. CPU and memory should stay well under 80%.

Phase 6: Lower DNS TTL

Three days before the planned DNS switch, lower your TTL.

Log into your domain registrar. Find the A records for every domain hosted on the dedicated server. Change TTL from its current value (often 3600 or 86400) to 300 seconds.

Wait 24 hours for the TTL change to propagate.

Verify:

dig yourdomain.com | grep TTL

The number should be 300 or close to it.

After TTL is low, DNS changes propagate globally in about 5 minutes. This minimises the cutover window.

Phase 7: The Cutover

Choose a maintenance window during your lowest traffic period. Look at your analytics hourly breakdown. Find the 2-hour window with the least activity.

Step 7.1: Final File Sync

Run rsync again immediately before cutover. This time, only changed files transfer:

rsync -avzP \
  --delete \
  -e "ssh -i ~/.ssh/migration_key" \
  /var/www/ \
  root@CLOUD_INSTANCE_IP:/var/www/

The --delete flag removes any files on the cloud instance that no longer exist on the dedicated server.

This sync typically completes in 2-5 minutes for an active site.

Step 7.2: Final Database Sync

Enable maintenance mode on the application. Then take the definitive database snapshot:

mysqldump \
  --all-databases \
  --single-transaction \
  --quick \
  --lock-tables=false \
  --routines \
  --triggers \
  --events \
  -u root -p \
  | gzip > /root/migration/cutover-database.sql.gz

Transfer and import immediately:

rsync -avzP -e "ssh -i ~/.ssh/migration_key" \
  /root/migration/cutover-database.sql.gz \
  root@CLOUD_INSTANCE_IP:/root/migration/

# On cloud instance:
gunzip /root/migration/cutover-database.sql.gz
mysql -u root -p < /root/migration/cutover-database.sql

Step 7.3: Switch DNS

Change every A record from the dedicated server IP to the cloud instance IP.

If you manage multiple domains, update all of them in the same session. Note the exact time of each change.

Verify propagation:

watch -n 10 "dig +short yourdomain.com"

The output should change from the dedicated server IP to the cloud instance IP within 5-10 minutes.

Step 7.4: Disable Maintenance Mode

The cloud instance is now receiving traffic. Remove maintenance mode:

# WordPress
cd /var/www/yourdomain.com
wp maintenance-mode deactivate

Step 7.5: Verify Traffic Has Shifted

On the cloud instance, tail the access log:

sudo tail -f /var/log/nginx/access.log

Real visitor requests should appear within minutes of DNS propagation.

On the dedicated server, tail its access log:

sudo tail -f /var/log/apache2/access.log

Traffic gradually drops to zero here as DNS propagates globally.

Phase 8: Post-Cutover Monitoring

Step 8.1: Watch the Error Logs for 24 Hours

On the cloud instance:

sudo tail -f /var/log/nginx/error.log
sudo tail -f /var/log/php_errors.log
sudo journalctl -f -p err

New error patterns appearing in the first 24 hours point to configuration differences that testing did not catch. Fix them immediately.

Step 8.2: Verify Cron Jobs

Check that cron jobs ran at their expected times:

sudo grep CRON /var/log/syslog | tail -20

Verify the output of critical cron jobs matches what the dedicated server produced.

Step 8.3: Check Mail Delivery

If you kept a mail server on cloud (not recommended but sometimes necessary), test outbound delivery:

echo "Test email from cloud server" | mail -s "Migration Test" your@email.com

Check delivery. Check mail logs for bounce messages:

sudo tail -50 /var/log/mail.log

Step 8.4: Keep the Dedicated Server Warm for 72 Hours

Do not cancel the dedicated server contract immediately. Keep it running for at least 72 hours after DNS switch.

Some visitors cache DNS longer than TTL. Edge cases in your application may only surface under real traffic. Having the dedicated server available as an instant rollback is worth the cost.

After 72 hours of stable cloud operation, you can proceed to decommission.

Phase 9: Decommission the Dedicated Server

Before cancelling the dedicated server, make one final backup of everything:

# Final backup of all web files
tar -czf /root/migration/final-web-backup.tar.gz /var/www/

# Final database backup
mysqldump --all-databases -u root -p | gzip > /root/migration/final-db-backup.sql.gz

Download both files to your local machine. Keep them for at least 90 days.

Then cancel the dedicated server contract through your provider’s billing portal.

Common Problems and Solutions

Problem: Application throwing database connection errors after migration

Check that the database user, password, and database name in the application config match what you created on the cloud instance. The password changed even if the username and database name stayed the same.

Problem: File upload failing after migration

Check the upload directory permissions and ownership. Also verify the PHP upload_tmp_dir setting points to a directory that exists and is writable by the web server user.

Problem: Cron jobs running but producing no output

The cron job probably references an absolute path from the dedicated server that does not exist on the cloud instance. Edit the cron file and verify every path exists:

find /old/path/to/script.sh 2>/dev/null || echo "File missing"

Problem: Emails stopped sending after migration

If you moved mail to an external service, the application SMTP configuration needs to point to the new service. Update the SMTP host, port, username, and password in the application settings.

Problem: Site loads but CSS or images are broken

The application is generating URLs with the old server IP or hostname hardcoded. Run the IP address grep search from Step 0.6 again on the cloud instance. Check the browser console for the specific broken URL patterns.

Problem: Redis connection refused after migration

Redis on the cloud instance may be configured to listen only on 127.0.0.1 by default. If the application connects to a different address, update either the application config or the Redis bind address to match.

Frequently Asked Questions

How long does a dedicated server to cloud migration take?

The audit phase takes 2-8 hours for a complex server. The environment build takes 2-4 hours. The initial data transfer depends on site size: a 20GB site typically takes 30-60 minutes on a 1 Gbps connection. Testing takes as long as it needs to take, typically 4-8 hours. The actual cutover window on migration day is 20-45 minutes. Total elapsed time from start to stable cloud operation is typically 3-7 days, including the 24-48 hour periods for TTL propagation and initial stabilisation.

Can I migrate without downtime?

Yes, using the same approach as migrating any live site. Lower the DNS TTL in advance, keep both servers live throughout the migration, do a final database sync with a brief maintenance window, switch DNS, then monitor both servers during propagation. The dedicated server serves all traffic until DNS propagates. The cloud instance takes over gradually as DNS updates propagate globally. Done correctly, visitors experience no downtime.

What is the biggest risk in this type of migration?

Undiscovered dependencies. A cron job that runs weekly and your test window does not cover. A PHP function deprecated in a newer version that you upgraded during migration. A third-party API that has the dedicated server IP whitelisted and blocks requests from the new IP. The pre-migration audit catches most of these. No audit catches all of them. Build your timeline to allow for fixing issues found after cutover.

Should I upgrade PHP and MySQL during the migration?

Only if you have tested your complete application against the new versions. The safest approach is to migrate first on matching software versions, then upgrade afterward one component at a time. Testing shows which plugins or code need updates for PHP 8.x compatibility. Doing the version upgrade and the server migration simultaneously means two potential sources of breakage are active at the same time.

How do I handle email if the dedicated server runs a mail server?

The honest answer is that migrating a self-hosted mail server is harder than migrating a web application. The new cloud IP has no email reputation. Outbound email from a fresh IP frequently lands in spam. The practical recommendation for most migrations is to move transactional email to an external SMTP service before the server migration. Hosted email accounts (Google Workspace or Microsoft 365) replace the IMAP/POP3 server. This removes the mail server complexity from the scope of the cloud migration entirely.

About The Author

Hostinger

4.7/5 (62k)
Claim 88% OFF Now

Liquid Web

4.3/5 (2.6k)
Claim 50% OFF Now

WP Engine

4.3/5 (1.6k)
Claim 33% OFF Now