Introducción
Las razones para migrar un servidor dedicado son muchas y todas válidas: el proveedor se está volviendo caro, el hardware ya tiene años y el rendimiento se nota, el soporte técnico es pésimo, o simplemente necesitas mover tu infraestructura a un datacenter más cercano a tus usuarios para reducir latencia. Cualquiera de estas razones es suficiente. El problema no es por qué migrar, sino cómo hacerlo sin que todo explote en el proceso.
El miedo clásico es "¿y si se rompe todo?". Perder datos, horas de downtime, clientes llamando, el jefe encima. Ese miedo es completamente válido — una migración mal ejecutada puede ser catastrófica. Pero la buena noticia es que ese escenario no viene de la complejidad técnica, viene de la falta de preparación. La mayoría de migraciones que salen mal no fallan porque el administrador no sabe usar rsync, sino porque nadie documentó qué estaba corriendo en el servidor antes de empezar.
La clave está en una sola idea: la mayoría de migraciones fallan por falta de preparación, no por complejidad técnica. Un servidor que has documentado completamente, con un plan de rollback claro y un proceso de testing antes de tocar el DNS, es un servidor que puedes migrar con confianza. El downtime "cero" es casi imposible si tienes bases de datos con escrituras constantes, pero "menos de 10 minutos" es perfectamente alcanzable para la mayoría de casos.
En esta guía vas a tener un proceso sistemático con checklist, comandos reales y un plan de rollback. El objetivo no es que memorices cada paso, sino que lo sigas como protocolo la próxima vez que tengas que mover un servidor. Al final tendrás el servidor nuevo corriendo en paralelo, habrás verificado que funciona bien, y harás el corte de DNS cuando estés seguro — no a ciegas.
Fase 1 — Auditoría del servidor actual
Esta es la fase más importante. No puedes migrar lo que no has documentado. El objetivo aquí no es solo hacer un listado, sino entender completamente qué está corriendo, qué depende de qué, y qué se puede romper si algo falta en el servidor nuevo.
Inventariar servicios activos
Empieza por saber exactamente qué procesos están activos y qué están escuchando en qué puertos. Esto te dará la lista completa de lo que tienes que replicar.
# Ver todos los servicios activos
sudo systemctl list-units --type=service --state=active
# Ver los que arrancan al inicio
sudo systemctl list-unit-files --type=service --state=enabled
# Puertos en escucha (muestra qué está corriendo)
sudo ss -tlnp
# o
sudo netstat -tlnp
# Procesos por consumo de CPU/RAM
ps aux --sort=-%mem | head -20
Documentar configuraciones críticas
Los servicios son el "qué", la configuración es el "cómo". Necesitas documentar cómo está configurado cada servicio importante antes de intentar replicarlo.
# Listar todos los vhosts de nginx
ls -la /etc/nginx/sites-enabled/
cat /etc/nginx/nginx.conf
# Crontabs de todos los usuarios
sudo crontab -l
crontab -l
sudo bash -c 'for user in $(cut -f1 -d: /etc/passwd); do echo "=== $user ==="; crontab -u $user -l 2>/dev/null; done'
# Usuarios del sistema y sus homes
cat /etc/passwd | grep -v nologin | grep -v false
# Variables de entorno en uso
sudo cat /etc/environment
sudo cat /etc/profile.d/*.sh 2>/dev/null
Medir uso real de recursos
Esto te ayuda a elegir el hardware adecuado en el servidor nuevo y a identificar qué procesos consumen más recursos.
# Disco
df -h
du -sh /var/www/* 2>/dev/null | sort -rh | head -20
# RAM en uso real
free -h
vmstat -s | head -10
# CPU — average load
uptime
cat /proc/cpuinfo | grep "model name" | head -1
# Bandwidth (si tienes vnstat instalado)
vnstat -m
Checklist de pre-migración
Antes de tocar el servidor nuevo, este checklist debe estar completo. No saltes ningún punto.
| Item | Comando/Acción | ✓ |
|---|---|---|
| Lista de servicios activos | systemctl list-units |
|
| Versiones exactas (nginx, PHP, MySQL...) | nginx -v; php -v; mysql -V |
|
| Lista de bases de datos | mysqlshow o psql -l |
|
| Crontabs documentados | crontab -l (todos los usuarios) |
|
| SSL certificates y fechas de vencimiento | certbot certificates |
|
| Variables de entorno y .env files | cat /etc/environment |
|
| Usuarios SSH con acceso | cat /etc/ssh/sshd_config; cat /root/.ssh/authorized_keys |
|
| Reglas de firewall actuales | iptables -L -n; ufw status |
|
| DNS records del dominio | dig +short A dominio.com |
|
| TTL actual del DNS | dig dominio.com | grep TTL |
Fase 2 — Preparar el servidor nuevo
El servidor nuevo tiene que ser configurado antes de migrar ningún dato. El objetivo es tener un entorno idéntico al original en cuanto a versiones de software y configuración de seguridad base.
Instalar el mismo stack
Las versiones importan. Una aplicación que corre sobre PHP 8.0 puede comportarse diferente en PHP 8.2. Replica las versiones exactas del servidor viejo.
# En el servidor NUEVO
# Replicar versiones exactas del viejo
# Ver versiones en el servidor viejo
nginx -v
php -v
mysql --version
node --version
python3 --version
# Instalar en el nuevo (ejemplo para Ubuntu 22.04 + nginx + PHP 8.1 + MySQL 8)
sudo apt update
sudo apt install -y nginx php8.1-fpm php8.1-mysql php8.1-curl php8.1-zip \
php8.1-gd php8.1-mbstring php8.1-xml php8.1-bcmath
sudo apt install -y mysql-server-8.0
Seguridad base antes de migrar datos
No pongas datos en un servidor que no está protegido. Antes de copiar nada, asegura el acceso SSH y configura el firewall.
# Deshabilitar login con contraseña — solo keys SSH
sudo sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl reload sshd
# Configurar firewall básico
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
# Instalar fail2ban
sudo apt install fail2ban -y
sudo systemctl enable --now fail2ban
Configurar acceso SSH del viejo al nuevo
Para que rsync funcione sin interrupciones (y sin pedir contraseña cada vez), el servidor viejo necesita poder conectarse al nuevo por SSH usando una clave dedicada para la migración.
# En el servidor VIEJO: agregar clave pública al nuevo
# Esto permite que rsync funcione sin contraseña
ssh-keygen -t ed25519 -C "migration-key" -f ~/.ssh/migration_key -N ""
ssh-copy-id -i ~/.ssh/migration_key.pub root@IP-SERVIDOR-NUEVO
# o
cat ~/.ssh/migration_key.pub | ssh root@IP-SERVIDOR-NUEVO "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
Fase 3 — Migrar los datos
Aquí está el grueso del trabajo. La estrategia es hacer una sincronización inicial (que puede tardar horas si tienes muchos datos) y luego una sincronización final justo antes del corte de DNS para copiar solo los cambios recientes.
Archivos con rsync
rsync es la herramienta ideal para esto: copia solo los cambios, soporta compresión en tránsito, puede reanudar si se interrumpe, y preserva permisos y timestamps.
# Desde el servidor VIEJO hacia el NUEVO
# -a: archive (preserva permisos, timestamps, owner)
# -v: verbose
# -z: comprimir en tránsito
# -P: mostrar progreso + resumir si se interrumpe
# --delete: eliminar archivos en destino que no están en origen
# Sincronización inicial (puede tardar horas)
rsync -avzP --delete \
/var/www/ \
root@IP-NUEVO:/var/www/
# Config de nginx
rsync -avzP \
/etc/nginx/ \
root@IP-NUEVO:/etc/nginx/
# SSL certificates
rsync -avzP \
/etc/letsencrypt/ \
root@IP-NUEVO:/etc/letsencrypt/
# Hacer rsync final justo antes del corte DNS (sincroniza solo los cambios)
rsync -avzP --delete \
/var/www/ \
root@IP-NUEVO:/var/www/
--dry-run primero para ver qué se va a copiar sin realmente hacerlo. Especialmente cuando usas --delete.
Bases de datos MySQL
Para MySQL, la estrategia depende del tamaño. La opción segura para bases de datos de cualquier tamaño es --single-transaction, que hace el dump sin bloquear tablas en servidores InnoDB.
# En el servidor VIEJO
# Exportar todas las bases de datos
mysqldump \
--single-transaction \
--quick \
--lock-tables=false \
--all-databases \
--routines \
--triggers \
| gzip > /tmp/all-databases-$(date +%Y%m%d).sql.gz
# Copiar al servidor nuevo
rsync -avzP /tmp/all-databases-*.sql.gz root@IP-NUEVO:/tmp/
# En el servidor NUEVO: importar
gunzip < /tmp/all-databases-YYYYMMDD.sql.gz | mysql -u root -p
Para bases de datos grandes (>10GB), usa streaming directo para ahorrar espacio en disco:
# Pipe directo sin archivo intermedio (ahorra espacio de disco)
mysqldump --single-transaction --quick --lock-tables=false --all-databases | \
gzip | \
ssh root@IP-NUEVO "gunzip | mysql -u root"
PostgreSQL
# Exportar
sudo -u postgres pg_dumpall | gzip > /tmp/postgres-all-$(date +%Y%m%d).sql.gz
# Importar en el nuevo
gunzip < /tmp/postgres-all-*.sql.gz | sudo -u postgres psql
Crontabs
Los crontabs son fáciles de olvidar y silenciosamente críticos — si no los migras, los jobs dejan de correr sin ningún error visible.
# Exportar crontab de root en el servidor VIEJO
sudo crontab -l > /tmp/crontab-root-backup.txt
rsync -avzP /tmp/crontab-root-backup.txt root@IP-NUEVO:/tmp/
# Importar en el NUEVO
sudo crontab /tmp/crontab-root-backup.txt
sudo crontab -l # verificar
Fase 4 — Testing en paralelo
Esta fase es la diferencia entre una migración que te da confianza y una que hacen con los ojos cerrados. El objetivo es verificar que todo funciona en el servidor nuevo antes de cambiar el DNS, mientras el servidor viejo sigue sirviendo tráfico real.
Probar con /etc/hosts local
Antes de cambiar el DNS (que afectaría a todos tus usuarios), puedes apuntar el dominio al servidor nuevo solo desde tu máquina local editando el archivo hosts.
# En tu máquina local (NO en el servidor)
# Editar /etc/hosts (Linux/Mac) o C:\Windows\System32\drivers\etc\hosts (Windows)
# Agregar al final:
IP-SERVIDOR-NUEVO tudominio.com www.tudominio.com
Ahora cuando navegas a tudominio.com desde tu máquina, llega al servidor nuevo, mientras que el resto del mundo sigue llegando al servidor viejo.
# Verificar qué IP estás resolviendo localmente
dig +short tudominio.com @localhost
# o
curl -H "Host: tudominio.com" http://IP-SERVIDOR-NUEVO/
Checklist de verificación
Con el hosts modificado, ejecuta estas verificaciones una por una:
| Verificación | Método |
|---|---|
| Sitio web carga | Abrir navegador, ver que no hay errores |
| HTTPS funciona | Candado verde, no warning de certificado |
| Login/autenticación | Probar usuario/contraseña si aplica |
| Base de datos conecta | Ver que las páginas dinámicas cargan |
| Formularios funcionan | Enviar formulario de contacto de prueba |
| Emails se envían | Enviar email de prueba desde la app |
| Crons corren | Revisar log de cron después de la hora programada |
| Rendimiento | Comparar TTFB con el servidor viejo |
Comparar rendimiento
Una migración también es una oportunidad de verificar que el nuevo servidor es al menos tan rápido como el viejo. Si el TTFB es peor, investiga antes de hacer el corte.
# Comparar TTFB (Time to First Byte) entre viejo y nuevo
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
http://IP-SERVIDOR-VIEJO/
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
-H "Host: tudominio.com" http://IP-SERVIDOR-NUEVO/
Fase 5 — El corte DNS
Si llegaste aquí con el checklist de verificación completo, el trabajo difícil ya está hecho. El corte DNS es el momento en que el tráfico real empieza a llegar al servidor nuevo. Con la preparación correcta, debería ser el paso más tranquilo de todos.
Reducir el TTL 24-48 horas antes
El TTL (Time to Live) del DNS controla cuánto tiempo los resolvers cachean el registro. Si tu TTL está en 86400 (24 horas) y cambias el registro A, algunos visitantes seguirán llegando al servidor viejo durante hasta 24 horas. La solución: bajar el TTL a 300 (5 minutos) un día antes de la migración.
# Verificar TTL actual del dominio
dig tudominio.com | grep -E "^tudominio|TTL"
# Desde la interfaz de tu DNS provider (Cloudflare, etc.)
# Cambiar TTL del registro A a 300 (5 minutos)
# Esperar 24-48 horas (que el TTL viejo expire en todos los resolvers)
El rsync final
Justo antes de cambiar el DNS (menos de 30 minutos antes), haz una última sincronización para capturar todos los archivos que cambiaron desde la sincronización inicial. Como rsync solo copia los cambios, esto suele ser muy rápido.
# Justo antes de cambiar el DNS (< 30 minutos antes)
# Sincronizar solo los cambios desde la última vez
rsync -avzP --delete /var/www/ root@IP-NUEVO:/var/www/
# Verificar bases de datos: ¿hay datos nuevos desde el último dump?
# Si hay transacciones activas, considera:
# 1. Hacer un mysqldump rápido y reimportar los registros nuevos
# 2. Si es crítico: brief maintenance mode mientras cambias DNS
echo "Hora del rsync final: $(date)"
Cambiar el DNS
En tu proveedor DNS (Cloudflare, Namecheap, etc.), cambia el registro A del dominio de la IP del servidor viejo a la IP del servidor nuevo. Con TTL en 300, la mayoría de resolvers actualizarán en 5-15 minutos.
# Verificar propagación desde tu terminal
watch -n 5 "dig +short tudominio.com"
# Ver propagación global online
# Usar: whatsmydns.net o dnschecker.org
# Verificar que el nuevo servidor recibe tráfico
sudo tail -f /var/log/nginx/access.log
Mantener el servidor viejo activo
No apagues el servidor viejo inmediatamente. Mantenlo activo por 48-72 horas después del corte:
- La propagación DNS puede tardar hasta 48h en algunos resolvers
- Lo necesitas como fallback si algo falla en el servidor nuevo
- Te permite comparar comportamiento si algo se ve diferente
- Si hay que hacer rollback, tener el viejo activo hace que sea instantáneo
Fase 6 — Post-migración
El DNS ya apunta al servidor nuevo y el tráfico está fluyendo. Ahora toca verificar con DNS real (sin los cambios en /etc/hosts), monitorear errores y dejar todo configurado correctamente para el largo plazo.
Verificar con DNS real
# Quitar los cambios en /etc/hosts de tu máquina local
# y verificar que el dominio resuelve al servidor nuevo
dig +short tudominio.com
# Debe mostrar: IP-SERVIDOR-NUEVO
# Monitoreo de errores en tiempo real
sudo tail -f /var/log/nginx/error.log
sudo journalctl -u nginx -f
Restaurar TTL normal
# Después de 24-48h confirmando que todo funciona
# Volver TTL a valor normal: 3600 (1 hora) o 86400 (1 día)
# Esto reduce la carga en los DNS de tu proveedor
Mantener el TTL en 300 indefinidamente genera más queries a los nameservers de tu proveedor sin razón. Una vez que estés seguro de que la migración fue exitosa, vuelve al TTL normal.
Configurar monitoreo y backups en el servidor nuevo
El servidor nuevo es una pizarra limpia — aprovecha para configurar bien el monitoreo y los backups desde el principio.
# Instalar Uptime Kuma u otro monitor
# Configurar rclone backups (ver guía de backups)
# Configurar alertas de disco/RAM
# Verificar que fail2ban está activo
sudo fail2ban-client status
# Configurar logrotate para no quedarse sin disco
sudo nano /etc/logrotate.d/nginx
Dar de baja el servidor viejo
No canceles el servidor viejo de inmediato. Espera al menos una semana sin problemas antes de hacerlo.
# Una semana después de la migración exitosa:
# 1. Hacer un backup final del servidor viejo
# 2. Descargar el backup a algún lugar seguro
# 3. Cancelar la suscripción del servidor viejo
# Si tiene datos valiosos, tener snapshot/backup por 30 días más
Plan de rollback
Esta sección es crítica. Tienes que tenerla preparada antes de empezar la migración, no cuando ya algo salió mal. La buena noticia: con TTL=300 y el servidor viejo activo, hacer rollback es tan simple como cambiar el registro A de vuelta.
Si algo sale mal
Con TTL=300, revertir el DNS toma menos de 5 minutos:
# En tu proveedor DNS: volver el registro A a IP-SERVIDOR-VIEJO
# Con TTL=300, la mayoría de resolvers actualizarán en 5-15 minutos
# Verificar que el tráfico vuelve al viejo
watch -n 5 "dig +short tudominio.com"
Por eso es tan importante bajar el TTL antes y mantener el servidor viejo activo. El costo de mantener el servidor viejo unos días extra es mínimo comparado con el costo de no poder hacer rollback rápido.
Qué hacer si los datos divergieron
Si el servidor viejo recibió operaciones de escritura después del rsync final (nuevos registros, pedidos, etc.) y tienes que hacer rollback, necesitas migrar esos datos nuevos cuando vuelvas a intentar la migración.
# Exportar solo los registros NUEVOS desde el timestamp del rsync final
mysqldump --where="created_at > '2026-03-28 14:30:00'" mi_base tabla_usuarios > /tmp/nuevos-usuarios.sql
mysqldump --where="created_at > '2026-03-28 14:30:00'" mi_base tabla_pedidos > /tmp/nuevos-pedidos.sql
# Importar en el servidor nuevo cuando vuelvas a intentar
mysql mi_base < /tmp/nuevos-usuarios.sql
mysql mi_base < /tmp/nuevos-pedidos.sql
Para minimizar la divergencia de datos, el window de tiempo entre el rsync final y el corte de DNS debería ser lo más corto posible — idealmente menos de 30 minutos. Para aplicaciones con alto volumen de escrituras, considera poner un modo mantenimiento breve justo en ese window.