Copias nocturnas, un script que lee un sensor cada 5 minutos o un reinicio semanal: todo eso se programa con cron. El problema es que un script que funciona perfectamente en la terminal a menudo falla en silencio cuando lo lanza cron. Aquí verás la sintaxis, por qué pasa eso y cómo comprobar si la tarea se ejecutó de verdad.
En resumen
- Cada línea del crontab tiene 5 campos de tiempo (minuto, hora, día del mes, mes, día de la semana) y después el comando.
- Cron arranca los comandos con un entorno mínimo: usa siempre rutas absolutas y redirige la salida a un log.
- En Raspberry Pi OS Bookworm los registros de cron están en el journal: se consultan con
journalctl -u cron.
Todo lo que sigue vale para Raspberry Pi OS Bookworm, tanto Lite como con escritorio. Cron viene instalado y activo de serie.
Cómo se lee una línea de crontab
Una tarea es una línea con este formato:
minuto hora día-del-mes mes día-de-la-semana comando

| Campo | Valores | Notas |
|---|---|---|
| Minuto | 0–59 | |
| Hora | 0–23 | Formato 24 h, hora local del sistema |
| Día del mes | 1–31 | |
| Mes | 1–12 | También jan, feb… |
| Día de la semana | 0–7 | 0 y 7 son domingo; también mon, tue… |
En cada campo puedes usar:
*→ cualquier valor.,→ lista:1,15es «el 1 y el 15».-→ rango:1-5es «de lunes a viernes»./→ intervalo:*/10es «cada 10».
Tabla de ejemplos
| Expresión | Cuándo se ejecuta |
|---|---|
*/5 * * * * | Cada 5 minutos |
0 * * * * | Al principio de cada hora |
30 3 * * * | Todos los días a las 03:30 |
0 8 * * 1-5 | De lunes a viernes a las 08:00 |
0 0 1 * * | El día 1 de cada mes a medianoche |
15 4 * * 0 | Los domingos a las 04:15 |
0 9,21 * * * | A las 09:00 y a las 21:00 |
@reboot | Una vez, al arrancar el sistema |
@daily | Equivale a 0 0 * * * |
@weekly | Equivale a 0 0 * * 0 |
Un detalle que confunde: si restringes a la vez el día del mes y el día de la semana, cron ejecuta la tarea cuando se cumple cualquiera de los dos, no ambos. 0 12 13 * 5 se ejecuta todos los días 13 y todos los viernes, no solo los viernes 13.
Editar el crontab paso a paso
Cada usuario tiene su propio crontab. Las tareas se ejecutan con los permisos de ese usuario.
- Abre tu crontab:
crontab -e
- La primera vez te pide un editor. Si no sabes cuál elegir, nano es el más sencillo.
- Añade la tarea al final del archivo, por ejemplo:
*/5 * * * * /usr/bin/python3 /home/pi/scripts/sensor.py >> /home/pi/logs/sensor.log 2>&1
- Guarda y cierra (en nano: Ctrl+O, Intro, Ctrl+X). Verás el mensaje
crontab: installing new crontab. No hace falta reiniciar nada. - Comprueba lo que hay instalado:
crontab -l
Para tareas que necesitan permisos de administrador, usa el crontab de root con sudo crontab -e. Es mejor que poner sudo dentro de tu crontab, porque ahí no hay nadie para escribir la contraseña.
Ojo con
crontab -r: borra el crontab entero sin preguntar. Larestá justo al lado de laeen el teclado.
El entorno mínimo: por qué tu script falla en cron
Cuando abres una terminal, tu shell carga .bashrc, el PATH completo, tus variables y el directorio actual. Cron no carga nada de eso. Arranca los comandos con /bin/sh, un PATH reducido (normalmente solo /usr/bin:/bin) y el directorio personal como directorio de trabajo.

Consecuencias prácticas:
- Un comando que está en
/usr/local/bino en~/.local/binno se encuentra. - Un script que abre
datos.csvsin ruta lo busca en tu carpeta personal, no junto al script. - Si usas un entorno virtual de Python, cron no lo activa.
La solución es ser explícito:
*/10 * * * * /home/pi/env/bin/python /home/pi/scripts/lectura.py >> /home/pi/logs/lectura.log 2>&1
Llamar directamente al python de dentro del entorno virtual equivale a activarlo. Si necesitas variables, puedes declararlas al principio del crontab:
PATH=/usr/local/bin:/usr/bin:/bin
SHELL=/bin/bash
Para saber qué ruta tiene un programa, usa which:
which rsync
El símbolo % es especial
En un crontab, % significa salto de línea. Un comando como date +%Y-%m-%d se rompe. Escápalo con barra invertida: date +\%Y-\%m-\%d. Otra opción más limpia es meter la lógica en un script y llamar solo al script.
Redirigir la salida a un log
Por defecto, cron intenta enviar por correo lo que imprime el comando. Raspberry Pi OS no trae servidor de correo, así que esa salida se pierde y en el journal aparece un aviso del tipo «No MTA installed, discarding output». Si el script falla, no te enteras.
Por eso conviene terminar cada línea con una redirección:
| Final de la línea | Qué hace |
|---|---|
>> /ruta/log 2>&1 | Añade salida normal y errores al log |
> /ruta/log 2>&1 | Igual, pero sobrescribe el log en cada ejecución |
> /dev/null 2>&1 | Descarta todo (solo si ya sabes que funciona) |
La carpeta del log tiene que existir antes; cron no la crea. Y un log con >> crece sin fin: si la tarea es frecuente, vacíalo de vez en cuando o usa logrotate.
@reboot: tareas al arrancar
@reboot lanza el comando una vez cuando arranca el servicio cron:
@reboot sleep 30 && /home/pi/scripts/inicio.sh >> /home/pi/logs/inicio.log 2>&1
El sleep 30 no es decorativo. Cron arranca pronto, y en ese momento la red o un disco USB pueden no estar listos todavía. Si tu script depende de ellos, espera o, mejor, usa un servicio de systemd con dependencias (más abajo).
Ver si una tarea se ejecutó
En Bookworm no existe /var/log/syslog por defecto: los registros van al journal de systemd. Para ver la actividad de cron:
journalctl -u cron --since today
Cada ejecución aparece como una línea con CMD y el comando lanzado. Para seguirlo en directo mientras esperas a que toque la tarea:
journalctl -u cron -f
Si la línea CMD aparece pero el resultado no es el esperado, el problema está en el script: mira tu log. Si no aparece, revisa la sintaxis con crontab -l y que la hora del sistema sea correcta con timedatectl. La Raspberry Pi no tiene reloj con batería en la mayoría de modelos y toma la hora de la red al arrancar.
Cuándo usar un timer de systemd
Cron es perfecto para tareas sencillas. Un timer de systemd compensa cuando necesitas más control:
| Necesidad | Cron | Timer de systemd |
|---|---|---|
| Sintaxis rápida en una línea | Sí | No: dos archivos |
| Recuperar ejecuciones perdidas con la Pi apagada | No | Sí, con Persistent=true |
| Esperar a que haya red | Manual, con sleep | Sí, con After=network-online.target |
| Logs automáticos en el journal | Solo el lanzamiento | Toda la salida del script |
| Evitar dos ejecuciones solapadas | Manual | Sí, de serie |
| Retraso aleatorio para no saturar | No | Sí, con RandomizedDelaySec |

Un ejemplo mínimo. Primero el servicio, en /etc/systemd/system/copia.service:
[Unit]
Description=Copia diaria de datos
[Service]
Type=oneshot
User=pi
ExecStart=/home/pi/scripts/copia.sh
Después el timer, en /etc/systemd/system/copia.timer:
[Unit]
Description=Lanza la copia cada día a las 03:30
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
[Install]
WantedBy=timers.target
Y se activa con:
sudo systemctl daemon-reload
sudo systemctl enable --now copia.timer
systemctl list-timers
La salida del script queda en journalctl -u copia.service, sin redirecciones. Cambia pi por tu usuario: desde Bullseye, Raspberry Pi OS ya no crea el usuario pi por defecto.
Errores habituales
- El script funciona a mano pero no en cron. Casi siempre es el
PATHo una ruta relativa. Usa rutas absolutas para todo. - El script no tiene permiso de ejecución. Dale permiso con
chmod +x script.sho llámalo con su intérprete (/bin/bash script.sh). - Falta la línea
#!al principio del script. Sin ella, cron no sabe con qué intérprete ejecutarlo. - Usar
%sin escapar en el comando: la línea se corta y falla. - Editar
/etc/crontabsin poner el usuario. Ese archivo, a diferencia decrontab -e, lleva un sexto campo con el usuario antes del comando. - Hora equivocada. Si la zona horaria no es la tuya, las tareas se lanzan desfasadas. Ajústala en
sudo raspi-config, en Localisation Options. - La tarea depende de la red al arrancar y falla de vez en cuando con
@reboot. Pásala a systemd.



