Migrazione sicura di VPS su diverso provider: guida completa a zero downtime per aziende
A febbraio 2025 mi ha contattato il responsabile IT di una PMI ligure che vende componentistica elettronica tramite un e-commerce B2B basato su Laravel 10 con circa 4.200 prodotti a catalogo, 380 clienti attivi e un fatturato online di circa due milioni di euro l'anno. Il server era un OVH VPS Value da 8 vCPU, 32 GB di RAM e 160 GB di SSD, con Debian 11 Bullseye, Nginx, PHP-FPM 8.2, MySQL 8.0 e Redis 6. Il costo mensile era 89 euro più IVA, e il problema concreto era che da novembre 2024 il server mostrava latenze di I/O disco anomale - picchi di iowait al 40-60% durante gli import notturni dei listini fornitori - che facevano impallare le query MySQL e rallentavano il sito fino a 8-12 secondi di response time nelle ore di punta. Due ticket al supporto OVH in tre mesi, entrambi chiusi con "non riscontriamo anomalie dal nostro lato". Il cliente era esasperato.
La mia proposta è stata diretta: migrare l'intero stack su un Hetzner CPX31 - 8 vCPU AMD EPYC, 16 GB di RAM, 160 GB NVMe - a 16,69 euro al mese, data center di Falkenstein (Germania), con trasferimento completo di dati, configurazione e DNS in una finestra di manutenzione notturna concordata. Il CPX31 costa un quinto dell'OVH, e lo storage NVMe di Hetzner sul segmento cloud è consistentemente più veloce di quello SSD su OVH VPS Value - un dato che ho verificato empiricamente su oltre quindici macchine migrate negli ultimi due anni. In questo articolo ti racconto il protocollo operativo che ho usato per completare la migrazione con zero downtime e zero ordini persi, perché è lo stesso metodo che applico per ogni trasferimento di VPS tra provider e che puoi adattare alla tua situazione.
Stai cercando un Consulente Informatico esperto per migrare il tuo VPS tra provider senza rischiare downtime? Nel mio profilo professionale trovi l'esperienza concreta su Hetzner, OVH, Contabo, Digital Ocean e Aruba. Contattami per una consulenza diretta.
Perché una migrazione VPS tra provider fallisce e come prevenirlo?
La maggior parte delle migrazioni VPS fallisce per tre motivi ricorrenti: mancata preparazione del DNS, trasferimento dati incompleto (soprattutto sul database), e assenza di un piano di rollback. Ho visto PMI perdere giorni di operatività perché il tecnico incaricato ha spostato il record A del DNS senza abbassare il TTL in anticipo, con il risultato che metà dei clienti vedeva il sito vecchio e metà quello nuovo per 24-48 ore. Oppure perché il database è stato esportato con mysqldump alle 22:00, importato sul nuovo server alle 23:30, e nel frattempo il sito di produzione aveva ricevuto 47 ordini che sono andati persi nella transizione.
Il principio fondamentale di una migrazione a zero downtime è che il nuovo server deve essere una copia perfetta e aggiornata del vecchio prima di spostare il traffico. Non dopo, non durante - prima. Questo significa che la migrazione non è un evento singolo ("sposto tutto stanotte") ma un processo che dura almeno una settimana, con fasi preparatorie che iniziano giorni prima del cutover effettivo.
Fase 1: provisioning del nuovo server e hardening di base
Il primo giorno l'ho dedicato al provisioning del Hetzner CPX31 e alla configurazione dello stack identico al server di origine. La parola chiave è parità: ogni differenza tra i due ambienti è un potenziale punto di rottura durante il cutover. Lo script che uso per il provisioning iniziale di un server Debian 12 è questo:
#!/usr/bin/env bash
set -euo pipefail
# Aggiornamento sistema
apt update && apt upgrade -y
# Pacchetti base
apt install -y nginx mariadb-server redis-server \
php8.2-fpm php8.2-mysql php8.2-redis php8.2-xml \
php8.2-mbstring php8.2-curl php8.2-zip php8.2-gd \
php8.2-intl php8.2-bcmath \
fail2ban ufw certbot python3-certbot-nginx \
rsync htop iotop
# Firewall base
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
# SSH hardening
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
systemctl restart sshdDopo il provisioning, ho copiato le configurazioni Nginx virtualhost per virtualhost, verificando che ogni direttiva fosse identica. Lo stesso per php.ini, my.cnf e redis.conf. Non copio mai le configurazioni alla cieca con un rsync di /etc: preferisco ricrearle manualmente sul nuovo server partendo dalla configurazione stock di Debian 12, applicando solo le personalizzazioni documentate. Questo mi permette di eliminare configurazioni residue accumulate in anni di patch e workaround che nessuno ricorda più perché siano lì.
Fase 2: il trasferimento dati con rsync incrementale
Il trasferimento dei dati applicativi - codice sorgente, upload utenti, file di configurazione, certificati - avviene con rsync in modalità incrementale. Il primo sync è il più lungo perché trasferisce tutto; i sync successivi trasferiscono solo i file modificati, e l'ultimo sync prima del cutover è quasi istantaneo.
Ho impostato un cron job sul server di origine che eseguiva un rsync incrementale ogni sei ore verso il nuovo server Hetzner:
# Cron sul server OVH: rsync incrementale ogni 6 ore
0 */6 * * * /usr/bin/rsync -aAXz --delete \
--exclude='/var/log/*' \
--exclude='/tmp/*' \
--exclude='/dev/*' \
--exclude='/proc/*' \
--exclude='/sys/*' \
--exclude='/run/*' \
-e "ssh -i /root/.ssh/migration_key -p 22" \
/var/www/ root@NEW_IP:/var/www/ \
>> /var/log/migration-rsync.log 2>&1I flag sono importanti: -a preserva permessi, ownership, timestamp e symlink; -A preserva le ACL; -X preserva gli extended attributes; -z comprime il trasferimento; --delete rimuove sul destinatario i file cancellati sull'origine. Ho usato una chiave SSH dedicata (migration_key) creata per questa operazione specifica e revocata subito dopo la migrazione - mai riutilizzare chiavi SSH tra operazioni diverse, perché una chiave compromessa diventa un vettore di accesso permanente.
Il primo sync ha trasferito circa 28 GB in poco meno di un'ora grazie alla banda eccellente tra OVH e Hetzner (entrambi in Europa, latenza sotto i 10ms). I sync successivi - ogni sei ore per cinque giorni - trasferivano in media 50-200 MB di dati modificati e completavano in meno di un minuto.
Fase 3: la replica del database MySQL
Il database è la parte più critica di qualsiasi migrazione, perché è lo stato mutabile dell'applicazione. Un file PHP che manca lo noti subito; una riga di ordine che manca nel database potresti non notarla per settimane. Per il database ho usato la replica nativa MySQL (primary-replica), che mantiene il nuovo server sincronizzato in tempo reale con il vecchio:
# Sul server OVH (primary): abilitare il binary log
# In /etc/mysql/mysql.conf.d/mysqld.cnf:
# server-id = 1
# log_bin = /var/log/mysql/mysql-bin.log
# binlog_do_db = ecommerce_production
# Dump iniziale con posizione del binlog
mysqldump -u root -p --single-transaction --master-data=2 \
--databases ecommerce_production > /root/initial_dump.sql
# Trasferire il dump e importarlo sul nuovo server Hetzner
scp /root/initial_dump.sql root@NEW_IP:/root/
# Sul server Hetzner (replica):
# mysql -u root -p < /root/initial_dump.sql
# Configurare la replica sul server Hetzner
# CHANGE REPLICATION SOURCE TO
# SOURCE_HOST='OLD_IP',
# SOURCE_USER='repl_user',
# SOURCE_PASSWORD='password_sicura',
# SOURCE_LOG_FILE='mysql-bin.000042',
# SOURCE_LOG_POS=15743;
# START REPLICA;La replica MySQL garantisce che ogni INSERT, UPDATE e DELETE eseguito sul database di produzione venga replicato sul nuovo server in tempo quasi-reale (lag tipico sotto i 500ms). Questo è cruciale: al momento del cutover, il database del nuovo server è già perfettamente allineato, e non c'è bisogno di un dump dell'ultimo minuto che richiederebbe di mettere il sito in manutenzione.
Ho monitorato lo stato della replica per cinque giorni con SHOW REPLICA STATUS\G - la documentazione ufficiale MySQL sulla replica descrive nel dettaglio ogni campo di output -, verificando che Seconds_Behind_Source restasse costantemente a 0 e che non ci fossero errori di replica. In quel periodo il database ha processato circa 12.000 transazioni senza un singolo errore di sincronizzazione.
La preparazione DNS: abbassare il TTL con 48 ore di anticipo
Il DNS è il meccanismo che traduce il nome del dominio nell'indirizzo IP del server. Quando cambi l'IP, i resolver DNS di tutto il mondo devono aggiornare la loro cache - e il tempo che impiegano dipende dal TTL (Time To Live) impostato sul record. Il TTL di default su molti provider DNS è 86.400 secondi (24 ore), il che significa che dopo un cambio di IP potresti avere utenti che raggiungono ancora il vecchio server per un giorno intero.
La soluzione è semplice ma va fatta con anticipo: almeno 48 ore prima del cutover, abbassi il TTL dei record A e AAAA a 300 secondi (5 minuti). Questo garantisce che, quando cambi l'IP, la propagazione sia quasi istantanea. Sul pannello Cloudflare del cliente ho impostato:
# Record DNS pre-migrazione (TTL abbassato 48h prima)
A @ OLD_IP TTL 300
A www OLD_IP TTL 300
AAAA @ (none) TTL 300
MX @ mx1... TTL 3600 (la posta non migra, TTL invariato)Un errore comune è dimenticare di aspettare le 48 ore: se abbassi il TTL e fai il cutover lo stesso giorno, i resolver che avevano in cache il vecchio TTL di 24 ore continueranno a servire il vecchio IP. Le 48 ore servono a garantire che il vecchio TTL sia scaduto ovunque prima del cambio.
Fase 4: il cutover - la finestra di manutenzione
La notte del 15 febbraio 2025, alle 02:00, con il titolare collegato su Telegram per monitorare, ho eseguito il cutover. La procedura che uso è standardizzata e la eseguo sempre nello stesso ordine:
# 1. Ultimo rsync dei file (delta minimo, <30 secondi)
rsync -aAXz --delete -e "ssh -i /root/.ssh/migration_key" \
/var/www/ root@NEW_IP:/var/www/
# 2. Verificare che la replica MySQL sia allineata
# SHOW REPLICA STATUS\G → Seconds_Behind_Source = 0
# 3. Mettere il sito in manutenzione sul vecchio server
# (Laravel: php artisan down --secret="token-segreto")
# 4. Aspettare 10 secondi per le transazioni in volo
# 5. Ultimo rsync per catturare eventuali file scritti durante il down
# 6. Sul nuovo server: fermare la replica e promuovere a primary
# STOP REPLICA;
# RESET REPLICA ALL;
# 7. Sul nuovo server: verificare che Laravel funzioni
# curl -s -o /dev/null -w "%{http_code}" https://NEW_IP/health-check
# (deve restituire 200)
# 8. Cambiare il record DNS su Cloudflare: A @ → NEW_IP
# 9. Togliere la manutenzione sul nuovo server
# php artisan upLa finestra di manutenzione effettiva - dal momento in cui ho messo il sito in artisan down al momento in cui il DNS puntava al nuovo server - è stata di 3 minuti e 22 secondi. Tre minuti alle due di notte, quando il traffico sul portale era zero. Per gli utenti, non c'è stato nessun downtime percepito.
Dopo il cutover, ho lasciato il vecchio server OVH attivo per 72 ore come fallback. Se qualcosa fosse andato storto - un problema applicativo non rilevato nei test, un servizio dimenticato, una configurazione mancante - sarebbe bastato reimpostare il record DNS sul vecchio IP per tornare operativi in meno di 5 minuti grazie al TTL a 300 secondi. Dopo 72 ore senza problemi, ho alzato il TTL a 3600 secondi (un'ora) e programmato la dismissione del server OVH.
Che differenza di performance ha fatto la migrazione?
I numeri parlano da soli. Ho misurato le performance prima e dopo con wrk (load testing HTTP) e con il monitoring applicativo di Laravel Telescope:
- Response time medio pagina catalogo: da 1.840ms (OVH) a 380ms (Hetzner) - riduzione dell'80%
- Response time import listini notturno: da 47 minuti a 11 minuti - il collo di bottiglia era il disco
- iowait medio: da 35% (OVH) a 2% (Hetzner) - NVMe contro SSD SATA
- Costo mensile: da 89€ a 16,69€ - risparmio dell'81%
La differenza di I/O era il punto centrale: lo storage NVMe del CPX31 Hetzner ha una latenza tipica sotto i 200 microsecondi, contro i 2-5 millisecondi dello storage SSD dei VPS OVH Value. Per un e-commerce con MySQL che fa centinaia di query per pagina, quella differenza si moltiplica esponenzialmente.
Per nuovi clienti Hetzner, puoi ottenere €20.00 di credito gratuito utilizzando questo link con codice sconto - una scelta ideale per aziende europee che necessitano conformità GDPR e data center in Germania.
Le cinque regole che applico a ogni migrazione VPS
Dopo oltre quindici migrazioni VPS tra provider negli ultimi tre anni, ho cristallizzato un insieme di regole non negoziabili che mi hanno sempre evitato disastri.
Regola 1: mai migrare senza replica del database. Un mysqldump fatto alle 22:00 e importato alle 23:30 lascia un buco di 90 minuti in cui ogni ordine, ogni registrazione, ogni modifica viene persa. La replica MySQL primary-replica è l'unica tecnica che garantisce zero data loss al momento del cutover. Se il tuo database è troppo grande per la replica (multi-terabyte), usa Percona XtraBackup per backup hot che non bloccano le scritture.
Regola 2: non migrare la posta insieme al sito. I record MX hanno una propagazione molto più lenta e complessa dei record A. Se il tuo server gestisce anche la posta (Postfix, Dovecot), migra prima il sito, stabilizzalo, e poi migra la posta in una finestra separata - oppure, ancora meglio, sposta la posta su un servizio dedicato come un relay SMTP esterno prima della migrazione del server.
Regola 3: rsync con --delete e chiave SSH dedicata. Il flag --delete è fondamentale per eliminare dal server di destinazione i file che sono stati rimossi dall'origine, mantenendo i due filesystem perfettamente allineati. La chiave SSH dedicata va revocata immediatamente dopo la migrazione - non lasciare mai chiavi di migrazione attive su un server di produzione.
Regola 4: testa il nuovo server con il file hosts locale. Prima del cutover, aggiungi una riga nel tuo /etc/hosts (o C:\Windows\System32\drivers\etc\hosts su Windows) che mappa il dominio sull'IP del nuovo server. Questo ti permette di navigare il sito sul nuovo server esattamente come lo vedranno gli utenti dopo il cambio DNS, senza toccare il DNS pubblico. Se qualcosa non funziona, lo scopri prima del cutover, non dopo.
Regola 5: mantieni il vecchio server attivo per 72 ore. Il rollback deve essere possibile e istantaneo. Se il vecchio server è già stato spento, non hai rete di sicurezza. Il costo di 72 ore aggiuntive di VPS è trascurabile rispetto al costo di un giorno di downtime.
Ho descritto il protocollo di disaster recovery per applicazioni PHP in un articolo dedicato che copre anche gli scenari in cui il rollback non è possibile. E se il tuo caso è più complesso - migrazione di database MySQL di grandi dimensioni con tabelle da decine di milioni di righe - ho documentato le tecniche specifiche nell'articolo sulla migrazione strategica di database MySQL tra server.
Cosa ho imparato sulle migrazioni VPS nelle PMI italiane
Il pattern più comune che osservo è questo: la PMI sceglie un provider VPS tre o quattro anni fa sulla base del prezzo più basso o del consiglio di un tecnico occasionale, il server funziona "abbastanza bene" per un po', poi le performance degradano man mano che l'applicazione cresce, il supporto del provider si rivela inadeguato, ma nessuno migra perché "funziona, non tocchiamolo". Quando finalmente il problema diventa insostenibile - come nel caso del cliente ligure con l'iowait al 40% - la migrazione viene fatta in emergenza, di fretta, senza il protocollo strutturato che ho descritto, e i danni sono spesso peggiori del problema originale.
La migrazione infrastrutturale non è un'emergenza: è un'operazione di ingegneria che richiede pianificazione, test e un piano di rollback. Se la affronti con metodo, il downtime è zero e il risultato è un'infrastruttura più veloce, più economica e più sicura. Se la improvvisi, rischi di trovarti nella situazione che ho descritto nell'articolo sulla gestione urgente di intrusioni su VPS - un server nuovo ma configurato male che viene compromesso nelle prime settimane perché nessuno ha fatto l'hardening post-migrazione.
Se stai valutando una migrazione del tuo VPS tra provider - o se sei bloccato su un'infrastruttura che non risponde più alle esigenze del tuo business - non aspettare che il problema diventi un'emergenza. Una migrazione pianificata costa una frazione di una migrazione d'urgenza, sia in termini economici che di rischio operativo. Contattami e valutiamo insieme il percorso più sicuro per la tua infrastruttura.