Skip to content

anacron: Scheduled Jobs for Machines That Aren't Always On

How anacron catches up on daily, weekly and monthly jobs a powered-off machine missed: anacrontab syntax, timestamps, Debian vs RHEL wiring, and per-user setups.

●By 10 min read

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)

cronanacronsystemd timer (Persistent=true)
GranularityMinuteDayMicrosecond (calendar or monotonic)
Missed runsLostRun on next invocationRun once at next boot or timer start
Runs asAny user, per crontabroot by default (see per-user section)Any user or system unit
Is a daemonYes (crond / cron)No, started by something elseManaged by PID 1
Overlap protectionNone built inPer-job lock on timestamp fileOne instance of the unit at a time
Best forFixed times on always-on hostsDaily+ jobs on intermittent hostsAnything 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 / variableMeaningNotes
periodRun if at least this many days have passed since the timestamp1 = daily, 7 = weekly; @monthly = once per calendar month
delayMinutes to wait after anacron starts before launching the jobSpreads load after boot so jobs do not all fire at once
job-identifierName of the timestamp file and log labelMust be unique; no slashes
commandRun through SHELLAny shell command; run-parts is the usual one
RANDOM_DELAYExtra random 0..N minutes added to every delaycronie anacron; check man 5 anacrontab elsewhere
START_HOURS_RANGEOnly start jobs within this hour windowOutside the window anacron exits; jobs wait for the next invocation
MAILTOWhere job output is mailedDelivered 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.

sponsored

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:

OptionEffect
-fForce: run jobs regardless of their timestamps
-uUpdate timestamps to today without running anything
-sSerialize: start the next job only after the previous one finishes
-nNow: ignore the delay fields (implies -s)
-dStay in the foreground and print messages to stderr as well as syslog
-qWith -d, suppress the stderr copy of messages
-t fileUse an alternative anacrontab
-S dirUse an alternative spool (timestamp) directory
-TTest 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.

sponsored

Related Tools

See also