Anacron vs Cron: How /etc/anacrontab Runs Daily Jobs
Contents
Cron starts a job at an exact minute and skips it if the machine is off at that minute; anacron runs each job once every so many days, whenever the machine is next up, and it is what runs /etc/cron.daily, cron.weekly, and cron.monthly on Fedora and RHEL, and on Debian and Ubuntu when the anacron package is installed. Anacron isn't a daemon. Cron or a systemd timer starts it, usually once an hour; it compares each job's last-run date with today, runs whatever is due after a delay, and exits. That is why daily scripts run at about 03:06 to 03:51 on Fedora and RHEL, about 07:35 on Debian and Ubuntu, and later on a machine that was off.
Checked October 25, 2026, against two implementations. Cronie 1.7.2, which includes anacron, is what Fedora and Arch ship; RHEL 9 has cronie 1.5.7 and RHEL 10 has 1.7.0 (CentOS Stream 9, 10), with the same anacron code apart from the battery switch. Debian and Ubuntu package anacron 2.3 on its own, even alongside cronie (rules): 2.3-43 in Debian 13, 2.3-39 in Ubuntu 24.04, and 2.3-45 in Ubuntu 26.04 (Debian, Ubuntu).
How Anacron Decides a Job Is Due#
Anacron keeps one timestamp file per job in /var/spool/anacron, named after the job and holding a single date such as 20261025. When it starts, it subtracts that date from today's. If the difference is at least the job's period in days, the job is due: anacron waits the job's delay, counted in minutes from when anacron itself started, runs the command with /bin/sh -c, and writes the date when the command exits (anacron(8), lock.c, main.c). Debian's 2.3 code works the same way (lock.c).
What follows from that:
- Daily means once per calendar date, not every 24 hours. Only the date is stored, so a job that ran at 23:40 is due again just after midnight if anacron starts then. A stored date in the future, after the clock was set back, also makes it due.
- A failed run counts as a run. The date is written whatever the exit status (runjob.c), so a script that failed waits for its next period.
- Output is mailed, or lost. Anything a job prints goes to
MAILTO, or to the user running anacron, through/usr/sbin/sendmail. Without it, anacron logs that it can't find sendmail and discards the output (runjob.c).
The /etc/anacrontab Format#
These are the settings and jobs in Fedora's default file, from cronie 1.7.2 (anacrontab):
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.monthlyDebian 13's default has the same three jobs with delays of 5, 10, and 15 minutes, adds @yearly 20 cron.yearly, sets HOME and LOGNAME, and has neither scheduling variable (anacrontab). Ubuntu's leaves out the PATH line, and 24.04's has no yearly job.
| Field | Meaning |
|---|---|
| Period | Days between runs, such as 1 or 7, or a name such as @monthly |
| Delay | Minutes after anacron starts before this job starts; 0 for none |
| Job identifier | Any non-blank characters except /; names the timestamp file and appears in the logs |
| Command | The rest of the line, run with /bin/sh -c |
Which period names work depends on the version:
| anacron | Period names accepted |
|---|---|
| cronie 1.7.2, 1.7.0, 1.5.7 (Fedora, Arch, RHEL) | @daily, @weekly, @monthly; also @yearly and @annually, undocumented |
| Debian 2.3-43 (Debian 13), 2.3-39 (Ubuntu 24.04) | @monthly, @yearly |
| Debian 2.3-45 and later (Ubuntu 26.04) | Adds @daily and @weekly |
That comes from cronie's readtab.c, Debian's readtab.c, and Debian's changelog for 2.3-44. @monthly runs once a month whatever its length; write daily and weekly periods as 1 and 7, which every version reads the same way.
Environment Lines and the Two Scheduling Variables#
A NAME=value line sets an environment variable for every job below it. SHELL is only passed along: both implementations run commands with /bin/sh -c whatever it says (cronie, Debian). MAILTO picks the mail recipient; on cronie, an empty one turns mail off. Cronie's anacron also reads two lines as settings:
RANDOM_DELAY=45: each time anacron starts, it picks one random number from 0 to 45 and adds it to the delay of every job listed after that line (readtab.c).START_HOURS_RANGE=3-22: a due job is skipped unless its start time, anacron's start plus the delay, falls between 03:00 and 21:59; a missed window means no run that day (lock.c, anacrontab(5)).
Debian's anacron 2.3 has neither. It passes both lines to jobs as ordinary environment variables, so a START_HOURS_RANGE copied from a Fedora guide does nothing, and anacron -T won't flag it.
One gap in cronie 1.7.2: the hours check runs only when the job's timestamp file already holds a date. A new job, or a fresh install with empty timestamp files, runs at the next hourly start at any hour. The fix was merged in October 2026 and isn't in a release yet (#248).
When /etc/cron.daily Runs#
Times are the host's local time. Without anacron, cron runs the directories only if the machine is up at that minute, and a missed day is gone; missed runs after downtime compares what other schedulers do.
| System | cron.daily, host up all night | Host that was off |
|---|---|---|
| Fedora, RHEL, Arch | 03:06 to 03:51 | First :01 after boot plus 5 to 50 minutes, if before 22:00 |
Fedora, RHEL with cronie-noanacron |
04:02 | Skipped that day |
| Debian 13, Ubuntu 24.04 and 26.04 with anacron | 07:35 to 07:40 | 5 minutes after boot, at any hour |
| Debian, Ubuntu without anacron | 06:25 | Skipped that day |
Fedora, RHEL, and Arch#
Crond runs run-parts /etc/cron.hourly at one minute past every hour (0hourly). The first script there, 0anacron, exits at once if /var/spool/anacron/cron.daily already holds today's date or the machine is on battery, and otherwise starts anacron -s (0anacron). With the defaults, the 03:01 start is the first that can put cron.daily inside 3-22, hence 03:06 to 03:51. When the weekly and monthly jobs are due, they follow in the same run, one at a time because of -s.
Because of that early exit, anacron isn't started again once cron.daily has run that day, so a job you add to /etc/anacrontab later in the day is first considered the next morning.
Fedora's cronie requires a package that provides dailyjobs: cronie-anacron, or cronie-noanacron, which runs the directories from fixed cron lines at 04:02 daily, 04:22 on Sundays, and 04:42 on the 1st (cronie.spec, dailyjobs).
Debian and Ubuntu#
/etc/crontab still runs the directories at 06:25 daily, 06:47 on Sundays, and 06:52 on the 1st, but each line begins with test -x /usr/sbin/anacron || and does nothing once anacron is installed (crontab); Ubuntu 24.04's is identical. On systemd hosts, anacron.timer fires on the half hour from 07:30 to 23:30, up to five minutes late at random, with Persistent=true (anacron.timer), and anacron.service also starts at boot, running anacron -d -q -s (anacron.service, default). With no hours range, a host booted at 02:00 runs cron.daily at 02:05. Without systemd, /etc/cron.d/anacron starts anacron at the same :30 times (cron.d).
Debian's /etc/cron.daily/0anacron runs anacron -u cron.daily, so running the directory by hand with run-parts also updates the timestamp (0anacron).
Why cron.daily Didn't Run Today#
- Anacron thinks it already ran. Today's date in
/var/spool/anacron/cron.dailyends the question, even if that run failed or the date came fromanacron -u. - The machine wasn't up in the window. With cronie's defaults, a host that comes up after about 21:00 waits until after 03:00 tomorrow, and a host that is only on between 22:00 and 03:00 never runs it.
- It's on battery. Debian's service has
ConditionACPower=true; a skipped start logs a line endingwas skipped because of an unmet condition check (ConditionACPower=true)(job.c), and README.Debian shows the drop-in that removes the check. Cronie's0anacronexits when every mains supply in/sys/class/power_supplyis offline; on Fedora and RHEL 10,ANACRON_RUN_ON_BATTERY_POWER=yesin/etc/default/anacronoverrides it, and RHEL 9's 1.5.7 has no override (1.5.7). - Nothing started anacron. On cronie, crond has to be running (
crond.service; on Arch,cronie.service), and0anacronlisted in/etc/cron.hourly/jobs.denydisables it (anacrontab(5)). On Debian,systemctl list-timers anacron.timershould show a next run. - Run-parts skipped the script. Both run-parts versions skip files that aren't executable. Debian's runs only names made of letters, digits, underscores, and hyphens, so
backup.shnever runs (run-parts(8)). Fedora's allows dots, but skips names ending in~,.rpmnew,.rpmsave, and similar, and any name listed in that directory'sjobs.deny(run-parts). - The script exits on purpose. Debian 13's
/etc/cron.daily/logrotateexits on systemd hosts, wherelogrotate.timerrotates logs instead (patch);systemctl list-timersshows work that moved to timers. - Yesterday's run is still going. Anacron locks a job while it runs, and cronie's next start logs
locked by another anacron - skipping. On Debian the service is still active, and a timer doesn't start a unit that is already running (systemd.timer(5)).
These commands answer most of those questions:
# Last-run date per job (YYYYMMDD), and when each file was last written
ls -l --time-style=long-iso /var/spool/anacron
sudo head /var/spool/anacron/*
# Anacron's log lines, on either family
sudo journalctl -t anacron --since yesterday
# Debian and Ubuntu: the timer's next run, and starts skipped on battery
systemctl list-timers anacron.timer
sudo journalctl -u anacron.service --since yesterday
# What run-parts would run, without running it
run-parts --test /etc/cron.dailyAnacron logs to syslog with facility cron (anacron(8)). The lines to look for, with example values:
Anacron started on 2026-10-25
Will run job `cron.daily' in 17 min.
Job `cron.daily' started
Job `cron.daily' terminated (exit status: 1)
Job `cron.daily' locked by another anacron - skipping
Normal exit (0 jobs run)Debian's first line reads Anacron 2.3 started on, and the minutes in Will run job include any random delay. Normal exit (0 jobs run) means nothing was due or, on cronie, that a due job's start time fell outside START_HOURS_RANGE, a skip anacron doesn't log separately (lock.c). No anacron lines at all after the daily run is normal on cronie, since 0anacron stops starting it. If the daily job ran, the problem is in a script; cron job not running covers paths, environment, and permissions.
Running Anacron by Hand#
# Check /etc/anacrontab; exit status 0 means no errors
sudo anacron -T
# Run cron.daily now, ignoring its timestamp and delay, in the foreground
sudo anacron -d -f -n cron.daily
# Record cron.daily as done today without running it
sudo anacron -u cron.dailyThe options are the same in both: -f ignores timestamps, -n ignores delays and implies -s, -s runs jobs one at a time, -d stays in the foreground and prints anacron's messages, -q hides them, -u updates timestamps without running anything, and -T checks the file, printing a line such as anacron: Invalid syntax in /etc/anacrontab on line 14 - skipping this line. Arguments are job identifiers or quoted shell patterns such as 'cron.*'. -t and -S pick another anacrontab and spool directory, which is how an ordinary user runs a personal anacron. On cronie, -f and -n also bypass START_HOURS_RANGE. Job output is still mailed rather than printed, so run a script directly to watch it.
Changing When Daily Jobs Run#
On cronie, edit the variables. START_HOURS_RANGE=1-5 with RANDOM_DELAY=0 runs cron.daily at 01:06 on a host that is up, or at the first start after boot before 05:00.
On Debian and Ubuntu, override the timer with sudo systemctl edit anacron.timer:
# /etc/systemd/system/anacron.timer.d/override.conf
[Timer]
OnCalendar=
OnCalendar=*-*-* 01..05:30The empty OnCalendar= clears the packaged schedule before the new one is added (systemd.timer(5)). The start at boot remains, because multi-user.target starts anacron.service directly.
Anacron vs Cron vs systemd Timers#
| Behavior | cron | anacron | systemd timer with Persistent=true |
|---|---|---|---|
| Shortest interval | One minute | One day | Seconds |
| Start time | The scheduled minute | A window: START_HOURS_RANGE (cronie), the timer (Debian) |
The OnCalendar= time, plus any random delay |
| Run missed while off | Skipped | Once, at the next anacron start | Once, when the timer next starts |
| What it records | Nothing | The date of each job's last run | The time of the last trigger |
| Where results go | Syslog, and mail when there is output | Syslog, and mail when there is output | The journal, with each run's exit status |
Anacron suits work that should happen once a day or less on machines that aren't always on, when the exact hour doesn't matter. A timer can do the same catch-up at a time you choose, with logs in the journal (systemd timers); systemd timers vs cron covers moving a job over. The crontab guide has a script that lists every schedule on a host, anacron's included.
Monitoring an Anacron Job#
Anacron logs a run and mails its output, but nothing reports a day it didn't run. A check-in monitor does: the job reports to CronRadar when it starts and finishes, and CronRadar alerts when an expected report doesn't arrive. There is no CronRadar SDK for anacron and no auto-discovery, so each job reports with its own HTTP check-in.
CronRadar has no "once a day, any time" setting: a monitor expects each run at a five-field cron time, evaluated in UTC, plus a grace period, and every check-in moves the expectation to the next scheduled time. Set the schedule to the earliest time the job can start and make the grace period reach past the latest. A run earlier than the schedule breaks this, because its check-in only moves the expectation to that same day's time, which then passes with nothing arriving.
Take a job added to cronie's default anacrontab with a 15-minute delay, on a server that stays up and keeps its clock in UTC:
1 15 daily-backup /usr/local/bin/daily-backup-monitored.sh- Earliest start: 03:00.
START_HOURS_RANGE=3-22keeps any job from starting earlier. - Latest start: about 04:01, the 03:01 run plus 15 minutes plus the full 45-minute random delay, later if cron.daily is still running, because
-smakes jobs wait their turn. - So
schedule=0 3 * * *withgracePeriod=7200puts the deadline at 05:00, which covers a cron.daily run of up to about an hour. The monitor showswarningeach morning from 03:00 until the start arrives; onlycritical, past the grace period, opens an incident.
On a laptop or a VM that is shut down overnight, the job can start any time until about 22:00, so the same schedule needs gracePeriod=72000 (20 hours), and a day the machine stays off alerts at 23:00. On Debian and Ubuntu, the start at boot can come at any hour, so use local midnight as the schedule with a grace period under 24 hours, or move a job that needs a predictable time to a cron line or a timer. If the host's clock isn't on UTC, convert, and with daylight saving time use the earlier UTC time and an extra hour of grace (schedule syntax).
The wrapper reports the start, runs the job, and reports complete or fail with the job's own exit code. Each report gives up after five seconds and ignores errors:
#!/bin/sh
# /usr/local/bin/daily-backup-monitored.sh, started by anacron as daily-backup.
# /etc/anacrontab is world-readable, so the key lives in a root-only file.
CRONRADAR_API_KEY=$(cat /etc/cronradar/api-key 2>/dev/null)
url="https://cron.life/api/v1/ping/daily-backup"
run_id="daily-backup-$(date +%s)-$$"
report() {
event=$1
shift
curl --fail --silent --show-error --max-time 5 --output /dev/null \
--request POST --get --user "$CRONRADAR_API_KEY:" \
--data-urlencode "schedule=0 3 * * *" \
--data-urlencode "gracePeriod=7200" \
--data-urlencode "run_id=$run_id" \
"$@" "$url/$event" || true
}
report start
/usr/local/bin/daily-backup.sh
rc=$?
if [ "$rc" -eq 0 ]; then
report complete
else
report fail --data-urlencode "message=daily-backup.sh exited with code $rc"
fi
exit "$rc"After adding the line, run sudo anacron -u daily-backup once. Cronie 1.7.2 skips the hours check for a job with an empty timestamp file, so the first run could land just after midnight, and its check-in would move the expectation only to 03:00 that day. With today's date recorded, the first run waits for the next morning's window.
The first check-in creates the monitor. schedule and gracePeriod apply only then; change them later in the monitor's settings. A fail alerts immediately. A start with no finish shows as stuck only once the next day's deadline passes, so give the backup its own time limit, for example with timeout, to turn a hang into a failure.
Frequently Asked Questions#
What is the difference between cron and anacron?#
Cron is a daemon that starts each job at the minute its schedule names, and only if the machine is up then. Anacron is a program that cron or a systemd timer starts; it runs any job whose period in days has passed since the date in its timestamp file, then exits. Cron handles exact times and intervals down to a minute; anacron handles daily, weekly, and monthly work on machines that aren't always on.
When does /etc/cron.daily run?#
On a host that stays up, at about 03:06 to 03:51 local time on Fedora, RHEL, and Arch, and 07:35 to 07:40 on Debian and Ubuntu with anacron. After downtime it runs soon after boot, inside cronie's 03:00 to 22:00 window or at any hour on Debian. Without anacron, cron runs it at a fixed time and skips it if the machine is off.
Why didn't cron.daily run today?#
Check /var/spool/anacron/cron.daily first: today's date there means anacron counts the day as done, even after a failed run. Otherwise, look for a machine that was off or on battery during the window, a stopped crond or disabled anacron.timer, or a script run-parts skips for its name or a missing execute bit.
Can anacron run jobs more than once a day?#
No. Periods are whole days and the timestamp file holds only a date, so the most often a job can run is once per calendar date. For hourly or more frequent work, use a cron line or a systemd timer.
How do I force anacron to run now?#
Run sudo anacron -d -f -n cron.daily to run that job at once, in the foreground, ignoring its timestamp and delay; leave out the job name to run every job. On Debian and Ubuntu, sudo systemctl start anacron.service runs only the jobs that are due, with their delays, and does nothing on battery.
Put this guide to work
Know when the next run is missing
Connect one scheduled job, confirm a real completion, then verify the alert destination. Keep your existing scheduler in charge of running the work.