Migrar un sitio web a otro servidor sin que los visitantes lo noten depende menos de herramientas especiales que del orden de los pasos: preparar el DNS con antelación, tener el servidor nuevo funcionando y probado antes de tocar nada, y cubrir el periodo en que parte del tráfico sigue llegando al servidor antiguo. En este tutorial migrarás un sitio con Nginx, PHP y MySQL (por ejemplo WordPress) de un servidor Ubuntu a otro con Ubuntu 24.04, copiando archivos con rsync, la base de datos con mysqldump y el certificado TLS, y harás el cambio final con el servidor antiguo reenviando el tráfico al nuevo.
Requisitos previos
- Servidor antiguo: el que sirve hoy el sitio, con Nginx, PHP-FPM y MySQL o MariaDB. En esta guía su IP es
old_server_ip. - Servidor nuevo: Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con IP
new_server_ipy con la misma pila instalada (Nginx, PHP-FPM con las mismas extensiones y MySQL). Si es posible, usa la misma versión mayor de PHP y de la base de datos. - Un usuario no root con
sudoen ambos servidores (your_user) y acceso SSH por clave desde el servidor nuevo al antiguo. - Acceso al panel de tu proveedor DNS para cambiar el registro A de
your_domain. - El sitio en
/var/www/your_domainy la base de datosyour_dbcon el usuarioyour_db_user. Sustituye estos nombres por los tuyos.
Si el sitio no tiene base de datos (un sitio estático), sáltate los pasos 3 y 6.
Paso 1: Bajar el TTL del DNS
El TTL indica cuántos segundos pueden guardar en caché los resolvedores la IP de tu dominio. Si es de 86400 (un día), tras cambiar el registro A algunos visitantes seguirán yendo al servidor antiguo durante un día. Consulta el TTL actual:
dig +noall +answer your_domain A
your_domain. 3600 IN A old_server_ip
En el panel de tu proveedor DNS, baja el TTL del registro A (y del www si existe) a 300 segundos. Hazlo al menos con tanta antelación como el TTL antiguo: si era de 3600, espera como mínimo una hora antes del cambio; si era de 86400, un día. Así, cuando cambies la IP, casi todos los clientes la verán en menos de cinco minutos.
Paso 2: Copiar los archivos con rsync
Desde el servidor nuevo, trae los archivos del sitio. Los archivos suelen pertenecer a www-data y algunos pueden no ser legibles por tu usuario, así que rsync necesita ejecutarse con sudo en ambos extremos. En el servidor antiguo, permite temporalmente que tu usuario ejecute rsync y mysqldump con sudo sin contraseña, ya que se lanzarán por SSH sin terminal:
echo 'your_user ALL=(root) NOPASSWD: /usr/bin/rsync, /usr/bin/mysqldump' | sudo tee /etc/sudoers.d/migracion-rsync
sudo chmod 440 /etc/sudoers.d/migracion-rsync
sudo visudo -c
/etc/sudoers: parsed OK
/etc/sudoers.d/migracion-rsync: parsed OK
En el servidor nuevo, lanza la primera copia. Las opciones -aHAX conservan permisos, propietarios, enlaces duros, ACL y atributos extendidos, y --numeric-ids mantiene los UID/GID tal cual (www-data es el 33 en ambos servidores Ubuntu):
sudo mkdir -p /var/www/your_domain
sudo rsync -aHAX --numeric-ids --info=progress2 \
-e "ssh -i /home/your_user/.ssh/id_ed25519" \
--rsync-path="sudo rsync" \
your_user@old_server_ip:/var/www/your_domain/ /var/www/your_domain/
La barra final en your_domain/ es importante: copia el contenido del directorio y no el directorio dentro de sí mismo. Como el comando se ejecuta con sudo, indica la clave SSH de tu usuario con -i.
Esta primera copia puede tardar; las siguientes solo transfieren los cambios. Comprueba que el tamaño coincide en ambos servidores:
sudo du -sh /var/www/your_domain
Copia también el bloque de servidor de Nginx del sitio y revísalo antes de activarlo, porque la ruta del socket de PHP-FPM puede cambiar entre versiones (/run/php/php8.3-fpm.sock en Ubuntu 24.04):
sudo rsync -a --rsync-path="sudo rsync" -e "ssh -i /home/your_user/.ssh/id_ed25519" \
your_user@old_server_ip:/etc/nginx/sites-available/your_domain /etc/nginx/sites-available/
Paso 3: Migrar la base de datos
En el servidor nuevo, crea la base de datos y el usuario de la aplicación con la misma contraseña que usa la configuración del sitio (por ejemplo wp-config.php):
sudo mysql
CREATE DATABASE your_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'your_db_user'@'localhost' IDENTIFIED BY 'your_db_password';
GRANT ALL PRIVILEGES ON your_db.* TO 'your_db_user'@'localhost';
EXIT;
Ejecuta antes sudo -v para que sudo no pida la contraseña en mitad de la tubería. Después transfiere la base de datos en un solo paso: el volcado se genera en el servidor antiguo, viaja comprimido por SSH y se importa directamente en el nuevo. --single-transaction obtiene una copia coherente de tablas InnoDB sin bloquear el sitio:
ssh your_user@old_server_ip \
"sudo mysqldump --single-transaction --quick --routines --triggers --events your_db | gzip" \
| gunzip | sudo mysql your_db
Si en el servidor antiguo sudo mysqldump pide contraseña (instalaciones en las que root no usa autenticación por socket), usa mysqldump -u your_db_user -p y añade --no-tablespaces.
Comprueba que el número de tablas coincide en ambos servidores:
sudo mysql -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='your_db'"
Esta importación sirve para las pruebas. Los datos que cambien a partir de ahora (comentarios, pedidos, usuarios) se volverán a copiar en el paso 6.
Paso 4: Copiar el certificado TLS
Let's Encrypt no puede emitir un certificado nuevo por HTTP mientras el dominio apunte al servidor antiguo. Lo más sencillo es copiar el directorio /etc/letsencrypt completo; rsync -a conserva los enlaces simbólicos de live/ hacia archive/, de los que depende Certbot:
sudo apt install certbot python3-certbot-nginx
sudo rsync -a --rsync-path="sudo rsync" -e "ssh -i /home/your_user/.ssh/id_ed25519" \
your_user@old_server_ip:/etc/letsencrypt/ /etc/letsencrypt/
sudo certbot certificates
Found the following certs:
Certificate Name: your_domain
Domains: your_domain www.your_domain
Expiry Date: 2026-11-30 10:12:44+00:00 (VALID: 66 days)
Activa el sitio en Nginx y comprueba la configuración:
sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Abre los puertos web en el firewall si usas UFW:
sudo ufw allow 'Nginx Full'
Paso 5: Probar el servidor nuevo antes del cambio
Prueba el sitio en el servidor nuevo sin tocar el DNS. curl --resolve fuerza la resolución del dominio a la IP nueva y usa el certificado y el nombre reales:
curl -sI --resolve your_domain:443:new_server_ip https://your_domain
HTTP/2 200
server: nginx
content-type: text/html; charset=UTF-8
Para revisarlo en el navegador, añade temporalmente una línea al archivo hosts de tu ordenador (/etc/hosts en Linux y macOS, C:\Windows\System32\drivers\etc\hosts en Windows):
new_server_ip your_domain www.your_domain
Recorre las páginas principales, inicia sesión en el panel de administración, sube una imagen y prueba los formularios. Mientras tanto, vigila los errores en el servidor nuevo:
sudo tail -f /var/log/nginx/error.log
Cuando termines, elimina la línea del archivo hosts. No sigas hasta que todo funcione: hasta ahora no has cambiado nada para los visitantes.
Paso 6: Sincronización final y cambio de tráfico
Esta es la única fase en la que hay que coordinar los dos servidores. Para un sitio con base de datos, la forma segura de no perder escrituras es congelarlas durante unos minutos:
-
Pon el sitio en modo lectura o mantenimiento en el servidor antiguo. En WordPress, por ejemplo, crea el archivo
.maintenanceen la raíz del sitio o usa un plugin de mantenimiento. Los visitantes siguen viendo el sitio; solo se bloquean los cambios durante la ventana. -
Repite la copia de archivos desde el servidor nuevo. Esta vez añade
--deletepara eliminar lo que se borró en el origen. Solo transfiere los cambios, así que tarda poco:
sudo rsync -aHAX --numeric-ids --delete \
-e "ssh -i /home/your_user/.ssh/id_ed25519" \
--rsync-path="sudo rsync" \
your_user@old_server_ip:/var/www/your_domain/ /var/www/your_domain/
- Vuelve a importar la base de datos. El volcado incluye
DROP TABLE IF EXISTSpara cada tabla, así que reemplaza los datos de prueba:
ssh your_user@old_server_ip \
"sudo mysqldump --single-transaction --quick --routines --triggers --events your_db | gzip" \
| gunzip | sudo mysql your_db
- Haz que el servidor antiguo reenvíe el tráfico al nuevo. Así, los visitantes que aún tengan la IP antigua en caché llegan al servidor nuevo. En el servidor antiguo, sustituye el contenido de los bloques
location /del sitio por un proxy hacia la IP nueva:
sudo nano /etc/nginx/sites-available/your_domain
location / {
proxy_pass https://new_server_ip;
proxy_ssl_server_name on;
proxy_ssl_name $host;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Si el bloque tiene otros location (por ejemplo location ~ \.php$), coméntalos para que todas las peticiones pasen por el proxy. Comprueba y recarga Nginx:
sudo nginx -t && sudo systemctl reload nginx
-
Cambia el registro A de
your_domain(ywww) anew_server_ipen tu proveedor DNS. -
Desactiva el modo mantenimiento en el servidor nuevo, si se copió con los archivos (por ejemplo, borra el archivo
.maintenance).
Desde el paso 4 de esta lista, todas las peticiones terminan en el servidor nuevo, lleguen por DNS nuevo o antiguo. La ventana sin escrituras dura lo que tardan la copia incremental y la importación, normalmente unos minutos.
Notasi tu aplicación registra la IP de los visitantes, las peticiones que pasen por el proxy llegarán con la IP del servidor antiguo. Es temporal; si lo necesitas, configura el módulo
realipde Nginx en el servidor nuevo para confiar en la cabeceraX-Real-IPque envíaold_server_ip.
Paso 7: Verificar la propagación y retirar el servidor antiguo
Comprueba qué IP devuelven varios resolvedores públicos:
dig +short your_domain @1.1.1.1
dig +short your_domain @8.8.8.8
new_server_ip
new_server_ip
En el servidor antiguo, el log de acceso muestra cuánto tráfico sigue llegando por la IP antigua. Debería caer casi a cero en pocos minutos, aunque algunos clientes y bots tardan más:
sudo tail -f /var/log/nginx/access.log
Confirma también en el servidor nuevo que la renovación del certificado funcionará ahora que el dominio apunta a él:
sudo certbot renew --dry-run
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/your_domain/fullchain.pem (success)
Elimina la regla temporal de sudo en el servidor antiguo:
sudo rm /etc/sudoers.d/migracion-rsync
Mantén el servidor antiguo encendido, con el proxy activo, al menos unos días. Después, haz una última copia de seguridad y apágalo. Cuando todo esté estable, puedes volver a subir el TTL del DNS a 3600.
Plan de vuelta atrás
Si aparece un problema grave tras el cambio y no puedes corregirlo rápido en el servidor nuevo:
- Restaura en el servidor antiguo la configuración de Nginx original (quita el proxy) y recárgalo.
- Devuelve el registro A a
old_server_ip. Con el TTL a 300, el cambio se propaga en minutos. - Si el servidor nuevo recibió escrituras importantes (pedidos, registros), cópialas al antiguo antes de reabrirlo, o vuelve a poner el sitio en modo mantenimiento mientras lo haces.
Mientras no hayas retirado el servidor antiguo, volver atrás es cuestión de minutos.
Solución de problemas
rsync: connection unexpectedly closedysudo: a terminal is required: la regla desudoers.dno está en el servidor antiguo o las rutas no son/usr/bin/rsyncy/usr/bin/mysqldump. Compruébalas concommand -v rsync mysqldump.502 Bad Gatewayen el servidor nuevo: el socket de PHP-FPM de la configuración copiada no existe. Revisals /run/php/y corrigefastcgi_passen el bloque de servidor.Error establishing a database connection: el usuario de la base de datos no existe en el servidor nuevo o su contraseña no coincide con la del archivo de configuración de la aplicación.- El proxy del servidor antiguo devuelve
502: el servidor nuevo no acepta conexiones en el puerto 443 desdeold_server_ip. Revisasudo ufw statusen el servidor nuevo. - Bucle de redirecciones tras el cambio: la aplicación no sabe que la petición original era HTTPS. Comprueba que el bloque de servidor del servidor nuevo escucha en 443 con el certificado y no redirige de nuevo a sí mismo.
Conclusión
Has migrado el sitio a un servidor nuevo preparándolo y probándolo antes de tocar el DNS, con una ventana corta sin escrituras y con el servidor antiguo reenviando el tráfico residual, de modo que ningún visitante encuentra el sitio caído. Como siguientes pasos, configura copias de seguridad en el servidor nuevo, revisa el rendimiento con la carga real y, si la base de datos es grande o no admite una ventana de solo lectura, consulta la guía de migración de bases de datos con replicación.
