# Anacron vs Cron: How /etc/anacrontab Runs Daily Jobs

By Dan Ochoa · Published 2026-10-25 · Updated 2026-10-25
Canonical: https://cronradar.com/blog/anacron

> How anacron runs cron.daily, weekly, and monthly jobs on Fedora, RHEL, Debian, and Ubuntu, what /etc/anacrontab controls, and why cron.daily skipped a day.

**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](https://gitlab.com/redhat/centos-stream/rpms/cronie/-/blob/c9s/cronie.spec), [10](https://gitlab.com/redhat/centos-stream/rpms/cronie/-/blob/c10s/cronie.spec)), with the same anacron code apart from the battery switch. Debian and Ubuntu package anacron 2.3 on its own, even alongside cronie ([rules](https://sources.debian.org/src/cronie/1.7.2-4/debian/rules/)): 2.3-43 in Debian 13, 2.3-39 in Ubuntu 24.04, and 2.3-45 in Ubuntu 26.04 ([Debian](https://sources.debian.org/src/anacron/), [Ubuntu](https://launchpad.net/ubuntu/+source/anacron)).

## 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)](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/man/anacron.8), [lock.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/anacron/lock.c#L76-L182), [main.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/anacron/main.c#L379-L398)). Debian's 2.3 code works the same way ([lock.c](https://sources.debian.org/src/anacron/2.3-43/lock.c/#L77-L152)).

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](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/anacron/runjob.c#L384-L392)), 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](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/anacron/runjob.c#L211-L228)).

## The /etc/anacrontab Format

These are the settings and jobs in Fedora's default file, from cronie 1.7.2 ([anacrontab](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/contrib/anacrontab)):

```text
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
```

Debian 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](https://sources.debian.org/src/anacron/2.3-43/debian/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](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/anacron/readtab.c#L234-L245), Debian's [readtab.c](https://sources.debian.org/src/anacron/2.3-43/readtab.c/#L207-L215), and Debian's [changelog](https://sources.debian.org/src/anacron/2.3-50/debian/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](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/anacron/runjob.c#L184), [Debian](https://sources.debian.org/src/anacron/2.3-43/runjob.c/#L125)). `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](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/anacron/readtab.c#L328-L335)).
- **`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](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/anacron/lock.c#L156-L172), [anacrontab(5)](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/man/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](https://github.com/cronie-crond/cronie/pull/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](https://cronradar.com/blog/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](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/contrib/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](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/contrib/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`.

_Figure: With the defaults on a host that is up all night, the 03:01 run of cron.hourly is the first that can put cron.daily inside START_HOURS_RANGE=3-22, so cron.daily starts 5 minutes plus a random 0 to 45 minutes later, between 03:06 and 03:51._

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](https://src.fedoraproject.org/rpms/cronie/blob/rawhide/f/cronie.spec), [dailyjobs](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/contrib/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](https://sources.debian.org/src/cron/3.0pl1-197/debian/crontab.main/)); 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](https://sources.debian.org/src/anacron/2.3-43/debian/anacron.timer/)), and `anacron.service` also starts at boot, running `anacron -d -q -s` ([anacron.service](https://sources.debian.org/src/anacron/2.3-43/debian/anacron.service/), [default](https://sources.debian.org/src/anacron/2.3-43/debian/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](https://sources.debian.org/src/anacron/2.3-43/debian/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](https://sources.debian.org/src/anacron/2.3-43/debian/anacron.0anacron.cron.daily/)).

## Why cron.daily Didn't Run Today

- **Anacron thinks it already ran.** Today's date in `/var/spool/anacron/cron.daily` ends the question, even if that run failed or the date came from `anacron -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 ending `was skipped because of an unmet condition check (ConditionACPower=true)` ([job.c](https://github.com/systemd/systemd/blob/v257/src/core/job.c#L800-L813)), and [README.Debian](https://sources.debian.org/src/anacron/2.3-43/debian/README.debian/) shows the drop-in that removes the check. Cronie's `0anacron` exits when every mains supply in `/sys/class/power_supply` is offline; on Fedora and RHEL 10, `ANACRON_RUN_ON_BATTERY_POWER=yes` in `/etc/default/anacron` overrides it, and RHEL 9's 1.5.7 has no override ([1.5.7](https://github.com/cronie-crond/cronie/blob/cronie-1.5.7/contrib/0anacron)).
- **Nothing started anacron.** On cronie, crond has to be running (`crond.service`; on Arch, `cronie.service`), and `0anacron` listed in `/etc/cron.hourly/jobs.deny` disables it ([anacrontab(5)](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/man/anacrontab.5)). On Debian, `systemctl list-timers anacron.timer` should 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.sh` never runs ([run-parts(8)](https://manpages.debian.org/trixie/debianutils/run-parts.8.en.html)). Fedora's allows dots, but skips names ending in `~`, `.rpmnew`, `.rpmsave`, and similar, and any name listed in that directory's `jobs.deny` ([run-parts](https://github.com/cronie-crond/crontabs/blob/crontabs-20190603/run-parts)).
- **The script exits on purpose.** Debian 13's `/etc/cron.daily/logrotate` exits on systemd hosts, where `logrotate.timer` rotates logs instead ([patch](https://salsa.debian.org/debian/logrotate/-/blob/debian/3.22.0-1/debian/patches/debian/skip-cronjob-when-running-with-systemd.patch)); `systemctl list-timers` shows 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)](https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html)).

These commands answer most of those questions:

```bash
# 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.daily
```

Anacron logs to syslog with facility `cron` ([anacron(8)](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/man/anacron.8)). The lines to look for, with example values:

```text
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](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/anacron/lock.c#L165-L172)). 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](https://cronradar.com/blog/cron-job-not-running) covers paths, environment, and permissions.

## Running Anacron by Hand

```bash
# 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.daily
```

The 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`:

```ini
# /etc/systemd/system/anacron.timer.d/override.conf
[Timer]
OnCalendar=
OnCalendar=*-*-* 01..05:30
```

The empty `OnCalendar=` clears the packaged schedule before the new one is added ([systemd.timer(5)](https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html)). 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](https://cronradar.com/blog/systemd-timers)); [systemd timers vs cron](https://cronradar.com/blog/systemd-timers-vs-cron) covers moving a job over. [The crontab guide](https://cronradar.com/blog/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:

```text
1	15	daily-backup	/usr/local/bin/daily-backup-monitored.sh
```

- **Earliest start: 03:00.** `START_HOURS_RANGE=3-22` keeps 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 `-s` makes jobs wait their turn.
- **So `schedule=0 3 * * *` with `gracePeriod=7200`** puts the deadline at 05:00, which covers a cron.daily run of up to about an hour. The monitor shows `warning` each morning from 03:00 until the start arrives; only `critical`, 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](https://docs.cronradar.com/schedules)).

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:

```bash
#!/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.
