at Command: One-Time Job Scheduling on Linux
What You'll Learn
- How to schedule a one-time job with
at - How to list and cancel pending jobs with
atq/atrm - When to reach for
atinstead ofcron
Quick Summary
- Run once at a set time →
at - Run repeatedly on a schedule →
cron/ systemd timer - Confirm
atdis running first;atqandatrmare all you need to manage jobs
Assumptions (target environment)
- OS: Ubuntu / Debian (RHEL-based works the same way)
- The
atpackage and theatddaemon are required (often not installed by default)
What Is the at Command?
Conclusion:
atruns a command once at a specified time. It is built for single, non-repeating tasks.
Where cron handles recurring jobs ("every day", "every week"), at handles one-off scheduled execution — "once tonight at 11 PM" or "once, 5 minutes from now".
A scheduled job is watched by atd (the at daemon), which runs it when the time arrives. The job survives logging out or closing your shell.
A related tool is batch. Instead of a time, batch runs a job when system load drops below a threshold. Use it to push heavy work out of busy periods.
Is at Ready to Use? Checking and Installing atd
Conclusion: Having the
atbinary is not enough — jobs only run ifatdis active. Check the daemon first.
On many Ubuntu / Debian systems at is not installed by default. Install and verify it:
# Debian / Ubuntu $ sudo apt install at # RHEL / CentOS / Rocky $ sudo dnf install at
Check the daemon status:
$ systemctl status atd
● atd.service - Deferred execution scheduler
Loaded: loaded (/lib/systemd/system/atd.service; enabled; preset: enabled)
Active: active (running) since ...
If it is not active (running), start and enable it:
$ sudo systemctl enable --now atd
The most common "my job never ran" cause is a stopped atd. You can register a job fine, but if atd is down nothing happens when the time comes.
How Do You Schedule a Job?
Conclusion: Run
at TIME, type commands at the prompt, and finish withCtrl+D. For scripts, pass them with-f.
Interactive entry
$ at now + 5 minutes warning: commands will be executed using /bin/sh at> echo "hello" >> /tmp/at-test.log at> <EOT> job 3 at Fri Jun 5 23:10:00 2026
Type commands at the at> prompt, then press Ctrl+D (shown as <EOT>). On success you get a job number and the scheduled time.
From a file (recommended)
Interactive input is error-prone. In practice, pass a script with -f:
$ at -f /path/to/job.sh 23:00
# Pipe a one-liner in $ echo 'tar czf /backup/data.tgz /var/www' | at 02:00 tomorrow
The working directory, environment variables, and umask are saved at submission time and restored at run time. Still, avoid relying on PATH — write commands with absolute paths.
How Do You Write the Time Spec?
Conclusion: You can use
HH:MM,now + N units,midnight/noon/teatime, and explicit dates. Relative specs are the most practical.
at accepts flexible time expressions. Common patterns:
| Form | Meaning |
|---|---|
at 23:00 |
11 PM today (tomorrow if already past) |
at 10:00 AM |
10 in the morning |
at now + 30 minutes |
30 minutes from now |
at now + 2 hours |
2 hours from now |
at midnight |
00:00 tonight |
at noon |
12:00 |
at teatime |
16:00 (tea time) |
at 02:00 tomorrow |
2 AM tomorrow |
at 10:00 next week |
Same weekday next week, 10:00 |
at 2026-12-31 23:59 |
Explicit date and time |
# Example: rotate logs at 3 AM tomorrow $ echo '/usr/local/bin/rotate-logs.sh' | at 3:00 tomorrow
If you give a time that has already passed today, at interprets it as the same time tomorrow. A job you expected to run "right away" can slip to the next day. To reliably target the near future, use now + N minutes.
How Do You List and Cancel Jobs?
Conclusion: List with
atq, remove withatrm JOBID, and inspect withat -c JOBID. These three cover job management.
List pending jobs (atq)
$ atq
3 Fri Jun 5 23:10:00 2026 a user 5 Sat Jun 6 02:00:00 2026 a user
Columns are job number / scheduled time / queue / owner. a is the normal queue; b is the batch queue.
Remove a job (atrm)
$ atrm 3
Delete by number. You can remove several at once:
$ atrm 3 5
Inspect a job (at -c)
To see exactly what is scheduled before it runs, use -c:
$ at -c 5
It prints the saved environment-restoration block and the actual command body.
atq is the same as at -l, and atrm is the same as at -d. In scripts, the explicit atq / atrm names read more clearly.
Where Does the Output Go?
Conclusion: A job's stdout and stderr are sent to the user by local mail. Without a mailer, the output is lost — redirect it to a file.
Output from an at job does not go to your terminal; it goes to local mail for the submitting user. Read it with mail or under /var/mail/<user>.
On a server with no MTA (mail delivery), the output is simply discarded. To keep results reliably, redirect inside the job:
$ echo '/usr/local/bin/backup.sh > /var/log/backup.log 2>&1' | at 02:00
If a job "ran but left no result", it likely went to mail, or you forgot to set an output target. Always redirect production jobs to a log file with > ... 2>&1.
When Should You Use at Instead of cron?
Conclusion: Use
cron/ systemd timer for recurring work andatfor one-time work. "Just tonight" or "once after maintenance" fitsatbest.
| Use case | Recommended |
|---|---|
| Recurring (daily, weekly) | cron / systemd timer |
| Once at a set time | at |
| Run when load drops | batch |
| Reliable recurring jobs (logs, dependencies) | systemd timer |
Writing a one-off task into cron and forgetting to remove it — so it fires again the next day — is a common mishap. When you know it is single-use, at needs no cleanup and is safer.
For recurring jobs in general, see cron Basics and systemd Timer vs cron.
Access Control and Caveats
Conclusion: Access to
atis controlled by/etc/at.allowand/etc/at.deny. Ifat.allowexists, it takes precedence.
- If
/etc/at.allowexists: only listed users may useat - If
at.allowis absent but/etc/at.denyexists: everyone except listed users may use it - If neither exists: many distros allow only root (behavior varies by distro)
# Allow a specific user only $ echo 'deploy' | sudo tee -a /etc/at.allow
On shared servers, at can become a foothold for running jobs. If you do not need it, restrict it with at.deny or stop atd. If you do use it operationally, explicitly allow users via at.allow.
Summary
atschedules a one-time job; recurring work belongs tocron- Jobs only run if
atdis active — check the daemon first when nothing happens - Prefer relative specs like
now + N minutes; a past time rolls to the next day - Manage jobs with
atq(list) /atrm(remove) /at -c(inspect) - Output goes to mail — redirect production jobs to a log file