Anacron

Posted on 3 2026

Cron is designed for servers: machines that run continuously and can be relied upon to be awake at 3am when the daily backup is scheduled. A desktop is not a server. It gets switched off, put to sleep, closed, and generally ignored for stretches of time. A cron job scheduled for midnight on a machine that is off at midnight simply does not run. There is no catch-up.

Anacron solves this. Rather than scheduling tasks at a specific time, anacron schedules them at a minimum frequency: once per day, once per week, once per month. Every time anacron runs, it checks when each job was last executed. If enough time has passed and the job has not run, it runs it. If the machine was off for three days, the daily job runs shortly after the next boot, not at the scheduled time that was missed.

On Kubuntu 24.04, the system-level anacron handles /etc/cron.daily, /etc/cron.weekly, and /etc/cron.monthly via a systemd timer. This page covers setting up a per-user anacron instance for jobs that belong to your user account rather than the system, which is the right approach for backup scripts, key renewal reminders, and other personal automation tasks.

Why per-user anacron

The system-level anacron runs as root. Tasks like rotating system logs or updating package databases belong there. Tasks like backing up your home directory, checking certificate expiry, or running personal maintenance scripts do not. A per-user anacron instance gives you the same resilient scheduling model for user-owned tasks, without requiring root privileges and without mixing personal jobs into system configuration.

Installation

Anacron is included with the cron package on Ubuntu:

sudo apt install anacron

Verify it is available:

which anacron

Directory structure

Create the per-user anacron directory structure in your home directory:

mkdir -p ~/.anacron/cron.{daily,weekly,monthly,fortnightly} ~/.anacron/spool

The spool directory is where anacron records when each job was last run. The cron subdirectories are where job scripts live, following the same convention as the system-level /etc/cron.* directories.

Anacrontab

Create and edit the file ~/.anacron/anacrontab. Shell variables do not expand in this file, so replace username and home directory with your actual literal values.

# ~/.anacron/anacrontab
# See anacron(8) and anacrontab(5) for details.

# Mail job output to this address (requires postfix null client from previous section)
MAILTO=you@yourdomain.net

SHELL=/bin/bash
LOGNAME=yourusername
PATH=/home/yourusername/bin:/home/yourusername/.local/bin:/usr/local/bin:/bin:/usr/bin

# Avoid running jobs outside reasonable hours
START_HOURS_RANGE=8-22

# Add a random delay to avoid all jobs starting simultaneously
RANDOM_DELAY=10

# period    delay    job-id          command
1           5        daily-cron      nice run-parts --report /home/yourusername/.anacron/cron.daily
7           10       weekly-cron     nice run-parts --report /home/yourusername/.anacron/cron.weekly
14          15       fortnightly     nice run-parts --report /home/yourusername/.anacron/cron.fortnightly
@monthly    20       monthly-cron    nice run-parts --report /home/yourusername/.anacron/cron.monthly

The four columns are:

  • Period: how often the job should run, in days, or @monthly
  • Delay: how many minutes after anacron starts to wait before running this job
  • Job ID: a unique name used in logs and the spool directory
  • Command: what to run

The nice prefix runs jobs at reduced priority so they do not make the desktop unresponsive. run-parts --report runs every executable file in the directory and prints the name of each script that produces output, which is what ends up in the mail sent to MAILTO.

Replace yourusername and the home directory path with your actual values throughout.

Running anacron

On login

Add anacron to ~/.profile so it runs whenever you log in to a desktop session. This ensures jobs run at least once per session if they are overdue:

# Add to ~/.profile
# Run per-user anacron on login
/usr/sbin/anacron -t "${HOME}/.anacron/anacrontab" -S "${HOME}/.anacron/spool"

Hourly via cron

For jobs that should catch up more promptly, add an hourly cron entry that runs anacron every hour. Open the user crontab:

crontab -e

Add:

# Run per-user anacron every hour to check for overdue jobs
@hourly /usr/sbin/anacron -t "${HOME}/.anacron/anacrontab" -S "${HOME}/.anacron/spool"

The combination of both approaches means: anacron checks on login and once per hour. If the machine is on and a job is overdue, it will run within the hour. If the machine is off, it runs shortly after the next login.

Via systemd user timer (modern alternative)

On Kubuntu 24.04 with systemd, a user timer unit is a cleaner approach than the hourly cron entry. Create ~/.config/systemd/user/anacron.service:

[Unit]
Description=Per-user anacron
Documentation=man:anacron(8)

[Service]
Type=oneshot
ExecStart=/usr/sbin/anacron -t %h/.anacron/anacrontab -S %h/.anacron/spool

Create ~/.config/systemd/user/anacron.timer:

[Unit]
Description=Run per-user anacron hourly
Documentation=man:anacron(8)

[Timer]
OnBootSec=5min
OnUnitActiveSec=1h
Persistent=true

[Install]
WantedBy=timers.target

Enable and start the timer:

systemctl --user enable --now anacron.timer

The Persistent=true setting means the timer fires immediately on boot if it missed its last scheduled run, which is exactly the anacron behaviour you want. This approach is slightly cleaner than the crontab entry and integrates better with journald logging.

Adding jobs

Jobs are executable scripts placed in the appropriate directory. Scripts must be executable and must not have a file extension (run-parts ignores files with extensions by default).

Example: certificate expiry check

A weekly script that checks for certificates expiring within 30 days:

cat > ~/.anacron/cron.weekly/check-cert-expiry << 'EOF'
#!/usr/bin/env bash
# Check certificates for upcoming expiry

CERT_DIR="${HOME}/.ssl/certs"
WARN_DAYS=30

for cert in "${CERT_DIR}"/*.cert.pem; do
    [ -f "$cert" ] || continue
    expiry=$(openssl x509 -enddate -noout -in "$cert" | cut -d= -f2)
    expiry_epoch=$(date -d "$expiry" +%s)
    now_epoch=$(date +%s)
    days_remaining=$(( (expiry_epoch - now_epoch) / 86400 ))

    if [ "$days_remaining" -lt "$WARN_DAYS" ]; then
        echo "WARNING: $(basename "$cert") expires in ${days_remaining} days (${expiry})"
    fi
done
EOF

chmod 755 ~/.anacron/cron.weekly/check-cert-expiry

Example: GPG key expiry check

A monthly script that warns when GPG keys are approaching expiry:

cat > ~/.anacron/cron.monthly/check-gpg-expiry << 'EOF'
#!/usr/bin/env bash
# Check GPG keys for upcoming expiry

WARN_DAYS=60

gpg --list-keys --with-colons 2>/dev/null | \
    awk -F: '/^pub|^sub/ && $7 != "" {
        expiry = $7
        cmd = "date -d @" expiry " +%s"
        cmd | getline expiry_epoch
        close(cmd)
        now = systime()
        days = int((expiry_epoch - now) / 86400)
        if (days < '"$WARN_DAYS"' && days > 0) {
            print "WARNING: GPG key " $5 " expires in " days " days"
        }
        if (days <= 0) {
            print "EXPIRED: GPG key " $5 " expired " -days " days ago"
        }
    }'
EOF

chmod 755 ~/.anacron/cron.monthly/check-gpg-expiry

These are examples. The backup script that actually matters for this series is added when the backup section is set up, and goes in ~/.anacron/cron.daily/.

Testing

Run anacron manually with the -f flag to force all jobs regardless of when they last ran, and -d to run in the foreground with verbose output:

/usr/sbin/anacron -f -d \
    -t ~/.anacron/anacrontab \
    -S ~/.anacron/spool

This is useful for verifying that jobs run correctly before relying on the automatic schedule.

Check when jobs were last run:

ls -la ~/.anacron/spool/

The timestamps on the spool files show when each job category last ran.

Check the system journal for anacron output:

journalctl --user -u anacron.service

Or if using the crontab approach:

journalctl -t cron --since today

A note on the MAILTO setting

The MAILTO line in the anacrontab sends job output to the specified address. This only works if the Postfix null client from the previous section is configured and working. Without it, job output goes nowhere and you will not know if something failed.

Test by adding a simple job that always produces output:

cat > ~/.anacron/cron.daily/test-mail << 'EOF'
#!/usr/bin/env bash
echo "Daily anacron test from $(hostname -f) at $(date)"
EOF

chmod 755 ~/.anacron/cron.daily/test-mail

Force it to run and verify the mail arrives. Remove it once confirmed.

Anacron’s minimum scheduling granularity is one day. For tasks that need to run more frequently than daily, use a systemd user timer or a regular crontab entry instead. Anacron is the right tool for daily, weekly, and monthly jobs on a machine that is not always on, not for sub-daily scheduling.