Overview
Migrating hosting providers is one of those tasks that looks simple (copy files, update DNS) but has hidden complexity. A poorly planned migration means downtime, data loss, or security gaps.
This guide walks through a complete, methodical migration from any proprietary platform (AWS, DigitalOcean, Linode, Vultr, etc.) to a FOSS-friendly VPS.
Time required: 2-4 hours depending on application complexity Downtime: Minimal (with proper blue-green approach)
Before You Start: Assessment
Inventory Your Current Infrastructure
Before touching anything, document what you have:
# SSH into your current server and document:
# 1. Installed packages
dpkg --get-selections | grep -v deinstall
# 2. Running services
systemctl list-units --type=service --state=running
# 3. Nginx/Apache config
ls -la /etc/nginx/sites-enabled/
cat /etc/nginx/sites-enabled/default
# 4. Databases
sudo -u postgres psql -l
# 5. Cron jobs
crontab -l
sudo cat /var/spool/cron/crontabs/root
# 6. Installed Node versions (if applicable)
node --version
npm list -g --depth=0
Calculate Resource Requirements
Check your actual usage, not what you think you’re using:
# CPU and RAM usage
htop
free -h
# Disk usage by directory
du -sh /var/*
du -sh /home/*
# Database sizes
sudo -u postgres psql -c "SELECT pg_database.datname, pg_size_pretty(pg_database_size(pg_database.datname)) FROM pg_database ORDER BY pg_database_size DESC;"
# Docker disk usage
docker system df
Step 1: Choose Your Target Platform
Based on the Best FOSS-Friendly VPS Hosts comparison:
| Use Case | Recommended |
|---|---|
| General purpose, best value | Hetzner |
| Tightest budget | Netcup |
| Maximum free resources | Oracle Free Tier |
| Privacy / public-interest project | 1984 Hosting |
For this guide, we’ll assume migration to Hetzner (most common choice).
Create Your Hetzner Account
- Go to hetzner.com and create an account
- Verify with a phone number (SMS)
- Add SSH key for authentication:
- In Hetzner Console → SSH Keys → Add
- Paste your public key from
~/.ssh/id_ed25519.pub
- Order your VPS (CX21 recommended: 2 vCPU, 50 GB NVMe, 8 GB RAM for ~€6/mo)
Step 2: Prepare the New Server
Initial Setup
SSH into your new Hetzner server and run the VPS hardening guide first. This takes 30 minutes and is non-negotiable for production.
Install Required Software
# Update
sudo apt update && sudo apt upgrade -y
# Install core packages
sudo apt install -y \
nginx \
postgresql \
redis-server \
certbot \
python3-certbot-nginx \
ufw \
htop \
fail2ban \
unattended-upgrades
# If using Docker:
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# If using Coolify:
curl -fsSL https://get.coolify.io | bash
Step 3: Migrate Data
Option A: Direct rsync (Best for Large Files)
# On the NEW server, create the target directory
sudo mkdir -p /var/www
sudo chown -R $USER:$USER /var/www
# On the OLD server, run rsync (from OLD server)
rsync -avz --progress \
-e "ssh -i ~/.ssh/your_key" \
/var/www/ \
aaron@<new-server-ip>:/var/www/
Option B: Database Dump and Restore (PostgreSQL)
# On the OLD server — create dump
sudo -u postgres pg_dumpall > /tmp/postgres_backup.sql
gzip /tmp/postgres_backup.sql
# Transfer to new server
rsync -avz -e "ssh -i ~/.ssh/your_key" \
/tmp/postgres_backup.sql.gz \
aaron@<new-server-ip>:/tmp/
# On the NEW server — restore
gunzip /tmp/postgres_backup.sql.gz
sudo -u postgres psql -f /tmp/postgres_backup.sql
Option C: Use Docker Volumes
# On the OLD server — backup Docker volumes
docker run --rm \
-v app_data:/data \
-v $(pwd):/backup \
alpine \
tar czf /backup/app_data.tar.gz -C /data .
# Transfer and restore on new server
docker run --rm \
-v app_data:/data \
-v $(pwd):/backup \
alpine \
sh -c "rm -rf /data/* && tar xzf /backup/app_data.tar.gz -C /data"
Step 4: Configure the New Server
Nginx Configuration
sudo nano /etc/nginx/sites-available/yourapp
server {
listen 80;
server_name yourdomain.com;
root /var/www/yourapp;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# Proxy to application if needed
location /api {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
# Enable the site
sudo ln -s /etc/nginx/sites-available/yourapp /etc/nginx/sites-enabled/
sudo nginx -t # Test config
sudo systemctl reload nginx
SSL Certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Step 5: DNS Cutover Strategy
Never change DNS without a rollback plan.
Blue-Green Migration Approach
- Before migration: Note your current DNS TTL (usually 300-3600 seconds)
- Reduce TTL to 300 (5 minutes) on the old provider — do this 24 hours before migration
- Keep the old server running until DNS propagates
- Update DNS A record to new server IP
- Wait 15-30 minutes for propagation
- Monitor — check logs, error tracking, user reports
- Only shut down old server after 48 hours of confirmed successful operation
# Check current TTL
dig +short SOA yourdomain.com | awk '{print $2}'
# Check DNS propagation from multiple locations
dig @8.8.8.8 yourdomain.com A
dig @1.1.1.1 yourdomain.com A
Step 6: Validate Everything
Before declaring success, verify:
Functionality
- Application loads without errors
- All routes work correctly
- File uploads/downloads function
- API endpoints respond correctly
Security
- SSL certificate is valid (
https://www.ssllabs.com/ssltest/) - SSL rating is A or higher
- No sensitive files exposed (
robots.txt,.envnot in web root) - Headers are correct (
curl -I https://yourdomain.com)
Performance
- Response times are acceptable
- No resource exhaustion (check
htop) - Database connections are stable
Backups
- Automated backups are configured
- Backup restoration is tested
- Off-site backup location configured
Step 7: Post-Migration Cleanup
After 48 hours of confirmed success:
# On the OLD server, take a final backup
# Then decommission:
# 1. Disable services
sudo systemctl stop nginx postgresql
# 2. Document any config you want to keep
# 3. Terminate the instance via the provider's console
# On the NEW server, update DNS TTL back to reasonable value (3600)
Common Migration Pitfalls
”It Works on My Machine”
Your new server might have different software versions. Always check:
# Compare versions
node --version
nginx -v
psql --version
Environment Variables
Never copy .env files directly. Instead, manually recreate them on the new server:
# On old server, view env vars (without values if possible)
printenv | grep -v PATH
Permissions
Web servers run as www-data or similar. Verify file permissions:
# Set correct ownership
sudo chown -R www-data:www-data /var/www/yourapp
# Set correct permissions
sudo find /var/www/yourapp -type d -exec chmod 755 {} \;
sudo find /var/www/yourapp -type f -exec chmod 644 {} \;
Missing Dependencies
Your application might need system libraries not present on the new server:
# Check what libraries your binary/app needs
ldd /path/to/your/binary
Rollback Plan
If the migration fails:
- Do not delete the old server until everything is validated
- Update DNS back to old server IP immediately
- Investigate in a new debug session, not under pressure
- Document what went wrong for next time
Cost Comparison Example
| Provider | Plan | Monthly Cost |
|---|---|---|
| AWS | t3.medium + EBS + bandwidth | ~$40-60 |
| DigitalOcean | 2GB + volumes | ~$12 |
| Hetzner | CX21 | €6 |
Migrating from AWS to Hetzner can save €400-600+ per year for equivalent workloads.