Cómo programar tareas en Linux con cron y systemd

· 7 min de lectura · por el equipo técnico de VPS Argentina

Para que un comando corra solo a una hora fija, abrí el crontab con crontab -e y agregá una línea con cinco campos de tiempo (minuto, hora, día del mes, mes y día de la semana) y el comando con su ruta completa: 30 3 * * * /usr/local/bin/respaldo.sh corre todos los días a las 3:30. Mandá la salida a un log para enterarte de los errores y escribí cada % como \%. En el hosting se hace igual desde la sección de tareas cron de DirectAdmin. Para correr algo cada vez que arranca el servidor, usá una unidad de systemd.

Cómo se lee una línea de cron

Cada línea del crontab es una tarea: cinco campos de tiempo separados por espacios y, después, el comando. La tarea corre cada vez que el reloj del servidor coincide con los cinco campos.

  • Minuto: de 0 a 59.
  • Hora: de 0 a 23.
  • Día del mes: de 1 a 31.
  • Mes: de 1 a 12.
  • Día de la semana: de 0 a 7. El 0 y el 7 son domingo, y el 1, lunes.
  • En cualquier campo, * quiere decir todos los valores, la coma arma una lista (1,15), el guion un rango (1-5) y la barra un intervalo: */10 en los minutos es cada 10 minutos.
  • Si completás el día del mes y el día de la semana a la vez, alcanza con que se cumpla uno de los dos: 0 3 1 * 1 corre el día 1 de cada mes y, además, todos los lunes.

Horarios de ejemplo

ExpresiónCuándo corre
*/5 * * * *Cada 5 minutos.
0 * * * *Cada hora, en punto.
30 3 * * *Todos los días a las 3:30.
0 9 * * 1-5Los días hábiles a las 9:00, de lunes a viernes. Los feriados no los distingue.
0 4 * * 0Los domingos a las 4:00.
0 3 1 * *Una vez por mes, el día 1 a las 3:00.

Las horas son las del reloj del servidor, en su zona horaria.

Crear la tarea con crontab -e

Cada usuario tiene su propio crontab. crontab -e abre el tuyo en un editor: guardás, cerrás y la tarea queda activa, sin reiniciar nada. Este ejemplo, en el crontab de root, tiene dos tareas:

MAILTO=juan@tudominio.com.ar
# Cada 5 minutos. Lo que imprima llega por correo a MAILTO
*/5 * * * * /usr/local/bin/revisar-sitio.sh
# Todos los días a las 3:30, con la salida en un log por mes
30 3 * * * /usr/local/bin/respaldo.sh >> /var/log/respaldo-$(date +\%Y-\%m).log 2>&1
  1. La primera vez, Debian y Ubuntu pueden preguntarte qué editor usar: nano es el más simple. En AlmaLinux se abre vi; para usar nano, EDITOR=nano crontab -e (si no está, dnf install nano).
  2. crontab -l muestra las tareas cargadas, y como root, crontab -u juan -e edita las de otro usuario.
  3. Revisá la zona horaria con timedatectl. Para usar la de Argentina, timedatectl set-timezone America/Argentina/Buenos_Aires, y después reiniciá cron para que la tome: systemctl restart cron en Debian y Ubuntu, systemctl restart crond en AlmaLinux.
  4. Si el comando crontab no existe, instalalo: apt install cron en Debian y Ubuntu, o dnf install cronie y systemctl enable --now crond en AlmaLinux.

Por qué anda a mano y no en cron

Casi siempre es el entorno. Cron no lee tu .bashrc ni tu perfil: cada tarea arranca con lo mínimo.

  • El PATH de cron es corto: /usr/bin:/bin en Debian y Ubuntu, y en AlmaLinux suma /usr/sbin y /sbin. En ninguno está /usr/local/bin. Escribí la ruta completa de cada comando (command -v php te la dice) o poné al principio del crontab una línea con todas las carpetas: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin.
  • Lo que imprime la tarea, errores incluidos, se manda por correo al dueño del crontab o a la dirección de MAILTO. Para que ese correo salga, el servidor necesita un programa de envío, como Postfix. Si no lo tiene, la salida se descarta y los errores pasan sin que te enteres.
  • Por eso conviene mandar todo a un log: >> /var/log/mi-tarea.log 2>&1. >> agrega al final sin borrar lo anterior y 2>&1 suma los errores. Con MAILTO="" apagás el correo.
  • Los comandos los ejecuta /bin/sh, no bash, y en Debian y Ubuntu eso es dash, que no entiende algunas construcciones de bash. Si la línea se complica, pasala a un script que empiece con #!/bin/bash, o poné SHELL=/bin/bash al principio del crontab.

Tareas cron en el hosting, desde DirectAdmin

En el hosting no tenés acceso de root, pero DirectAdmin tiene su sección de tareas cron (Cronjobs), dentro de Funciones avanzadas. El formulario pide los mismos cinco campos y el comando, y la tarea corre con tu usuario del hosting.

Valen las mismas reglas: rutas completas, \% en lugar de % y la salida a un archivo de tu cuenta. Por ejemplo, para correr un script PHP todos los días a las 4:

  • Minuto 0, hora 4 y * en los otros tres campos.
  • Comando: /usr/local/bin/php /home/juan/domains/tudominio.com.ar/public_html/tarea.php >> /home/juan/cron.log 2>&1. En DirectAdmin, PHP está en /usr/local/bin/php, fuera del PATH de cron.

Un script al arrancar el servidor, con systemd

Para correr algo cada vez que arranca el servidor, usá una unidad de systemd: ves si falló, lo que imprime queda en el registro del sistema y podés hacer que espere a que la red esté lista. Creá /etc/systemd/system/mi-tarea.service:

[Unit]
Description=Mi tarea al arrancar
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/mi-tarea.sh
[Install]
WantedBy=multi-user.target
  1. El script tiene que empezar con #!/bin/bash y tener permiso de ejecución: chmod +x /usr/local/bin/mi-tarea.sh.
  2. systemctl daemon-reload hace que systemd lea la unidad nueva. Repetilo cada vez que la edites.
  3. systemctl enable --now mi-tarea.service la activa para cada arranque y además la corre en ese momento. Si no querés que corra ahora, usá solo enable.
  4. systemctl status mi-tarea.service muestra cómo terminó, y journalctl -u mi-tarea.service, todo lo que imprimió.
  5. En AlmaLinux, si creaste el script en otra carpeta y lo moviste con mv, corré restorecon -v /usr/local/bin/mi-tarea.sh. Si conserva la etiqueta de SELinux de la carpeta de origen, systemd no lo puede ejecutar.
  6. Si en vez de un script que termina es un programa que queda corriendo, sacá Type=oneshot y agregá Restart=on-failure en [Service]: systemd lo vuelve a levantar si se cae. Con User=juan corre con ese usuario y no como root.

Timers de systemd, la alternativa a cron

Con systemd también se programan tareas por horario, con dos ventajas sobre cron: lo que imprime la tarea queda en el registro sin configurar nada y, con Persistent=true, si el servidor estaba apagado a la hora prevista, la tarea corre apenas arranca. Un timer activa la unidad .service de su mismo nombre. Para un respaldo diario a las 3:30, creá /etc/systemd/system/respaldo.timer:

[Unit]
Description=Respaldo diario
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
[Install]
WantedBy=timers.target
  • Al lado va respaldo.service: igual a la unidad de arriba, con su propio ExecStart y sin la sección [Install]. El que se habilita es el timer, no el servicio.
  • Activalo con systemctl daemon-reload y systemctl enable --now respaldo.timer.
  • systemctl list-timers muestra cuándo corre la próxima vez, y systemd-analyze calendar "*-*-* 03:30:00" te deja probar una expresión antes de usarla.

Preguntas frecuentes

¿Cómo sé si la tarea corrió?

Cron anota cada ejecución en el registro del sistema: journalctl -t CRON en Debian y Ubuntu, journalctl -t CROND en AlmaLinux. Ahí ves que arrancó, no si terminó bien: para eso está el log al que mandaste la salida.

¿Sirve @reboot para correr algo al arrancar?

Para algo simple, sí: @reboot en lugar de los cinco campos corre el comando cuando arranca cron. Pero no espera a que la red esté lista y no tenés un systemctl status para ver cómo terminó. Para algo que importa, usá una unidad de systemd.

¿Cómo evito que una ejecución se pise con la anterior?

Si la tarea puede tardar más que el intervalo, corrila con flock: */5 * * * * /usr/bin/flock -n /tmp/revisar.lock /usr/local/bin/revisar-sitio.sh. Con -n, si la anterior sigue corriendo, la nueva no arranca. Con un timer de systemd no hace falta: no arranca una segunda copia mientras la primera sigue activa.

¿Dónde se guardan las tareas?

El crontab de cada usuario queda en /var/spool/cron/crontabs/ en Debian y Ubuntu, y en /var/spool/cron/ en AlmaLinux. No lo edites a mano: crontab -e revisa la sintaxis antes de guardar y te avisa si algo está mal.

Seguí leyendo