Tutorial: migrar tareas programadas de cron a systemd timers en Debian/Ubuntu

Linux & Sistemas · Por · Publicado el · Actualizado el · 9 min de lectura

Primer plano de código HTML con resaltado de sintaxis en editor de texto
Primer plano de código en editor, representando trabajo técnico y automatización Foto: Bibek ghosh / Pexels

Este paso a paso muestra cómo migrar tareas programadas de cron a systemd timers en Debian/Ubuntu con seguridad. Vas a mapear la sintaxis de cron a OnCalendar, crear unidades .service y .timer, validar logs, configurar tolerancias y disponer de un plan de rollback. El foco es la práctica y la previsibilidad en producción.

Prerequisitos y objetivos de la migración

La migración a systemd timers aporta trazabilidad, control de dependencias y mejor observabilidad. Tendrás logs centralizados vía journal, facilidades para reintentos y programaciones más expresivas con OnCalendar.

Requisitos: acceso root o sudo, editor de texto (nano/vi) y familiaridad básica con systemctl y permisos de archivo. Si la tarea es crítica, prueba primero en un entorno de homologación.

Beneficios prácticos:

  • Logs consolidados con journalctl y estado por unidad.
  • Ejecución con usuario dedicado, directorio de trabajo y variables de entorno claras.
  • Programaciones ricas (calendarios, tolerancias, retraso aleatorio).
  • Persistencia para ejecutar tareas perdidas cuando el host estuvo apagado.
  • Mejor integración con monitorización y alertas.

1) Levantar todo lo que está en cron (crontab y cron.d)

Antes de migrar, descubre todas las programaciones activas del sistema y de usuarios. Haz un backup del contenido para referencia. No elimines nada todavía — vamos a migrar, validar y solo entonces desactivar el cron equivalente.

Si tienes jobs que interactúan con bases de datos y pueden causar bloqueos, haz una revisión previa del impacto en concurrencia y ventanas de mantenimiento; consulta la guía para auditar y optimizar jobs cron.

# Listar crontab do root e de um usuário específico
sudo crontab -l || echo "(sem crontab do root)"
crontab -l -u appuser || echo "(sem crontab de appuser)"

# Listar definições em cron.d e arquivos de sistema
sudo ls -1 /etc/cron.d/ || true
sudo grep -R --line-number --color=never -E '^[^#].*' /etc/crontab /etc/cron.* 2>/dev/null || true

# Backup rápido dos crontabs
sudo crontab -l > ~/backup-root-crontab-$(date +%F).txt 2>/dev/null || true
crontab -l -u appuser > ~/backup-appuser-crontab-$(date +%F).txt 2>/dev/null || true

2) Mapear la programación de cron a OnCalendar (systemd)

OnCalendar acepta expresiones de calendario con precisión de segundos, intervalos y nombres de días/meses. La tabla abajo ayuda a convertir patrones comunes de cron. Para verificar si la expresión está correcta, usa systemd-analyze calendar.

Recuerda la zona horaria: systemd usa la hora local del sistema. Ajusta con timedatectl si es necesario.

Patrón cron Intención Equivalente OnCalendar (ejemplo)
0 2 * * * Diario a las 02:00 --* 02:00:00
0 2 * * 1-5 Laborables a las 02:00 Mon..Fri --* 02:00:00
*/15 * * * * Cada 15 minutos *:0/15
0 */6 * * * Cada 6 horas --* 00,06,12,18:00:00
30 1 1 * * Cada día 1 a las 01:30 --01 01:30:00
# Testar como o systemd interpreta a expressão
systemd-analyze calendar "Mon..Fri *-*-* 02:00:00"
# Saída mostra a próxima execução e interpretação amigável

3) Crear la unidad .service correspondiente al job

Cada job de cron se convierte en un .service con la acción real en ExecStart. Separa la lógica en scripts versionados y usa rutas absolutas. Define el usuario, directorio y variables según sea necesario. Evita ejecutar como root sin necesidad. Luego, ajusta permisos del archivo para no exponer secretos.

  • Guarda en /etc/systemd/system/ si es de alcance del sistema.
  • Usa User=appuser para jobs de aplicación.
  • Define WorkingDirectory y Environment según el script.
# /etc/systemd/system/backup-db.service
[Unit]
Description=Backup do banco de dados
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
User=appuser
WorkingDirectory=/opt/app
# Exemplo de variáveis (opcional)
Environment=PGHOST=/var/run/postgresql
Environment=PGUSER=backup
# Caminho absoluto do script/comando
ExecStart=/usr/local/bin/backup-db.sh
# (Opcional) limites de recursos
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=6

[Install]
WantedBy=multi-user.target

4) Crear la unidad .timer con OnCalendar y opciones útiles

El .timer apunta al .service y define cuándo ejecutar. Persistent asegura la ejecución de runs perdidos durante downtime. RandomizedDelaySec reduce el thundering herd en múltiples hosts. AccuracySec controla la tolerancia del disparo.

Si el .timer y el .service tienen el mismo nombre base, no es necesario definir Unit=, pero puedes dejarlo para mayor claridad.

  • OnCalendar define la programación principal.
  • Persistent=true ejecuta pendientes al reiniciar.
  • RandomizedDelaySec añade jitter controlado.
# /etc/systemd/system/backup-db.timer
[Unit]
Description=Timer do backup do banco (dias úteis 02:00)

[Timer]
OnCalendar=Mon..Fri *-*-* 02:00:00
Persistent=true
RandomizedDelaySec=5m
AccuracySec=1min
Unit=backup-db.service

[Install]
WantedBy=timers.target

5) Cargar, habilitar y probar (dry‑run controlado)

Después de crear los archivos, recarga systemd, habilita e inicia el timer. Para probar la lógica, lanza manualmente el .service. No desactives el cron original hasta validar la ejecución completa, para evitar ventanas sin job o ejecuciones duplicadas.

  • Habilita el timer para iniciar en el arranque.
  • Usa systemctl list-timers para comprobar la programación.
  • Arranca el .service manualmente para prueba inmediata.
sudo systemctl daemon-reload
sudo systemctl enable --now backup-db.timer

# Listar timers y ver la próxima ejecución
systemctl list-timers --all | grep backup-db || true

# Teste manual do serviço
sudo systemctl start backup-db.service
sudo systemctl status backup-db.service --no-pager

6) Validar ejecución y logs con journalctl

Sigue los logs por unidad y confirma variables, rutas y códigos de salida. Comprueba también la zona horaria y si el host mantiene la hora correcta vía NTP. Para estandarizar logs de aplicaciones, considera estructurar salidas en JSON; consulta ideas en logs estructurados (JSON).

  • journalctl -u muestra historial por servicio.
  • status muestra las últimas líneas y código de retorno.
  • Confirma timezone y NTP para programaciones correctas.
# Logs e status
date && timedatectl | sed -n '1,6p'
journalctl -u backup-db.service -n 100 --no-pager
journalctl -u backup-db.timer -n 50 --no-pager

# Última/Próxima execução do timer
systemctl status backup-db.timer --no-pager

7) Rollback seguro a cron (si algo sale mal)

Si la migración no cumple lo esperado, realiza un rollback sin prisa. Desactiva el timer antes de reactivar el cron, evitando ejecuciones duplicadas. Documenta el motivo y los ajustes necesarios. Solo elimina las unidades después de estabilizar el cron nuevamente.

  • Desactiva y detén el timer.
  • Reactiva la línea correspondiente en el crontab.
  • Elimina las units y recarga systemd solo cuando estés listo.
# Desativar timer e serviço
sudo systemctl disable --now backup-db.timer
sudo systemctl stop backup-db.service || true

# Reativar no crontab (edite e descomente a linha antiga)
sudo systemctl crontab -e

# Opcional: remover units e recarregar
sudo rm -f /etc/systemd/system/backup-db.{timer,service}
sudo systemctl daemon-reload

8) Verificación final y resolución de problemas comunes

Usa herramientas de diagnóstico de systemd para confirmar la programación e investigar errores. Problemas frecuentes incluyen rutas relativas en ExecStart, permisos de archivos, variables ausentes y timezone incorrecto. Para hosts nuevos, revisa ajustes básicos de seguridad y tuning en el checklist de seguridad para VPS.

  • Usa systemd-analyze calendar para comprobar la expresión.
  • Asegura rutas absolutas y permiso de ejecución del script.
  • Ajusta User= y WorkingDirectory cuando sea necesario.
  • Si se perdieron ejecuciones, activa Persistent=true.
  • Verifica conflictos con cron aún habilitado.
  • Sincroniza la hora con NTP y confirma la zona horaria.
# Confirmar parsing da expressão do timer
systemd-analyze calendar "Mon..Fri *-*-* 02:00:00"

# Conferir unidades com erro
a=($(systemctl --failed --type=service --no-legend | awk '{print $1}')); printf '%s
' "${a[@]}"

# Timers ativos e próximos disparos
systemctl list-timers --all --no-pager

Preguntas frecuentes

¿Por qué usar systemd timers en lugar de cron?

Systemd timers ofrecen logs integrados, dependencias, tolerancia a retrasos (Persistent), jitter para evitar picos simultáneos y control detallado por unidad. Esto facilita la observabilidad y la automatización operativa, especialmente en entornos con muchos hosts y requisitos de cumplimiento.

¿Cómo convertir una expresión cron compleja a OnCalendar?

Divide la intención en reglas de calendario explícitas o en múltiples líneas OnCalendar. Usa nombres de días/meses y rangos. Valida con systemd-analyze calendar para ver la próxima ejecución y confirmar el parsing. Cuando sea necesario, crea más de un timer para cubrir variaciones.

¿Con qué usuario se ejecutan los timers?

Define el usuario en la unidad .service con User=appuser. Evita root cuando sea posible. Si prefieres timers por usuario, usa unidades en ~/.config/systemd/user y gestiona con systemctl --user. Asegura permisos correctos para scripts, directorios y archivos de log.

¿Cómo registrar la salida y errores de mi job?

Por defecto, stdout y stderr van al journal. Consúltalos con journalctl -u mi-unidad.service. Si quieres archivos dedicados, redirige la salida en el script o usa StandardOutput y StandardError en la unit. Considera logs estructurados para facilitar filtros y correlación.

¿Y si el servidor estaba apagado en la hora del job?

Activa Persistent=true en el .timer. Al reiniciar el host, systemd ejecuta las instancias perdidas desde la última verificación. Combínalo con RandomizedDelaySec para distribuir la carga y evitar thundering herd en clusters.

¿Puedo deshabilitar cron después de migrar todo?

Sí, tras migrar, validar y documentar la equivalencia de todos los jobs, puedes desactivar las entradas correspondientes en cron. Hazlo de forma gradual, monitoreando logs y resultados para evitar brechas de ejecución o duplicidades.

¿Necesitas revisar tu migración de cron a systemd?

Si quieres analizar casos específicos, mapear programaciones complejas y validar rollback, contacta para que revisemos el escenario y los próximos pasos.

Hablar sobre tu migración

Ver también