Scheduling a one-time job with at is easy. Un-scheduling one you made a mistake on — or one that’s no longer needed because the situation changed — is what atrm is for. It’s a tiny command, but it’s saved me more than once from a job firing off at 3 AM that I really didn’t want running anymore.
What atrm Actually Is
atrm removes one or more pending jobs from the at job queue, identified by the job number that at assigned when the job was scheduled (and that atq shows you). It’s part of the same at package as at, atq, and the background atd daemon. Functionally, atrm is equivalent to at -d, which does the identical operation under a different flag on the base at command.
Basic Syntax
atrm job_id [job_id ...]
You need at least one job ID. There’s no interactive mode and no confirmation prompt — atrm deletes silently and immediately, so it’s worth double-checking the job ID before you run it.
A Basic Example
First, find the job number with atq:
atq
3 Fri Jul 31 22:00:00 2026 a john
4 Sat Aug 1 09:00:00 2026 a john
Then remove it:
atrm 3
No output on success. Verify it’s gone:
atq
4 Sat Aug 1 09:00:00 2026 a john
Job 3 is no longer listed and will not execute.
Removing Multiple Jobs at Once
atrm 3 4 7
Each job ID is processed independently — if one ID doesn’t exist or belongs to another user (and you’re not root), atrm reports an error for that specific ID but still processes the rest of the list.
atrm 99
atrm: 99: No such file or directory
Options
atrm itself has essentially no options beyond the job ID list — it’s intentionally a one-trick command. The equivalent functionality via the base at command does support a couple of related flags:
at -d job_id # identical to atrm job_id
There’s no -f (force) or -i (interactive confirm) flag on standard atrm — deletion is immediate and unconditional, which is worth remembering, especially in scripts.
Permissions and Ownership
A regular user can only remove their own jobs. If you try to remove another user’s job without root privileges:
atrm 5
atrm: 5: Permission denied
Root can remove any job on the system, from any user:
sudo atrm 5
Real-World Examples
Clearing every job you personally have pending:
atq | awk '{print $1}' | xargs -r atrm
The -r flag on xargs prevents it from running atrm with no arguments at all if atq returns nothing, which would otherwise produce an unhelpful “at least one job ID required” error.
Removing a job by matching its command content:
Since atrm only works with job numbers, and atq doesn’t show the actual command, you typically need to combine it with at -c to find the right job first:
for job in $(atq | awk '{print $1}'); do
if at -c "$job" | grep -q "old_backup_script.sh"; then
echo "Removing job $job (matches old_backup_script.sh)"
atrm "$job"
fi
done
Cancelling a scheduled maintenance action if a deployment is aborted:
#!/bin/bash
# deploy.sh
JOB_ID=$(echo "systemctl restart myapp" | at now + 20 minutes | grep -oP 'job \K[0-9]+')
echo "Scheduled restart as job $JOB_ID"
# ... later, if deployment fails and restart should be cancelled:
if [ "$DEPLOY_FAILED" = true ]; then
atrm "$JOB_ID"
echo "Cancelled scheduled restart (job $JOB_ID)"
fi
This pattern — capturing the job ID at scheduling time so it can be cancelled conditionally later — is one of the more valuable uses of atrm in automation, since it lets a script “undo” a previously scheduled action based on later events.
Cleaning up stale jobs older than a certain queue letter or pattern (admin housekeeping, as root):
sudo atq | awk '$5=="olduser" {print $1}' | xargs -r sudo atrm
Removes all pending jobs belonging to a user named olduser, for example as part of an account decommissioning process.
What Happens Under the Hood
Each pending at job is stored as an individual file in the spool directory (commonly /var/spool/cron/atjobs/), with a filename that encodes the job number, queue letter, and scheduled time. atrm simply locates and deletes the corresponding spool file (with appropriate permission checks first). Because the atd daemon works directly off this spool directory, removing the file is immediately effective — there’s no separate “queue database” to update or daemon to notify or restart.
Troubleshooting
“atrm: job_id: No such file or directory” — the job either already ran, was already removed, or the ID was mistyped. Double-check with atq first.
“atrm: job_id: Permission denied” — you’re trying to remove a job that belongs to a different user; you’ll need root (sudo atrm job_id).
Job still shows in atq after running atrm — very rare, but can happen if there’s a permissions issue on the spool directory itself preventing deletion; check ownership and permissions on /var/spool/cron/atjobs/ and check atd‘s logs.
“atrm: command not found” — the at package isn’t installed; install with apt install at or dnf install at — this also installs at, atq, and atd.
Accidentally removed the wrong job — there’s no undo. This is exactly why I always run atq and, for anything important, at -c job_id to confirm the contents, immediately before running atrm.
Security Considerations
- Because
atrmdeletes without confirmation, avoid using loosely-matched patterns (like the “match by command content” example above) in automated scripts without a dry-run/logging step first — a bad regex match could silently cancel an unrelated critical job. - On shared or multi-tenant systems, restrict who can use
at/atrmat all via/etc/at.allowand/etc/at.deny, since any user permitted to schedule jobs is also, by default, permitted to remove their own — and root can remove anyone’s. - As part of offboarding a user account, it’s good practice to check
sudo atqfor any jobs still owned by that username and clear them withatrmbefore or during account removal, so no orphaned scheduled action fires after the account is gone.
Comparison to Related Commands
at -d job_id— functionally identical alias for the same delete operation.atq— the natural partner command; you almost always runatqfirst to find the job ID you need to pass toatrm.crontab -r— the cron equivalent, but note the very different scope:crontab -rwipes an entire crontab in one shot, whileatrmremoves individual one-time jobs by ID, which is a much more surgical operation.systemctl stop/disableon a transient timer unit — the systemd-native equivalent for cancelling a scheduled one-shot timer.
atrm‘s behavior is consistent across Debian, Ubuntu, RHEL, CentOS, Fedora, and SUSE — it’s the same upstream at package everywhere, so scripts using it are highly portable.
Summary
atrm is a small, blunt, and completely reliable tool: give it a job ID, and that job is gone from the queue, immediately and without ceremony. The only real skill involved is knowing which job ID you actually want to remove — which is why atq (and at -c for double-checking contents) should always be your first stop before reaching for atrm.
References
man atrm,man at,man atqon your local system- GNU/Linux
atpackage documentation - Debian Administrator’s Handbook, “Task Scheduling: cron and atd”
