Cron is for recurring jobs. When you need something to run once later (restart a service at 2 a.m., revert a firewall change in 10 minutes if you get locked out, send a reminder), use at. It queues the command, and runs it at the given time, even after you log out.

Install and enable

sudo apt install at          # Debian/Ubuntu
sudo dnf install at          # RHEL/AlmaLinux
sudo systemctl enable --now atd

Schedule a job

Type commands at the at> prompt, then press Ctrl+D:

at 02:00
at> systemctl restart php8.3-fpm
at> <Ctrl+D>
job 3 at Thu Nov  6 02:00:00 2026

Or pipe the command in (better for scripts):

echo "systemctl restart php8.3-fpm" | sudo at 02:00
echo "/usr/local/bin/report.sh" | at now + 2 hours
at -f /usr/local/bin/cleanup.sh 23:30

Time formats

Expression Meaning
now + 10 minutes 10 minutes from now
now + 2 hours, now + 3 days Relative times
14:30, 2:30 PM Today (or tomorrow if already past)
midnight, noon, teatime 00:00, 12:00, 16:00
02:00 tomorrow Tomorrow at 2 a.m.
10:00 2026-12-24 Specific date
next monday Next Monday, at the current time

Times use the server’s time zone. Check with timedatectl. See timedatectl and time zones.

List, inspect and remove jobs

atq                  # list pending jobs (same as at -l)
at -c 3              # show the full script of job 3 (including environment)
atrm 3               # remove job 3

Root sees all users’ jobs. Normal users see their own.

The lockout safety net

A classic sysadmin trick before risky remote changes (firewall rules, SSH config, network settings): schedule an automatic undo, make the change, and cancel the undo only if you can still connect:

echo "ufw disable" | sudo at now + 10 minutes
sudo ufw enable
# open a NEW SSH session to confirm access, then:
sudo atq
sudo atrm <job-number>

If the change locks you out, the server reverts it in 10 minutes. For network changes on Ubuntu, netplan try has the same idea built in. And a VNC console in your VPS control panel is the final fallback. See UFW and VPS VNC console.

batch: run when the system is idle

batch queues a job to run as soon as the load average drops below a threshold (1.5 by default):

echo "nice -n 19 /usr/local/bin/reindex.sh" | batch

That’s handy for heavy one-off tasks on a busy server. See uptime and load average.

Output and environment

  • Output (stdout/stderr) is mailed to the user, if local mail works. Otherwise redirect it: echo "/path/job.sh >> /var/log/job.log 2>&1" | at 03:00.
  • Unlike cron, at saves your current environment (working directory, variables) with the job, so commands usually behave as they would in your shell. Use absolute paths anyway.
  • The shell is /bin/sh.

Permissions

Access is controlled by /etc/at.allow and /etc/at.deny (like cron). If at.allow exists, only users listed in it may use at.

Alternative: systemd-run

On systemd systems, transient timers work without atd:

sudo systemd-run --on-active=10m systemctl restart nginx
sudo systemd-run --on-calendar="2026-11-06 02:00" /usr/local/bin/report.sh
systemctl list-timers

Output goes to the journal. See systemd timers.

Quick reference

Task Command
Run later echo "cmd" | at now + 10 minutes
From a file at -f script.sh 02:00
List atq
Show job at -c <id>
Remove atrm <id>
When idle echo "cmd" | batch

Related: crontab guide and anacron.

  • Linux
  • Automation
  • Command Line