--- title: "Backups automáticos con rsync y systemd en Ubuntu 24.04" description: "Crea copias incrementales diarias de un servidor Ubuntu 24.04 con rsync por SSH, instantáneas con enlaces duros, clave de solo lectura y un timer systemd." category: "Recuperación de copias" language: es source: https://cubepath.com/es/docs/recuperación-de-copias/respaldo-automatico-con-rsync updated: 2026-09-25 --- # Backups automáticos con rsync y systemd en Ubuntu 24.04 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 `sudo` en 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. > Nota: rsync 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: ```bash sudo apt update sudo apt install rsync rsync --version | head -n 1 ``` ```output 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: ```bash 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: ```bash 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: ```text 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: ```bash 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): ```text PermitRootLogin forced-commands-only ``` Y recarga SSH: ```bash 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: ```bash 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: ```bash 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: ```bash sudo install -d -m 700 /srv/backups/web01 sudo install -d -m 755 /etc/rsync-backup sudo nano /etc/rsync-backup/web01.exclude ``` ```text *.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. ```bash sudo nano /usr/local/sbin/rsync-snapshot ``` ```bash #!/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 `incomplete` se queda y la siguiente lo reutiliza, así que solo transfiere lo que faltaba. `--delete` limpia de él lo que ya no existe en origen. - El enlace `latest` es relativo, de modo que sigue funcionando si montas el disco en otra ruta. - Las rutas de `SOURCE_PATHS` sin 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: ```bash sudo chmod 700 /usr/local/sbin/rsync-snapshot sudo /usr/local/sbin/rsync-snapshot ``` ```output 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: ```bash sudo /usr/local/sbin/rsync-snapshot sudo du -sh /srv/backups/web01/2026-* ``` ```output 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**: ```bash sudo nano /etc/systemd/system/rsync-snapshot.service ``` ```ini [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: ```bash sudo nano /etc/systemd/system/rsync-snapshot.timer ``` ```ini [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: ```bash sudo systemctl daemon-reload sudo systemctl enable --now rsync-snapshot.timer systemctl list-timers rsync-snapshot.timer ``` ```output 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: ```bash 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: ```bash sudo ls /srv/backups/web01/latest/ ``` ```output 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: ```bash 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: ```bash 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: ```bash rsync -a ~/wp-config.php your_user@your_server_ip:/tmp/ rm ~/wp-config.php ``` Y en el servidor de producción: ```bash 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.