🕰️ at & anacron

Cron & Scheduling Series: Part 1 — Cron Deep Dive  |  Part 2 — Systemd Timers  |  Part 3 — at & anacron

Two Schedulers for Special Needs

at schedules a one-time job to run at a specific future time — perfect for “run this once tonight” without editing a crontab. anacron solves a different problem — it guarantees that periodic jobs run even if the system was powered off when they were scheduled. Where cron silently misses jobs on a shutdown system, anacron catches up the moment the system comes back up.

Both tools fill gaps that cron and systemd timers don’t cover as elegantly. Every sysadmin’s toolkit should include all four.

at vs anacron — The Right Tool

⏱️ at — One-Time Jobs

Use when: You need to run something once at a specific future time.

  • Run a maintenance script at 2 AM tonight
  • Send a report in 30 minutes
  • Restart a service after a deploy completes
  • Schedule a reminder command

Key point: Job runs once and is gone.

🔄 anacron — Catch-Up Scheduler

Use when: Periodic jobs must not be missed on systems that are not always on.

  • Daily/weekly/monthly jobs on workstations
  • Laptops that sleep or power off
  • Servers with maintenance windows
  • Guarantee cron.daily runs after downtime

Key point: No exact time — runs N days after last run.

Part 1 — The at Command

1
Basic at Usage — Schedule a One-Time Job
# Install at if not present
sudo dnf install -y at        # RHEL/Rocky
sudo apt install -y at        # Debian/Ubuntu
sudo systemctl enable --now atd

# Schedule a command at a specific time
echo "/usr/local/bin/maintenance.sh" | at 02:00
echo "systemctl restart nginx" | at 2:30 AM
echo "/usr/bin/backup.sh" | at midnight
echo "/usr/bin/backup.sh" | at noon
echo "/usr/bin/report.sh" | at 9am tomorrow
echo "/usr/bin/cleanup.sh" | at now + 2 hours
echo "/usr/bin/deploy.sh"  | at now + 30 minutes
echo "/usr/bin/task.sh"    | at 3pm next friday
echo "/usr/bin/task.sh"    | at 10:00 AM Jul 4
Output after scheduling:
warning: commands will be executed using /bin/sh job 12 at Tue Apr 26 02:00:00 2026
💡 at is interactive too. Run at 2am with no command to get a prompt where you type multiple commands. Press Ctrl-D when done.
2
Managing at Jobs — atq, atrm, at -c
# List pending at jobs
atq
at -l       # same as atq
atq output:
12 Tue Apr 26 02:00:00 2026 a root 13 Tue Apr 26 09:00:00 2026 a craig 14 Wed Apr 27 14:30:00 2026 a root
# View the contents of a job
at -c 12

# Remove a pending job
atrm 12
atrm 12 13 14   # remove multiple

# Schedule from a file
at -f /usr/local/bin/maintenance.sh 2:00 AM tomorrow

# batch -- runs when system load drops below 1.5
echo "/usr/local/bin/heavy-report.sh" | batch
at captures your environment. Unlike cron, at saves your current PATH and environment variables when you schedule the job and replays them at run time. This is why at jobs are less prone to PATH-not-found failures than cron jobs.
3
Practical at Use Cases
# Restart a service after a config change in 5 minutes
echo "systemctl restart nginx" | at now + 5 minutes

# One-time database backup before a risky operation
echo "pg_dump mydb > /backup/pre-migration-$(date +%Y%m%d).sql" | at now + 1 minute

# Broadcast a reminder to all logged-in users
echo "echo 'Maintenance starting in 10 minutes!' | wall" | at 9:50 PM

# Access control
cat /etc/at.allow    # whitelist -- only these users can use at
cat /etc/at.deny     # blacklist -- everyone except listed users

# Check atd service
sudo systemctl status atd
batch vs at: batch queues a one-time job just like at, but holds it until the system load average drops below 1.5. Perfect for CPU-intensive jobs that should not impact normal operation — let the system decide when it has headroom to run them.

Part 2 — anacron

4
How anacron Works

Anacron maintains a timestamp for each job. When it runs it checks how many days since each job last executed. If the interval has passed it runs the job regardless of whether the system was on when it was due:

# Install anacron
sudo dnf install -y cronie-anacron    # RHEL/Rocky
sudo apt install -y anacron           # Debian/Ubuntu

# View the anacron configuration
cat /etc/anacrontab
/etc/anacrontab format:
# period delay job-id command # (days) (min) 1 5 cron.daily run-parts /etc/cron.daily 7 10 cron.weekly run-parts /etc/cron.weekly @monthly 15 cron.monthly run-parts /etc/cron.monthly
Reading /etc/anacrontab fields:
5
anacron Timestamps and Manual Control
# View timestamp files -- one per job-id
ls -la /var/spool/anacron/
cat /var/spool/anacron/cron.daily
Timestamp file -- just a date:
20260424
# Force all jobs to run now (ignore timestamps)
sudo anacron -f

# Test mode -- show what would run, don't actually run
sudo anacron -n -T

# Run with debug output
sudo anacron -d

# Reset a job's timestamp to force it to run next time
echo 19700101 | sudo tee /var/spool/anacron/cron.weekly
💡 Reset timestamp to force a run. Setting any timestamp to 19700101 (epoch) guarantees the interval has passed — anacron will run that job on its next wakeup. Useful for testing or recovering from a skipped job.
6
Add Custom Jobs to anacron
# Add custom jobs to /etc/anacrontab
sudo tee -a /etc/anacrontab << 'EOF'

# Run weekly DB backup -- every 7 days, 20 min delay
7       20      db-weekly-backup    /usr/local/bin/db-backup.sh

# Monthly log archive -- every 30 days, 25 min delay
30      25      log-monthly-archive /usr/local/bin/archive-logs.sh
EOF

# Verify syntax
sudo anacron -T && echo "Syntax OK"
⚠️ anacron requires root. Unlike cron, anacron only supports system-level jobs running as root. For user-level catch-up scheduling use systemd timers with Persistent=true.
7
anacron on RHEL — Integration with cronie

On RHEL/Rocky, anacron is triggered automatically via cron:

# On RHEL, /etc/cron.hourly/0anacron triggers anacron
cat /etc/cron.hourly/0anacron
0anacron script:
#!/bin/sh # Check whether 0anacron was run today already if test -r /var/spool/anacron/cron.daily; then day=$(cat /var/spool/anacron/cron.daily) if [ $(date +%Y%m%d) = "$day" ]; then exit 0; fi fi /usr/sbin/anacron -s
On Debian/Ubuntu, anacron is triggered differently — check /etc/cron.d/anacron or the systemd anacron.timer. The behavior is the same regardless of how it is triggered.
8
Choosing the Right Scheduler
NeedBest ToolWhy
Run once at 2 AM tonightatOne-time future execution
Run every weekday at 8 AMcronRecurring fixed-time schedule
Run daily but system may be offanacron or systemd timer (Persistent=true)Catch-up on missed runs
Run 5 minutes after every bootsystemd timer (OnBootSec=5min)Boot-relative monotonic timer
Run every 15 minutescron (*/15) or systemd timer (*:0/15)Both work equally well
Run when system load is lowbatchLoad-based one-time execution
System-level with full loggingsystemd timerjournald integration, dependencies
Simple one-liner jobcronLowest setup overhead

Quick Reference

at Commands

CommandWhat it does
echo "cmd" | at TIMESchedule one-time job
at -f script.sh TIMESchedule script file
atqList pending jobs
at -c JOBNUMView job contents
atrm JOBNUMRemove a pending job
echo "cmd" | batchRun when load is low
systemctl status atdCheck at daemon

at Time Expressions

ExpressionMeaning
now + 30 minutes30 minutes from now
now + 2 hours2 hours from now
2:30 AMNext 2:30 AM
midnightTonight at midnight
noonToday at noon
9am tomorrowTomorrow morning
3pm next fridayNext Friday afternoon
10:00 AM Jul 4Specific date and time

anacron Commands

CommandWhat it does
cat /etc/anacrontabView anacron job configuration
ls /var/spool/anacron/View last-run timestamps
sudo anacron -fForce run all jobs now
sudo anacron -TTest/validate anacrontab syntax
sudo anacron -dDebug mode -- show what would run
echo 19700101 | sudo tee /var/spool/anacron/JOBIDReset timestamp to force next run