Here’s a clear, production‑ready, step‑by‑step guide to migrating your website to a VPS in Germany. It’s written for busy teams and solo builders alike, with copy‑paste commands, zero‑downtime tactics, and post‑cutover checks. (If you still need a German VPS, you can use your preferred provider—many readers choose 99RDP for Frankfurt‑based instances.)
0) What you’ll need (checklist)
-
Domain registrar access (to update DNS A/AAAA/CNAME).
-
SSH or RDP access to your current host.
-
A fresh German VPS (Ubuntu 22.04 LTS or Debian 12 recommended; Windows Server if you use IIS/.NET).
-
Application stack info: web server (Nginx/Apache/IIS), language/runtime (PHP/Node/Python), database (MySQL/MariaDB/PostgreSQL/MongoDB), background jobs (cron/queue), storage (local/NFS/S3/CDN).
-
A rollback plan: a snapshot of the old server or a full backup you can restore quickly.
1) Plan for zero surprises
-
Inventory everything
-
Site files, env variables/secrets, SSL certs, database(s), scheduled tasks, queue workers, geo‑blocked IP rules, CDN settings, email sending (SMTP/API), third‑party callbacks (webhooks), and payment IP allowlists.
-
-
Pick the German location
-
Frankfurt is a popular hub (low latency to EU via DE‑CIX). If your audience is DACH/EU, you’ll usually see faster TTFB.
-
-
Lower DNS TTL
-
24 hours before cutover, set your domain’s A/AAAA/CNAME TTL to 300 seconds. This speeds up propagation during the move.
-
-
Freeze the write path (near cutover)
-
Put the site in maintenance or read‑only mode just before final sync to prevent data drift (especially carts, comments, or dashboards).
-
2) Provision and harden the German VPS
Linux (Ubuntu/Debian)
# Update base system
sudo apt update && sudo apt -y upgrade
# Create a non-root sudo user
sudo adduser deploy
sudo usermod -aG sudo deploy
# Harden SSH
sudo -s
mkdir -p /home/deploy/.ssh
nano /home/deploy/.ssh/authorized_keys # paste your public key
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
# Optional: change SSH port and disable password login
nano /etc/ssh/sshd_config
# Port 2222 (example)
# PasswordAuthentication no
systemctl restart ssh
# Basic firewall
sudo apt -y install ufw fail2ban
sudo ufw allow 2222/tcp # or your SSH port
sudo ufw allow http
sudo ufw allow https
sudo ufw enable
# (Optional) swap if RAM is tight
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Windows Server (IIS)
-
Create an Administrator alt user, enable RDP, update OS.
-
Install IIS + URL Rewrite + ARR if needed, and Web Deploy for app import.
-
Set Windows Firewall rules for RDP (3389), HTTP (80), HTTPS (443).
3) Install your runtime stack
Choose what you use; here are common recipes.
LEMP (Nginx + PHP + MySQL/MariaDB)
sudo apt -y install nginx
sudo apt -y install mariadb-server # or mysql-server
sudo apt -y install php-fpm php-mysql php-cli php-xml php-curl php-zip php-gd
LAMP (Apache + PHP)
sudo apt -y install apache2
sudo apt -y install mariadb-server
sudo apt -y install php libapache2-mod-php php-mysql php-xml php-curl php-zip php-gd
Node.js
sudo apt -y install curl
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt -y install nodejs
Create a systemd service (example):
sudo nano /etc/systemd/system/myapp.service
# -----------------------------------------
[Unit]
Description=My Node App
After=network.target
[Service]
User=deploy
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/node server.js
Restart=always
Environment=NODE_ENV=production
# -----------------------------------------
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
Python (Gunicorn + Nginx)
sudo apt -y install python3-venv python3-pip
python3 -m venv /var/www/myapp/venv
source /var/www/myapp/venv/bin/activate
pip install gunicorn yourframework
Docker (if you containerize)
sudo apt -y install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release; echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update && sudo apt -y install docker-ce docker-ce-cli containerd.io docker-compose-plugin
4) Stage the site files on the VPS
Option A: rsync (fast, incremental)
# from OLD server -> NEW VPS
rsync -avz --progress -e "ssh -p 2222" /var/www/your_site/ deploy@NEW_VPS_IP:/var/www/your_site/
Option B: scp
scp -P 2222 -r /var/www/your_site/ deploy@NEW_VPS_IP:/var/www/your_site/
Option C: Dockerized
# Save and move your images if needed
docker save your-image:tag | gzip > image.tar.gz
scp -P 2222 image.tar.gz deploy@NEW_VPS_IP:~
ssh -p 2222 deploy@NEW_VPS_IP "gunzip -c ~/image.tar.gz | docker load"
Set correct permissions:
sudo chown -R www-data:www-data /var/www/your_site
sudo find /var/www/your_site -type d -exec chmod 755 {} \;
sudo find /var/www/your_site -type f -exec chmod 644 {} \;
5) Migrate the database safely
MySQL/MariaDB
# On OLD server
mysqldump -u root -p --single-transaction --routines --triggers yourdb > yourdb.sql
# Transfer & import on NEW
scp -P 2222 yourdb.sql deploy@NEW_VPS_IP:~
ssh -p 2222 deploy@NEW_VPS_IP
mysql -u root -p -e "CREATE DATABASE yourdb /*\!40100 DEFAULT CHARACTER SET utf8mb4 */;"
mysql -u root -p yourdb < ~/yourdb.sql
PostgreSQL
# On OLD server
pg_dump -U postgres -Fc yourdb > yourdb.dump
# Transfer & restore
scp -P 2222 yourdb.dump deploy@NEW_VPS_IP:~
ssh -p 2222 deploy@NEW_VPS_IP
createdb -U postgres yourdb
pg_restore -U postgres -d yourdb ~/yourdb.dump
MongoDB
mongodump --db yourdb --out dumpdir
scp -P 2222 -r dumpdir deploy@NEW_VPS_IP:~
mongorestore --db yourdb ~/dumpdir/yourdb
Update app config (env file) to point to the new local DB socket/host, credentials, and any changed ports.
6) Configure the web server & SSL
Nginx server block (PHP‑FPM example):
sudo nano /etc/nginx/sites-available/your_site
# ------------------------------------------------
server {
server_name yourdomain.com www.yourdomain.com;
root /var/www/your_site/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm.sock; # adjust version
}
access_log /var/log/nginx/your_site.access.log;
error_log /var/log/nginx/your_site.error.log;
}
# ------------------------------------------------
sudo ln -s /etc/nginx/sites-available/your_site /etc/nginx/sites-enabled/your_site
sudo nginx -t && sudo systemctl reload nginx
Let’s Encrypt (HTTPS)
sudo apt -y install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com --redirect --agree-tos -m you@example.com
Apache vhost (snippet)
sudo a2enmod rewrite
sudo nano /etc/apache2/sites-available/your_site.conf
# ------------------------------------------------
<VirtualHost *:80>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
DocumentRoot /var/www/your_site/public
<Directory /var/www/your_site/public>
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/your_site.error.log
CustomLog ${APACHE_LOG_DIR}/your_site.access.log combined
</VirtualHost>
# ------------------------------------------------
sudo a2ensite your_site && sudo systemctl reload apache2
sudo apt -y install certbot python3-certbot-apache
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com
7) Test privately before going live
-
Edit local hosts file to point the domain to the new VPS IP so only you see the new server:
-
macOS/Linux:
/etc/hosts -
Windows:
C:\Windows\System32\drivers\etc\hosts
NEW_VPS_IP yourdomain.com www.yourdomain.com -
-
Browse the site. Check:
-
Pages, logins, forms, checkout flow
-
File uploads/downloads
-
Admin panel actions
-
Background jobs/queues
-
Webhooks (temporarily repoint a test webhook to staging if possible)
-
Error logs:
/var/log/nginx/*.logor/var/log/apache2/*.log
-
8) Final sync & cutover
-
Enable maintenance mode on the old server (or freeze writes).
-
Final rsync + DB dump (fast delta copy):
# files rsync -avz --delete -e "ssh -p 2222" /var/www/your_site/ deploy@NEW_VPS_IP:/var/www/your_site/ # database - repeat the dump/import quickly # (MySQL example) mysqldump -u root -p --single-transaction yourdb > final.sql scp -P 2222 final.sql deploy@NEW_VPS_IP:~ ssh -p 2222 deploy@NEW_VPS_IP "mysql -u root -p yourdb < ~/final.sql" -
Switch DNS
-
Update A (and AAAA if using IPv6) records to the German VPS IP.
-
Thanks to low TTL (300s), most users will hit the new server within minutes.
-
-
Keep the old server online for 24–48 hours (serving the maintenance page) in case of stragglers due to caching.
9) Post‑migration checks (the “first hour”)
-
HTTPS: no mixed content; HSTS if you used it before.
-
SEO: 200/301/404 status codes behave as before; robots.txt and sitemaps load; canonical tags correct; redirect www/non‑www and HTTP→HTTPS.
-
Performance: verify TTFB and core pages; enable opcache (PHP), gzip/brotli, HTTP/2/3 if supported.
-
Queues/cron:
crontab -e # install your cron jobs # or systemd timers / supervisor as previously -
Email: SMTP creds on the new host; SPF/DKIM/DMARC unchanged at DNS.
-
Webhooks (Stripe/PayPal/etc.): confirm the new public IP is allowed if you used allowlists.
10) Hardening, backups, and monitoring
Security basics
# Unattended security updates
sudo apt -y install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
# Fail2ban jail (default works for SSH/Nginx/Apache)
sudo systemctl enable --now fail2ban
Backups
-
Files: nightly
rsyncto another volume or object storage. -
DB: nightly dumps with retention. Example MySQL backup script:
sudo nano /usr/local/bin/db_backup.sh
# -----------------------------------------
#!/bin/bash
set -e
STAMP=$(date +%F_%H%M)
mysqldump -u root -p'yourpass' --single-transaction yourdb | gzip > /backup/yourdb_$STAMP.sql.gz
find /backup -type f -mtime +7 -delete
# -----------------------------------------
sudo chmod +x /usr/local/bin/db_backup.sh
(crontab -l 2>/dev/null; echo "15 2 * * * /usr/local/bin/db_backup.sh") | crontab -
Monitoring
-
Install metrics/log shipping (Netdata/Prometheus agent/Elastic agent) and set uptime checks from multiple regions (EU/US/Asia).
11) Troubleshooting quick fixes
-
502/504 (Nginx ↔ upstream)
-
App not running or wrong socket/port. Check service:
systemctl status php8.1-fpmor your app service. Verifyfastcgi_pass/proxy_pass.
-
-
403/404 after move
-
File permissions/owners. Ensure:
sudo chown -R www-data:www-data /var/www/your_site -
Apache:
AllowOverride Allfor.htaccessrewrites.
-
-
PHP uploads failing
-
Increase limits:
php.ini: upload_max_filesize, post_max_size, memory_limit Nginx: client_max_body_size 50M;
-
-
Database connection refused
-
Service listening locally? Confirm sockets/ports. Recheck env variables and credentials.
-
-
Mixed content
-
Update app/site URL to https:// in CMS settings; run a safe search‑replace if needed.
-
-
Email not sending
-
New server’s IP may be blocked by a provider’s policy; double‑check SMTP host, port, TLS; ensure PTR (reverse DNS) is configured by your provider if sending mail directly.
-
12) If you use a CMS or framework
-
WordPress
-
Update
WP_HOMEandWP_SITEURLinwp-config.phpor in DB. -
Regenerate
.htaccessvia Permalinks. -
Cache/OPcache/WAF rules as before; image/CDN endpoints intact.
-
-
Laravel / Symfony / Django / Rails / Next.js
-
Recreate
.envwith production values (keys, DB, mail). -
Run migrations/build:
composer install --no-dev --optimize-autoloader php artisan migrate --force npm ci && npm run build # if applicable -
For Python:
pip install -r requirements.txt, runcollectstaticif Django.
-
13) Zero‑downtime pattern (summary)
-
Lower TTL to 300s (T‑24h).
-
Full initial sync (files + DB) to German VPS.
-
Private testing via hosts file.
-
Maintenance/read‑only on old server.
-
Final delta sync (fast).
-
DNS switch to German VPS.
-
Keep old server as warm standby for 24–48h.
-
Monitor, then decommission.
14) Notes specific to a German VPS
-
Latency: users in DE/AT/CH and much of EU will see improved latency; peering in Frankfurt is excellent.
-
Data locality & compliance: hosting in Germany helps keep EU resident data in the EU, which many teams prefer for regulatory/contract reasons. (Always validate your own compliance needs.)
-
IPv6: enable AAAA records if your provider offers native IPv6—many EU networks do.
15) Optional: IIS / Windows quick path
-
Export site via Web Deploy on the old host, import on the new Windows Server in Germany.
-
Bindings: set hostnames on port 80/443 in IIS Manager.
-
SSL: import existing cert (PFX) or issue via your preferred CA; complete the HTTPS binding.
-
Update
web.configrewrites and test via hosts file before DNS cutover.
16) Decommission the old server (only when ready)
-
Take a final snapshot, keep backups for the retention period you need.
-
Remove secrets/keys, revoke any old API tokens bound to the old IP.
-
Update runbooks/docs with the new host’s details.
Quick CTA
If you need a ready‑to‑go VPS in Germany, many teams use providers like 99RDP—pick a Frankfurt location, attach IPv6 if available, and follow the checklist above for a smooth cutover.

No comments:
Post a Comment