Anacron
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.