Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Schedule a one-off job with at, atq and atrm

By updated

Improve this page

You do not always need cron or a systemd timer. When an action must run only once, in 10 minutes, tonight at 11 pm, or tomorrow morning, at is the most direct tool. This guide shows the at + atq + atrm trio, with outputs measured on Debian 13 and AlmaLinux 10, and the traps that make you believe a job is scheduled when it will never run.

What you will learn

  • Tell apart the uses of at and cron
  • Schedule a one-off command with at
  • List the queue with atq and read a job back with at -c
  • Delete a scheduled job with atrm
  • Check that a job did run
  • Avoid the classic mistakes (service, rights, environment)

In which context?

at is useful when an action must fire later, but only once. The atd daemon keeps the job in a queue, runs it when due, then forgets it:

  • restart a service outside production hours;
  • run a cleanup script in 15 minutes;
  • start an integrity check tonight;
  • schedule a technical reminder (mail, report, rotation).

at vs cron in 30 seconds

The difference fits in one word: at runs once, cron repeats. An at job leaves the queue as soon as it has run, a crontab line stays in place and fires again at each due time.

  • Use at for a one-off run.
  • Use cron for a recurring run.

A simple mental model:

  • "Run this script tomorrow at 09:00" -> at
  • "Run this script every day at 09:00" -> cron

Prerequisites

Do not assume at is there: on a minimal AlmaLinux 10 or RHEL 10, neither the package nor the unit exists. Start by checking the state of the atd service:

Terminal window
# Check that the atd service is active
systemctl status atd --no-pager

You should see Active: active (running).

Counter-intuitive, and a classic false positive: with atd stopped, submitting a job still succeeds. at accepts the work, queues it, and merely prints a warning, measured identically on Debian 13 and AlmaLinux 10:

warning: commands will be executed using /bin/sh
job 1 at Thu Jul 23 07:36:00 2026
Can't open /run/atd.pid to signal atd. No atd running?

atq then lists a job that will never run. So never conclude that the service is active because a job sits in the queue: check systemctl is-active atd separately.

Step 1: schedule a one-off job with at

Here we schedule a write into an output file. The -f option reads the commands from a file rather than from standard input, and the expression now + 2 minutes sets a relative due time.

Terminal window
mkdir -p /tmp/at-lab
printf '%s\n' 'echo "AT_OK" >> /tmp/at-lab/output.txt' > /tmp/at-lab/job.sh
at -f /tmp/at-lab/job.sh now + 2 minutes

Expected output (example):

warning: commands will be executed using /bin/sh
job 9 at Mon Apr 20 21:24:00 2026

The warning says /bin/sh, and it is only half true. On Debian and Ubuntu, the job is indeed run by /bin/sh, hence by dash. On AlmaLinux 10, at -c shows that the job body is handed to ${SHELL:-/bin/sh}, that is to the shell of the user who submitted the job, often bash. In both cases, the shell is not interactive: your aliases and functions do not exist. So write portable commands, with absolute paths.

The time is best written explicitly too. Three behaviours measured on Debian 13 and AlmaLinux 10 avoid bad surprises:

  • at 9:00 submitted at 20:49 is scheduled the next day at 9:00, without a warning;
  • at 0815 is read as 08:15, not as August 15; write at 09:00 Aug 15 for a date;
  • at now - 1 hour is refused with at: refusing to create job destined in the past.

Step 2: inspect the queue with atq

atq lists the pending jobs of the current user. It is the check to run right after scheduling, to verify the computed time of at.

Terminal window
atq

Expected output (example):

9 Mon Apr 20 21:24:00 2026 a bob
  • first column: job identifier;
  • date/time: planned run;
  • letter a: the queue of the job, a by default;
  • last field: owning user.

To read back what a job will actually run, at -c followed by the identifier prints the full script recorded by at. It starts with the environment captured at submission time, then ends with your commands:

Terminal window
at -c 9 | tail -5

Step 3: cancel a job with atrm

atrm removes a job from the queue before it runs, naming it by the identifier read in the first column of atq. Run atq again afterwards to confirm the deletion.

Terminal window
# Replace 9 with the actual identifier
atrm 9
atq

If the queue is empty, atq returns nothing.

Step 4: check the run when you do not delete the job

A job that has run leaves the queue: atq keeps no history. The only proof of the run is therefore the effect produced by the script, here a line in the output file. Schedule a job and let it run:

Terminal window
at -f /tmp/at-lab/job.sh now + 1 minute

After ~1 minute:

Terminal window
cat /tmp/at-lab/output.txt

You should see at least one AT_OK line.

Control who may use at

Two files govern access, on the same principle as for cron: /etc/at.allow, when it exists, is an allow list and makes /etc/at.deny irrelevant; otherwise, the accounts listed in /etc/at.deny are refused. A blocked account gets You do not have permission to use at.

The shipped values differ. On Debian 13 and Ubuntu 24.04, /etc/at.deny already lists the system accounts (alias, backup, bin, daemon, ftp...). On AlmaLinux 10, it is empty. In both cases, creating an empty /etc/at.allow forbids at to every account except root (measured on Debian 13).

Best practices

An at job runs with nobody in front of the screen, in a reduced environment: these rules aim at making it fail visibly rather than silently.

  • Use absolute paths in the scripts called by at.
  • Prefer a script (at -f script.sh) to a complex inline command.
  • Log the output (>> /var/log/... 2>&1) to ease the diagnosis.
  • Test the command directly before scheduling it.

Quick troubleshooting

Read this table by symptom: the first three lines cover the most frequent case, an atd service missing or stopped, which lets the queue fill up while nothing runs.

SymptomLikely causeFix
at: command not foundpackage not installedsudo dnf install at or sudo apt install at
systemctl is-enabled atd answers not-foundthe unit does not exist: the package is not installedinstall the package, then enable --now
The service is enabled but inactive (dead)the installation enables without startingsudo systemctl start atd
A time already past is accepted without a warningat 9:00 submitted in the afternoon is moved to the next dayCheck with atq; an explicitly past instant (now - 1 hour) is refused
0815 read as 08:15, not August 15a time form, not a dateWrite the date explicitly (at 09:00 Aug 15)
The job output disappearsno mail agent installed (Exec failed for mail command)Redirect explicitly to a file in the scheduled command
job never runatd inactivesudo systemctl enable --now atd
job present but no effectinvalid script or incomplete PATHuse absolute paths, read the job back with at -c
You do not have permission to use at.at.allow/at.deny policycheck /etc/at.allow and /etc/at.deny

Put it into practice

Schedule a one-off job with at on a VM, then check that it runs at the planned time and that the queue is handled correctly.

Key points

  • at schedules a one-off job, atq lists the pending jobs, atrm deletes one.
  • at -c <id> prints the script actually recorded, environment included.
  • The atd service must be active: a job in the queue proves nothing.
  • The job runs in a non-interactive shell (/bin/sh on Debian, the user's $SHELL on RHEL): no aliases, absolute paths.
  • /etc/at.allow, when it exists, takes precedence over /etc/at.deny.
  • In an admin/certification context, at complements cron and timers.

Knowledge check

Check that the essentials of this guide stuck. The questions cover only what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn