at Command: One-Time Job Scheduling on Linux

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 at instead of cron

Quick Summary

  • Run once at a set timeat
  • Run repeatedly on a schedulecron / systemd timer
  • Confirm atd is running first; atq and atrm are all you need to manage jobs

Assumptions (target environment)

  • OS: Ubuntu / Debian (RHEL-based works the same way)
  • The at package and the atd daemon are required (often not installed by default)

What Is the at Command?

Conclusion: at runs 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 at binary is not enough — jobs only run if atd is 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 with Ctrl+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.

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 with atrm JOBID, and inspect with at -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 and at for one-time work. "Just tonight" or "once after maintenance" fits at best.

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 at is controlled by /etc/at.allow and /etc/at.deny. If at.allow exists, it takes precedence.

  • If /etc/at.allow exists: only listed users may use at
  • If at.allow is absent but /etc/at.deny exists: 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

  • at schedules a one-time job; recurring work belongs to cron
  • Jobs only run if atd is 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

Next Reading