rsync copia ficheros entre directorios o servidores transfiriendo solo las diferencias, lo que lo hace ideal para copias de seguridad diarias. Combinado con la opción --link-dest, cada copia se comporta como una instantánea completa y navegable, pero los ficheros que no han cambiado no ocupan espacio adicional. En este tutorial configurarás un servidor de backup con Ubuntu 24.04 que cada noche descarga por SSH los datos de un servidor de producción, conserva un número fijo de instantáneas y usa una clave que solo permite leer.
Requisitos previos
Para seguir esta guía necesitas:
- Servidor de producción (
your_server_ip): el que quieres respaldar, con Ubuntu 24.04 LTS. - Servidor de backup: otro servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath en una ubicación distinta, con espacio en disco suficiente para una copia completa más los cambios de los días que quieras conservar.
- Un usuario no root con privilegios
sudoen ambos servidores. - Acceso SSH del servidor de backup al de producción por el puerto 22 (o el que uses).
Esta guía usa el modelo pull: es el servidor de backup quien se conecta y descarga los datos. Así, si alguien compromete el servidor de producción, no tiene credenciales para borrar las copias.
Notarsync copia ficheros. Para bases de datos, genera primero un volcado consistente (
mysqldump,pg_dumpall) en un directorio que rsync incluya, en lugar de copiar los ficheros de datos en caliente.
Paso 1: Comprobar rsync en ambos servidores
Ubuntu 24.04 incluye rsync de serie. Compruébalo en los dos servidores e instálalo si falta:
sudo apt update
sudo apt install rsync
rsync --version | head -n 1
rsync version 3.2.7 protocol version 31
Antes de automatizar nada, conviene entender las opciones que usarás:
| Opción | Efecto |
|---|---|
-a | Modo archivo: recursivo, conserva permisos, propietarios, fechas y enlaces simbólicos |
-H | Conserva los enlaces duros |
-A | Conserva las ACL |
-X | Conserva los atributos extendidos |
--numeric-ids | Guarda los UID y GID numéricos, sin traducirlos a nombres del servidor de backup |
--link-dest=DIR | Si un fichero no ha cambiado respecto a DIR, crea un enlace duro en lugar de copiarlo |
-n | Simulación: muestra qué haría sin copiar nada |
Recuerda también la regla de la barra final: rsync -a /var/www dest/ crea dest/www, mientras que rsync -a /var/www/ dest/ copia el contenido de www directamente en dest/.
Paso 2: Crear una clave SSH para el backup
Para conservar propietarios y permisos de todos los ficheros, rsync necesita leer como root en producción y escribir como root en el servidor de backup. En el servidor de backup, genera una clave dedicada, sin frase de paso porque la usará un proceso automático:
sudo ssh-keygen -t ed25519 -f /root/.ssh/rsync_backup -N "" -C "rsync-backup"
sudo cat /root/.ssh/rsync_backup.pub
Copia la línea que empieza por ssh-ed25519.
Paso 3: Restringir la clave a lectura con rrsync
Una clave de root sin restricciones daría al servidor de backup control total sobre producción. El paquete rsync de Ubuntu incluye rrsync, un script que, usado como comando forzado de SSH, solo permite ejecutar rsync, y con -ro solo en modo lectura.
En el servidor de producción, edita las claves autorizadas de root:
sudo nano /root/.ssh/authorized_keys
Añade una línea con este formato, sustituyendo your_backup_server_ip por la IP del servidor de backup y pegando la clave pública al final:
command="/usr/bin/rrsync -ro /",restrict,from="your_backup_server_ip" ssh-ed25519 AAAA...tu_clave... rsync-backup
command="/usr/bin/rrsync -ro /": cualquier conexión con esta clave ejecuta rrsync, que solo acepta lecturas de rsync.restrict: desactiva terminal, reenvío de puertos y de agente.from=: solo acepta la clave desde la IP del servidor de backup.
Comprueba cómo trata SSH el acceso de root en producción:
sudo sshd -T | grep -i permitrootlogin
El valor por defecto en Ubuntu, prohibit-password, permite el acceso con clave y sirve. Si el valor es no, cámbialo a forced-commands-only, que solo permite a root entrar con claves que tienen comando forzado, como esta. Edita /etc/ssh/sshd_config (o el fichero de /etc/ssh/sshd_config.d/ donde esté definido):
PermitRootLogin forced-commands-only
Y recarga SSH:
sudo systemctl reload ssh
Paso 4: Probar la conexión y hacer la primera copia manual
Desde el servidor de backup, prueba una simulación. La primera vez SSH te pedirá confirmar la huella del servidor; compárala con la de producción antes de aceptar:
sudo rsync -an -e "ssh -i /root/.ssh/rsync_backup" root@your_server_ip:/etc/hostname /tmp/
Si no hay errores, la clave funciona. Comprueba ahora que esa misma clave no permite abrir una shell:
sudo ssh -i /root/.ssh/rsync_backup root@your_server_ip
La conexión debe cerrarse con un mensaje de error de rrsync en lugar de darte una terminal.
Crea el directorio de las copias y un fichero de exclusiones con lo que no tiene sentido respaldar:
sudo install -d -m 700 /srv/backups/web01
sudo install -d -m 755 /etc/rsync-backup
sudo nano /etc/rsync-backup/web01.exclude
*.tmp
*.swp
/var/www/*/cache/
/var/www/*/node_modules/
/root/.cache/
/home/*/.cache/
Los patrones que empiezan por / se anclan a la raíz de cada ruta transferida. Ajusta la lista a tus aplicaciones.
Paso 5: Crear el script de instantáneas
El script hace una copia en un directorio temporal incomplete, usando la instantánea anterior como referencia para --link-dest. Solo si rsync termina bien, renombra el directorio con la fecha y actualiza el enlace latest. Por último, borra las instantáneas más antiguas que excedan el número a conservar.
sudo nano /usr/local/sbin/rsync-snapshot
#!/usr/bin/env bash
set -euo pipefail
SOURCE_HOST="root@your_server_ip"
SOURCE_PATHS=(/etc /home /root /var/www /var/backups)
BACKUP_ROOT="/srv/backups/web01"
EXCLUDE_FILE="/etc/rsync-backup/web01.exclude"
SSH_KEY="/root/.ssh/rsync_backup"
KEEP=14
stamp="$(date +%Y-%m-%d_%H%M%S)"
work="${BACKUP_ROOT}/incomplete"
latest="${BACKUP_ROOT}/latest"
sources=()
for path in "${SOURCE_PATHS[@]}"; do
sources+=("${SOURCE_HOST}:${path}")
done
link_dest=()
if [[ -d "${latest}" ]]; then
link_dest=(--link-dest="${latest}")
fi
mkdir -p "${work}"
rc=0
rsync -aHAX --numeric-ids --delete \
--exclude-from="${EXCLUDE_FILE}" \
"${link_dest[@]}" \
-e "ssh -i ${SSH_KEY} -o BatchMode=yes" \
"${sources[@]}" "${work}/" || rc=$?
# 24 = algunos ficheros desaparecieron durante la copia (normal en sistemas en uso)
if [[ ${rc} -ne 0 && ${rc} -ne 24 ]]; then
echo "rsync falló con código ${rc}" >&2
exit "${rc}"
fi
mv "${work}" "${BACKUP_ROOT}/${stamp}"
ln -sfn "${stamp}" "${latest}"
echo "Instantánea creada: ${BACKUP_ROOT}/${stamp}"
mapfile -t snapshots < <(find "${BACKUP_ROOT}" -mindepth 1 -maxdepth 1 -type d \
-name '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]_[0-9]*' | sort)
excess=$(( ${#snapshots[@]} - KEEP ))
for (( i = 0; i < excess; i++ )); do
echo "Borrando instantánea antigua: ${snapshots[i]}"
rm -rf -- "${snapshots[i]}"
done
Algunos detalles del diseño:
- Si una ejecución falla, el directorio
incompletese queda y la siguiente lo reutiliza, así que solo transfiere lo que faltaba.--deletelimpia de él lo que ya no existe en origen. - El enlace
latestes relativo, de modo que sigue funcionando si montas el disco en otra ruta. - Las rutas de
SOURCE_PATHSsin barra final se guardan con su nombre dentro de cada instantánea (etc/,home/...). Quita de la lista las que no existan en tu servidor, porque rsync devuelve un error si falta alguna.
Hazlo ejecutable solo por root y lánzalo a mano:
sudo chmod 700 /usr/local/sbin/rsync-snapshot
sudo /usr/local/sbin/rsync-snapshot
Instantánea creada: /srv/backups/web01/2026-09-25_103012
Lanza una segunda ejecución y compara el espacio de ambas. du cuenta cada enlace duro una sola vez, así que la segunda instantánea solo muestra lo que ha cambiado:
sudo /usr/local/sbin/rsync-snapshot
sudo du -sh /srv/backups/web01/2026-*
1.8G /srv/backups/web01/2026-09-25_103012
4.2M /srv/backups/web01/2026-09-25_103540
Paso 6: Programar el backup con un temporizador systemd
systemd registra la salida en el journal y no inicia una segunda ejecución mientras la anterior siga en marcha. Crea el servicio en el servidor de backup:
sudo nano /etc/systemd/system/rsync-snapshot.service
[Unit]
Description=Instantánea rsync de web01
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/rsync-snapshot
Nice=10
IOSchedulingClass=idle
Y el temporizador, que lo ejecuta cada noche a las 02:30 con un retraso aleatorio de hasta 15 minutos:
sudo nano /etc/systemd/system/rsync-snapshot.timer
[Unit]
Description=Instantánea rsync diaria de web01
[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=15min
Persistent=true
[Install]
WantedBy=timers.target
Actívalo y comprueba la próxima ejecución:
sudo systemctl daemon-reload
sudo systemctl enable --now rsync-snapshot.timer
systemctl list-timers rsync-snapshot.timer
NEXT LEFT LAST PASSED UNIT ACTIVATES
Fri 2026-09-26 02:38:11 UTC 15h left - - rsync-snapshot.timer rsync-snapshot.service
Después de la primera ejecución programada, revisa el resultado en el journal:
sudo journalctl -u rsync-snapshot.service -n 20 --no-pager
Si el backup falla, systemctl status rsync-snapshot.service mostrará el estado failed. Incluye ese estado en tu monitorización o añade OnFailure= al servicio para lanzar una unidad que te notifique.
Paso 7: Verificar y restaurar
Cada instantánea es un árbol de directorios normal, así que puedes navegarla directamente:
sudo ls /srv/backups/web01/latest/
backups etc home root www
Para comprobar que la copia coincide con producción, lanza una simulación con --checksum, que compara el contenido de cada fichero. Una lista vacía o con pocos ficheros recientes indica que la copia está al día:
sudo rsync -an --checksum --itemize-changes \
-e "ssh -i /root/.ssh/rsync_backup" \
root@your_server_ip:/etc/ /srv/backups/web01/latest/etc/
Para restaurar, la clave de backup no sirve porque es de solo lectura, y es intencionado. Copia lo que necesites a producción con tu usuario de administración y muévelo después con sudo. Por ejemplo, para recuperar un fichero de la instantánea de hace unos días, cópialo primero a tu directorio personal en el servidor de backup:
sudo install -o "$USER" -m 600 /srv/backups/web01/2026-09-22_023512/www/html/wp-config.php ~/wp-config.php
Envíalo a producción con tu usuario y borra la copia temporal:
rsync -a ~/wp-config.php your_user@your_server_ip:/tmp/
rm ~/wp-config.php
Y en el servidor de producción:
sudo install -o www-data -g www-data -m 640 /tmp/wp-config.php /var/www/html/wp-config.php
Haz una prueba de restauración completa de vez en cuando en un servidor de pruebas: es la única forma de saber que el backup sirve y cuánto tardas en recuperarte.
Solución de problemas
Permission denied (publickey). La clave pública no está en /root/.ssh/authorized_keys de producción, la IP de from= no coincide con la IP de salida del servidor de backup o PermitRootLogin está en no. Revisa sudo journalctl -u ssh en producción.
Host key verification failed. El servidor de backup aún no conoce la huella de producción y el script usa BatchMode. Ejecuta una vez a mano el comando de prueba del paso 4 y acepta la huella.
Código de salida 23 (some files/attrs were not transferred). Alguna ruta de SOURCE_PATHS no existe o hay ficheros que no se pueden leer. El mensaje anterior del journal indica cuáles.
file has vanished. Un fichero se borró mientras rsync lo copiaba. Es normal en directorios con ficheros temporales y el script lo trata como éxito (código 24). Si ocurre mucho, excluye esos directorios.
El backup satura la red de producción. Limita el ancho de banda añadiendo, por ejemplo, --bwlimit=20000 (en KiB/s) a la orden rsync del script.
El disco de backup se llena. Reduce KEEP, amplía las exclusiones o revisa con sudo du -sh /srv/backups/web01/* qué instantáneas crecen más, lo que señala directorios con muchos cambios diarios.
Conclusión
Has configurado backups diarios con rsync en modelo pull, con una clave de solo lectura limitada por rrsync, instantáneas incrementales con enlaces duros, retención automática y un temporizador systemd. Esta copia en otro servidor cubre uno de los soportes de la regla 3-2-1; como siguientes pasos, añade una copia cifrada en un almacenamiento externo con restic, programa volcados de tus bases de datos antes de la ventana de rsync y configura una alerta cuando el servicio falle.
