Skip to content

How to Use the at Command in Linux to Schedule One-Time Jobs

Updated 6 min read

Updated: 12 October 2026 · Applies to: at 3.2.5 on Debian 13, Ubuntu 26.04 and AlmaLinux 10.2 (tested); the same package and commands on Rocky Linux and RHEL

The at command in Linux runs a command once, at a time you choose. Pipe the command into at with the time: echo "/root/backup.sh" | at 23:30, or echo "reboot" | at now + 2 hours. atq lists the waiting jobs and atrm NUMBER cancels one. The atd service must be running. For a job that repeats every day or every hour, use cron instead.

Quick reference

  • Run a command at a time: echo "COMMAND" | at 23:55
  • Run a script file: at -f /root/job.sh 03:00
  • Run it in 30 minutes: echo "COMMAND" | at now + 30 minutes
  • List pending jobs: atq (same as at -l)
  • Show what a job will run: at -c 4
  • Cancel a job: atrm 4 (same as at -r 4 or at -d 4)
  • Run when the server is not busy: echo "COMMAND" | batch

1. Install at and start atd

Minimal server images often do not include at. Install it and start the atd service, which runs the jobs:

# Debian and Ubuntu
sudo apt install at
# AlmaLinux, Rocky Linux and RHEL
sudo dnf install at

sudo systemctl enable --now atd
systemctl status atd

The unit is called atd.service on all of these systems. If atd is not running, at still accepts the job but warns you:

$ echo 'date' | at now + 1 minute
warning: commands will be executed using /bin/sh
job 1 at Sun Oct 11 14:51:00 2026
Can't open /run/atd.pid to signal atd. No atd running?

In our test the job stayed in the queue and ran once atd was started.

2. Schedule a job

There are three ways to give at the commands:

  1. Type them. Run at with a time, type one command per line at the at> prompt, then press Ctrl+D on an empty line:
    $ at now + 5 minutes
    warning: commands will be executed using /bin/sh
    at Sun Oct 11 15:26:00 2026
    at> echo hello > /tmp/hi.txt
    at> <EOT>
    job 1 at Sun Oct 11 15:26:00 2026
  2. Pipe one command in. This is the quickest way and works in scripts:
    echo "/usr/local/bin/report.sh > /root/report.log 2>&1" | at 23:55
  3. Read a script file with -f:
    $ at -f /root/job.sh now + 1 minute
    warning: commands will be executed using /bin/sh
    job 12 at Sun Oct 11 14:51:00 2026

The warning is normal: the job runs with /bin/sh, so avoid Bash-only syntax in the commands, or call a script that starts with #!/bin/bash. According to the at manual, the job keeps the working directory, the umask and most environment variables you had when you scheduled it. at -v prints the run time before it reads the commands, which is a quick way to check how a time was understood.

3. Time formats that at understands

We submitted each of these on Debian 13 at 14:50 UTC on a Sunday; the result on the right is the time at reported. AlmaLinux 10 gave the same results.

  • now + 1 minute: 14:51 the same day. Units are minutes, hours, days and weeks.
  • now + 2 hours: 16:50 the same day.
  • 23:55: today at 23:55, or tomorrow if that time has passed.
  • noon tomorrow: Monday 12:00. midnight (00:00) and teatime (16:00) also work.
  • 9am next monday: Monday 09:00.
  • midnight + 3 days: Wednesday 00:00. It counts from the midnight that started today, not the next one.
  • 10:30 AM Oct 20: 20 October at 10:30.
  • 09:00 2026-10-20: 20 October at 09:00. The date must come after the time.
  • -t 202610201030: 20 October at 10:30, in the format [[CC]YY]MMDDhhmm.

A time at cannot read is rejected and nothing is queued:

$ echo 'echo x' | at 25:00
Problem in hours specification. Last token seen: 25:00
Garbled time

Times are in the server's own time zone, not yours. Check it with date before you schedule anything important; our lab containers ran in UTC. See how to check the time zone in Linux if you need to check or change it.

4. List, inspect and cancel jobs

$ atq
4	Mon Oct 12 12:00:00 2026 a root
5	Sun Oct 11 23:55:00 2026 a root
15	Sun Oct 11 14:50:00 2026 b root

Each line shows the job number, the run time, the queue and the owner (output shortened). Queue a is for at jobs and b for batch. The list is not in time order, so read the dates. Root sees every user's jobs; other users see only their own.

  • at -c 4 prints the whole job: the saved environment first, and your commands at the end. Use it to check what will really run.
  • atrm 4 cancels job 4. It prints nothing when it works.
  • To change a job, cancel it and schedule it again; at has no edit option.

5. Where the output goes

At does not print to your terminal. According to its manual, anything the job writes to standard output or standard error is mailed to you with /usr/sbin/sendmail. -m sends a mail even when there is no output, and -M never sends one. Most VPS images have no mail server, so redirect the output to a file:

echo "/root/backup.sh >> /root/backup.log 2>&1" | at 02:00

6. batch: run a job when the server is quiet

batch takes commands the same way as at but has no time: it starts the job as soon as the load average drops below a limit. According to the atd manual pages we read, the limit is 1.5 on Debian and Ubuntu and 0.8 on AlmaLinux. Start atd with -l to change it (on AlmaLinux, through OPTS in /etc/sysconfig/atd).

echo "tar -czf /root/site.tar.gz /var/www" | batch

Lab example: on our busy single-CPU test machine (load average about 1.5 to 1.9), a batch job waited about three minutes before it ran. That is the expected behaviour, not a fault.

7. Allow or block users

Root can always use at. For other users, according to the at.allow manual: if /etc/at.allow exists, only the users listed in it may use at; otherwise everyone not listed in /etc/at.deny may. Debian and Ubuntu ship an at.deny that lists system accounts; on AlmaLinux 10 it is empty, so every user may use at. A blocked user sees:

$ echo id | at now + 5 minutes
You do not have permission to use at.

at, cron or a systemd timer?

  • Once, at a set time: at.
  • Repeating (every night, every 5 minutes): cron. See how to add cron jobs and how to edit cron jobs. To list every scheduled job on a server, including atq, see list all cron jobs for all users.
  • Without at installed: on systemd servers, sudo systemd-run --on-active=3600 /usr/local/bin/report.sh runs the command once, an hour later, as a temporary timer (according to the systemd-run manual). systemctl list-timers shows it.
  • For jobs that must run even if the server was off at the planned time, see scheduling jobs with anacron.

On shared hosting without root access, use your control panel's cron jobs page instead (cron jobs in cPanel). On a Ucartz KVM VPS you have full root access, so you can install and use at as shown above.

Official documentation: at manual page.

Was this answer helpful?

Still stuck? Open a ticket