at Command in Linux: Complete Guide to Scheduling One-Time Tasks and Parameters

at command in Linux and it perimeters

cron gets all the attention, but it’s the wrong tool for a huge category of everyday sysadmin needs: “run this one thing, once, later tonight.” That’s exactly what at is for. I reach for it constantly for one-off maintenance windows, delayed reboots, and “remind me in an hour” style scripts — it’s one of the most underrated commands in the standard Linux toolkit.

What at Actually Is

at schedules a command (or a whole script) to run once, at a specific point in the future, and then exits — no recurring schedule, no crontab entry to remember to clean up. It’s part of the at package, which also includes the related commands atq (queue listing) and atrm (job removal), plus the background daemon atd that actually executes the jobs when their time comes.

Where cron is built for “every day at 2 AM forever,” at is built for “in 45 minutes, do this one thing.”

Basic Syntax

at [options] TIME
atq
atrm job_id [job_id ...]

You don’t put the command on the same line as at — instead, at drops you into an interactive prompt (or reads from stdin) where you type the command(s) to run, then finish with Ctrl+D.

A Basic Example

at 22:00
warning: commands will be executed using /bin/sh
at> /usr/local/bin/backup.sh
at> <EOT>
job 3 at Fri Jul 31 22:00:00 2026

You type the command(s), press Enter, then press Ctrl+D to signal end-of-input. at confirms the job number and scheduled time.

For scripting, it’s usually cleaner to pipe the command in instead of typing interactively:

echo "/usr/local/bin/backup.sh" | at 22:00

Or schedule a whole script file with -f:

at -f /usr/local/bin/backup.sh 22:00

Time Specification Formats

at accepts a genuinely flexible, almost English-like time syntax:

at 10:00pm
at 22:00
at now + 1 hour
at now + 30 minutes
at noon
at midnight
at teatime          # 4pm, a fun historical quirk
at 10:00 tomorrow
at 3:00pm next week
at 2026-08-15 09:00
at 09:00 Aug 15

Some useful patterns:

at now + 1 day       # exactly 24 hours from now
at now + 2 weeks
at 5pm + 3 days       # 5pm, three days from now

Full Option Reference

-f file        read commands from file instead of stdin
-m             send email to the user when the job completes, even with no output
-M             never send email
-v             show the scheduled time before reading the job
-c job_id      display the actual commands the given job will run
-l             same as atq (list pending jobs)
-d job_id      same as atrm (delete a job)
-q queue       use a specific queue letter (a–z, A–Z); default queue is 'a'
-t time        specify the time in [[CC]YY]MMDDhhmm[.ss] format
-V             print version

Using -f for Script Files

at -f /usr/local/bin/generate_report.sh now + 2 hours

This is the pattern I use most in scripting/automation contexts — reference an existing, tested script rather than typing commands inline.

Checking What a Job Will Actually Run

at -c 3

This dumps the full environment and command that job number 3 will execute — extremely useful for debugging a scheduled job that isn’t producing the output you expect, since it shows you exactly what environment variables were captured at scheduling time.

Queues

at supports multiple queues, denoted by single letters. The default queue is a; the batch queue (see below) is b. Jobs in “higher” alphabetical queues run with increasingly lower priority (higher nice value). This is a rarely-used feature, but useful for separating “must run promptly” jobs from “run when the system is idle” jobs:

at -q c 23:00

Listing Pending Jobs: atq

atq
3    Fri Jul 31 22:00:00 2026 a john
4    Sat Aug  1 09:00:00 2026 a john

The columns are: job number, scheduled date/time, queue letter, and owning user. As a regular (non-root) user, you only see your own jobs; root sees everyone’s.

Removing Scheduled Jobs: atrm

atrm 3

Removes job 3 from the queue silently (no output on success). You can remove multiple jobs at once:

atrm 3 4 5

Or combine listing and removal in a script — for example, to clear every job scheduled by the current user:

atq | awk '{print $1}' | xargs -r atrm

The batch Command

Closely related to at is batch, which schedules a command to run as soon as system load drops below a threshold (defined by atd, default around 1.5, adjustable with atd -l), rather than at a fixed clock time:

batch
at> /usr/local/bin/heavy_compile.sh
at> <EOT>

This is genuinely useful for resource-intensive, non-urgent jobs on shared or load-sensitive systems.

Where at Jobs Actually Live

Pending jobs are stored as individual spool files, typically in:

/var/spool/cron/atjobs/     # modern systems
/var/spool/at/               # older systems

Each spool file is essentially a self-contained shell script capturing the environment (working directory, environment variables, umask) at the moment you scheduled it — which is why a job scheduled from an interactive SSH session with certain env vars set will run later with that environment, not whatever the environment looks like at execution time.

How It Works Internally

The atd daemon runs continuously in the background (check with systemctl status atd), waking periodically to check the spool directory for jobs whose scheduled time has arrived. When a job’s time comes, atd executes it via /bin/sh, capturing stdout and stderr, and — unless -M was used — emails any output to the owning user through the local mail system, exactly like cron does.

Controlling Access

Just like cron, access to at is gated by two optional files, checked in order:

  • /etc/at.allow — if present, only listed users may use at.
  • /etc/at.deny — if at.allow doesn’t exist, users listed here are blocked; everyone else allowed.
sudo cat /etc/at.allow /etc/at.deny 2>/dev/null

Real-World Examples

Scheduling a maintenance-window reboot:

echo "/sbin/shutdown -r now" | at 03:00

Delayed cleanup after a deployment:

at now + 30 minutes <<'EOF'
rm -rf /tmp/deploy_staging_*
systemctl restart myapp
EOF

Warning users before a scheduled maintenance shutdown:

echo "wall 'System going down for maintenance in 5 minutes'" | at now + 55 minutes
echo "shutdown -h now" | at now + 60 minutes

A cleanup script that self-schedules its own removal reminder:

#!/bin/bash
# deploy.sh
./run_deployment.sh
echo "/usr/local/bin/verify_deployment.sh" | at now + 10 minutes

Auditing what’s currently pending across all users (as root):

for job in $(atq | awk '{print $1}'); do
    echo "=== Job $job ==="
    at -c "$job" | tail -5
done

Troubleshooting

“at: command not found” — the at package isn’t installed. Install with apt install at (Debian/Ubuntu) or dnf install at (RHEL/Fedora).

Job doesn’t run at the scheduled time — check atd is running: systemctl status atd; enable it if needed with systemctl enable --now atd.

Job ran but had no effect / wrong environment — remember the job’s environment (PATH, working directory, env vars) is captured at scheduling time, not execution time. If you scheduled it from a stripped-down environment (e.g., via a script called by cron), it inherits that stripped environment. Use at -c job_id to inspect exactly what’s stored.

No output visible after job runs — same as cron, output goes to local mail unless redirected; check mail for the owning user, or explicitly redirect inside the job:

echo "/usr/local/bin/task.sh >> /var/log/task.log 2>&1" | at 22:00

Permission denied using at — check /etc/at.allow and /etc/at.deny.

Comparison to Related Tools

  • cron/crontab — for recurring schedules; at is strictly for one-time execution.
  • systemd-run --on-calendar= / transient timers — the systemd-native equivalent, capable of one-shot scheduled execution with full journal logging and dependency management; more powerful but more verbose than at.
  • sleep && command — a crude manual alternative (sleep 3600 && ./task.sh &), but it ties the job to your current shell session and is lost if the session ends, unlike at, which persists independent of your login session.
  • batch — load-based deferred execution, part of the same package.

at behaves consistently across Debian, Ubuntu, RHEL, CentOS, Fedora, and SUSE, since it’s essentially always the same upstream at package. The main variance is whether it’s installed by default (increasingly, it isn’t, and needs a manual apt/dnf install at).

Summary

at fills the exact gap that cron leaves open: quick, one-off, future-scheduled commands that don’t deserve a permanent crontab entry. Combined with atq for visibility and atrm for cleanup, it’s a lightweight, dependable trio for anything from “reboot in 20 minutes” to “run this report once, tonight.” Learn the flexible time syntax once, and you’ll find yourself reaching for it far more often than you’d expect.

References

  • man at, man atq, man atrm, man atd on your local system
  • GNU/Linux at package documentation (Debian and Red Hat package repositories)
  • Debian Administrator’s Handbook, “Task Scheduling: cron and atd”
Total
0
Shares

Leave a Reply

Previous Post
modprobe command in Linux and it perimeters

modprobe Command in Linux: Complete Guide to Loading Kernel Modules and Parameters

Next Post
atq command in Linux and it perimeters

atq Command in Linux: Complete Guide to Viewing Scheduled Job Queue and Parameters

Related Posts