Cron has one assumption baked in: the machine is running when the clock hits the scheduled minute. If a laptop is asleep at 03:00, or a lab VM is shut down over the weekend, the job simply does not happen, and nothing ever tries again. Our cron jobs guide covers the crontab side of scheduling. This article covers the tool built for the other case.
anacron does not care what time it is. It asks a simpler question: "has this job run in the last N days?" If not, it runs it, after a delay. That makes it the right scheduler for laptops, desktops, and any server that is not guaranteed to be up at a fixed hour, and it is already quietly running the cron.daily directory on many systems you manage.
How anacron Decides What to Run
anacron is not a daemon. Something else starts it (cron, a systemd timer, or boot), it reads /etc/anacrontab, and for each job compares today's date against a timestamp file. Jobs that are due are started after their delay; when everything is finished, anacron exits.
# One timestamp file per job identifier, holding a date (YYYYMMDD)
$ ls /var/spool/anacron/
cron.daily cron.monthly cron.weekly
$ cat /var/spool/anacron/cron.daily
20261002
# What a normal run looks like (timestamp and hostname columns trimmed)
$ journalctl -t anacron --since today --no-pager
anacron[1412]: Anacron started on 2026-10-02
anacron[1412]: Will run job `cron.daily' in 5 min.
anacron[1412]: Jobs will be executed sequentially
anacron[1412]: Job `cron.daily' started
anacron[1412]: Job `cron.daily' terminated
anacron[1412]: Normal exit (1 job run)
| cron | anacron | systemd timer (Persistent=true) | |
|---|---|---|---|
| Granularity | Minute | Day | Microsecond (calendar or monotonic) |
| Missed runs | Lost | Run on next invocation | Run once at next boot or timer start |
| Runs as | Any user, per crontab | root by default (see per-user section) | Any user or system unit |
| Is a daemon | Yes (crond / cron) | No, started by something else | Managed by PID 1 |
| Overlap protection | None built in | Per-job lock on timestamp file | One instance of the unit at a time |
| Best for | Fixed times on always-on hosts | Daily+ jobs on intermittent hosts | Anything new on systemd hosts |
Day granularity is the whole design β anacron cannot schedule anything more often than once a day. If you need hourly jobs, they belong in cron or a timer.
Exit status is ignored β anacron records that a job ran for the day, not that it succeeded. A backup script that fails still counts as done until the period elapses again, so the script itself must alert on failure.
Locking β if two anacron processes start at once, each job is locked through its timestamp file, so the same job is not run twice in parallel.
anacrontab Syntax and Environment
Every job line has four fields: period in days, delay in minutes, a unique job identifier, and the command. Variable assignments above the jobs set the environment, much like a crontab. This is the stock file on RHEL, Fedora, and other distributions that ship cronie-anacron:
# /etc/anacrontab (RHEL / Fedora, cronie-anacron)
SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# the maximal random delay added to the base delay of the jobs
RANDOM_DELAY=45
# the jobs will be started during the following hours only
START_HOURS_RANGE=3-22
#period in days delay in minutes job-identifier command
1 5 cron.daily nice run-parts /etc/cron.daily
7 25 cron.weekly nice run-parts /etc/cron.weekly
@monthly 45 cron.monthly nice run-parts /etc/cron.monthly
| Field / variable | Meaning | Notes |
|---|---|---|
| period | Run if at least this many days have passed since the timestamp | 1 = daily, 7 = weekly; @monthly = once per calendar month |
| delay | Minutes to wait after anacron starts before launching the job | Spreads load after boot so jobs do not all fire at once |
| job-identifier | Name of the timestamp file and log label | Must be unique; no slashes |
| command | Run through SHELL | Any shell command; run-parts is the usual one |
RANDOM_DELAY | Extra random 0..N minutes added to every delay | cronie anacron; check man 5 anacrontab elsewhere |
START_HOURS_RANGE | Only start jobs within this hour window | Outside the window anacron exits; jobs wait for the next invocation |
MAILTO | Where job output is mailed | Delivered only if a local MTA is installed |
Output means mail β anything a job prints is collected and mailed to MAILTO. On a box without an MTA it goes nowhere, so redirect to a log file or use logger inside the script.
PATH is minimal β the same trap as cron: a command that works in your shell fails because /usr/local/bin is not on PATH. Use absolute paths or set PATH at the top of the file.
Validate before you rely on it β anacron -T parses the anacrontab and exits non-zero on a syntax error, without running anything. Add -t file to check a file other than the default.
Debian/Ubuntu vs RHEL/Fedora: Who Starts anacron
The anacrontab looks similar everywhere, but what triggers anacron differs, and that is where most "why did cron.daily not run" questions come from.
# --- RHEL / Fedora: crond runs cron.hourly, which starts anacron ---
$ cat /etc/cron.d/0hourly
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
01 * * * * root run-parts /etc/cron.hourly
$ ls /etc/cron.hourly/
0anacron
# 0anacron exits early if cron.daily already ran today or the machine
# is on battery, otherwise it runs: /usr/sbin/anacron -s
# --- Debian / Ubuntu with the anacron package: a systemd timer ---
$ systemctl list-timers anacron.timer
$ systemctl cat anacron.service | grep -E 'Exec|Condition'
ConditionACPower=true
ExecStart=/usr/sbin/anacron -d -q $ANACRON_ARGS
# /etc/crontab only runs the periodic dirs itself if anacron is missing
$ grep cron.daily /etc/crontab
25 6 * * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.daily; }
Debian servers often lack it β Ubuntu desktop installs anacron by default; minimal server and cloud images usually do not, in which case cron runs cron.daily at 06:25 and a host that is down then skips it. apt install anacron switches the behaviour.
RHEL needs cronie-anacron β it is installed with cronie on a standard install. If you remove it, cron.daily, cron.weekly and cron.monthly stop running entirely, because nothing else references them on RHEL.
Battery checks β both setups skip runs on battery power: the RHEL script reads /sys/class/power_supply, and the Debian unit has ConditionACPower=true. To allow battery runs on Debian, add a drop-in with an empty ConditionACPower=, which resets the condition list.
Run-parts naming rules β Debian's run-parts silently skips files whose names contain a dot, so /etc/cron.daily/backup.sh never runs. Name it backup. Test with run-parts --test /etc/cron.daily.
Running, Forcing and Debugging Jobs
The command-line options are few, and almost all of them are useful when testing a new job or working out why one has not fired:
| Option | Effect |
|---|---|
-f | Force: run jobs regardless of their timestamps |
-u | Update timestamps to today without running anything |
-s | Serialize: start the next job only after the previous one finishes |
-n | Now: ignore the delay fields (implies -s) |
-d | Stay in the foreground and print messages to stderr as well as syslog |
-q | With -d, suppress the stderr copy of messages |
-t file | Use an alternative anacrontab |
-S dir | Use an alternative spool (timestamp) directory |
-T | Test the anacrontab syntax and exit |
# Run one job right now, in the foreground, and watch it
$ sudo anacron -f -n -d cron.daily
Anacron started on 2026-10-02
Job `cron.daily' started
Job `cron.daily' terminated
Normal exit (1 job run)
# You ran the weekly job manually: tell anacron so it does not repeat it
$ sudo anacron -u cron.weekly
# Make a job due again by rewinding its timestamp
$ echo 20260901 | sudo tee /var/spool/anacron/cron.monthly
# Why did nothing run? Check the log for the last start
$ journalctl -t anacron -n 20 --no-pager # both families
$ grep anacron /var/log/cron # RHEL classic log
Job names are arguments β listing identifiers after the options restricts anacron to those jobs, so anacron -f -n cron.daily does not also fire the weekly and monthly ones.
START_HOURS_RANGE bites at night β if you test at 23:00 on RHEL with the default 3-22 window, anacron can exit without starting anything. Test inside the window, or point -t at a test anacrontab that omits START_HOURS_RANGE.
Do not edit the spool as a habit β rewinding a timestamp is fine for a test. For routine re-runs, use -f or run the script directly.
Adding Your Own Jobs Safely
There are two ways to add work. Dropping an executable script into /etc/cron.daily (or weekly, monthly) gets it run by the existing run-parts line. Adding a line to /etc/anacrontab gives the job its own delay and its own timestamp, so it is tracked independently:
# /etc/anacrontab β a job every 3 days, 20 min after anacron starts
3 20 db-dump /usr/local/sbin/db-dump >> /var/log/db-dump.log 2>&1
# /usr/local/sbin/db-dump
#!/bin/bash
set -euo pipefail
exec 9>/run/lock/db-dump.lock
flock -n 9 || { logger -t db-dump "already running"; exit 0; }
out=/var/backups/db/$(date +%F).sql.gz
if ! pg_dump -U backup appdb | gzip > "$out.tmp"; then
logger -p user.err -t db-dump "dump failed"
rm -f -- "$out.tmp"
exit 1
fi
mv -- "$out.tmp" "$out"
logger -t db-dump "wrote $out"
$ sudo chmod 755 /usr/local/sbin/db-dump
$ sudo anacron -T && echo "anacrontab OK"
Make failure visible β since anacron ignores exit codes, log failures with a priority your monitoring watches, or ping a dead-man's-switch URL on success so silence raises an alert.
Write atomically β dumping to a .tmp file and renaming on success means a half-written backup from a laptop that went to sleep mid-job is never mistaken for a good one.
Guard against slow jobs β a weekly job that takes longer than expected can still be running when the next invocation starts on another schedule. A flock in the script is cheap insurance. The shell script generator can scaffold the boilerplate.
Per-User anacron and When to Use a Timer Instead
The system anacron runs as root. A regular user can run a private copy by pointing it at their own anacrontab and spool directory, then starting it from their crontab. This works the same on both distribution families:
$ mkdir -p ~/.local/etc ~/.local/var/spool/anacron
$ cat ~/.local/etc/anacrontab
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
1 10 sync-notes $HOME/bin/sync-notes
7 15 prune-dl find $HOME/Downloads -type f -mtime +30 -delete
$ anacron -T -t ~/.local/etc/anacrontab
# crontab -e : check hourly, and once at boot
@hourly /usr/sbin/anacron -s -t $HOME/.local/etc/anacrontab -S $HOME/.local/var/spool/anacron
@reboot /usr/sbin/anacron -s -t $HOME/.local/etc/anacrontab -S $HOME/.local/var/spool/anacron
On a host that runs systemd, a timer with Persistent=true gives you the same catch-up behaviour with finer granularity, per-unit logs, and dependency handling. It is usually the better choice for new jobs; anacron remains the simplest option for "once a day, whenever the machine is up" and for the stock periodic directories.
# ~/.config/systemd/user/sync-notes.timer
[Unit]
Description=Sync notes daily, catching up after downtime
[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=10min
[Install]
WantedBy=timers.target
$ systemctl --user enable --now sync-notes.timer
$ systemctl --user list-timers
Persistent=true β systemd stores the last trigger time on disk and, if a run was missed while the machine was off, starts the service once as soon as the timer is activated again.
User timers need lingering for headless use β without loginctl enable-linger username, a user's systemd instance stops when they log out, and their timers stop with it.
Generate the unit β the systemd generator produces matching service and timer files, and the cron generator builds the crontab line if you stay with cron.
Frequently Asked Questions
What is the difference between cron and anacron?
cron runs jobs at exact times and assumes the machine is on; a run missed while it was off is lost. anacron runs jobs at most once per period of days and, if a run was missed, does it the next time it is invoked. anacron has day granularity and is not a daemon, so it is normally started by cron or a systemd timer.
Where does anacron store when a job last ran?
In one timestamp file per job identifier under /var/spool/anacron, for example /var/spool/anacron/cron.daily. Each file holds a date in YYYYMMDD format. A per-user anacron can use a different directory with the -S option.
Why is /etc/cron.daily not running on my Debian server?
On Debian and Ubuntu, /etc/crontab only runs cron.daily itself when anacron is not installed, at 06:25. If the host is down then, the run is skipped. If anacron is installed, check anacron.timer, the ConditionACPower condition, and remember that run-parts ignores script names containing a dot.
Can anacron run jobs more often than once a day?
No. The period field is measured in days, so the shortest interval is one day. For hourly or minute-level schedules use cron or a systemd timer; a timer with Persistent=true also catches up on runs missed while the machine was off.
Can a non-root user use anacron?
Yes. Create a private anacrontab and spool directory, then start anacron from the user's crontab with -t pointing to the file and -S pointing to the spool directory. The system-wide /etc/anacrontab and /var/spool/anacron are reserved for root.