Skip to content
Go back

The `at` Command: One-Time Scheduled Tasks in Linux

By SumGuy 5 min read
The `at` Command: One-Time Scheduled Tasks in Linux

Cron is everywhere—everyone knows it. But here’s the thing: cron is for recurring jobs. You reboot every Sunday at 2 AM, run backups daily, check logs every hour. That’s cron’s bread and butter.

But what if you need to run something exactly once, at a specific time? That’s where at comes in. It’s the Linux scheduler nobody talks about, and it’s dead simple.

Why Not Just Use Cron?

You could add a cron job that runs once and then deletes itself. You could set an alarm on your phone. Or you could use at, which exists specifically for this job.

Think of it like this: cron is your daily standup meeting. at is the one-off Zoom call your manager springs on you Friday afternoon.

Installing at

On most systems, at is already installed. Check:

Terminal window
$ at -V
at version 3.2.5

If missing, grab it:

Terminal window
# Debian/Ubuntu
sudo apt install at
# RHEL/CentOS
sudo yum install at
# Fedora
sudo dnf install at

Then start the service:

Terminal window
sudo systemctl start atd
sudo systemctl enable atd

Basic Syntax

Terminal window
at [TIME] [DATE]

You pipe in your command and hit Ctrl+D when done.

Terminal window
$ at 3:00 PM tomorrow
at> /usr/local/bin/backup.sh
at> <Ctrl+D>
job 5 at Fri Jan 11 15:00:00 2025

That’s it. One job scheduled. Time formats are forgiving:

Terminal window
at 10:00 AM Tuesday # Next Tuesday at 10 AM
at 2:30 PM Jan 15 # Jan 15 at 2:30 PM
at midnight # Tonight at 00:00
at now + 2 hours # In 2 hours from now
at 14:00 + 3 days # 3 days from now at 2 PM

Managing Your Queue

See what’s scheduled:

Terminal window
$ atq
5 Fri Jan 11 15:00:00 2025 a kingpin
7 Mon Jan 13 08:00:00 2025 a kingpin

The leftmost number is the job ID. Want details?

Terminal window
$ at -c 5
#!/bin/sh
# atrun uid=1000 gid=1000
# mail kingpin 0
umask 22
HOSTNAME=laptop; export HOSTNAME
...
/usr/local/bin/backup.sh

Remove a job:

Terminal window
$ atrm 5
$ atq
7 Mon Jan 13 08:00:00 2025 a kingpin

The Batch Command

batch is at’s nerdy cousin. Instead of scheduling at a fixed time, it runs when the system load drops below 1.5:

Terminal window
batch
at> time-consuming-analysis.py
at> <Ctrl+D>
job 9 at Fri Jan 11 20:30:00 2025

Perfect for heavy jobs during off-peak hours without hammering the server.

Real-World Examples

Schedule a reboot after you finish work:

Terminal window
at 6:00 PM today
at> sudo systemctl reboot
at> <Ctrl+D>

Remind yourself to check on a deploy in 15 minutes:

Terminal window
at now + 15 minutes
at> notify-send "Check the deploy status"
at> <Ctrl+D>

Run a database maintenance job at 2 AM (one-time):

Terminal window
at 2:00 AM tomorrow
at> /opt/scripts/db-cleanup.sh >> /var/log/db-cleanup.log 2>&1
at> <Ctrl+D>

Stagger a series of deploys across fleet:

Terminal window
for server in web1 web2 web3; do
at now + $((RANDOM % 60)) minutes
at> ssh $server 'sudo systemctl restart myapp'
at> <Ctrl+D>
done

Gotchas

Jobs don’t have your shell aliases. Use full paths to scripts and binaries.

No email by default. If you want output mailed to you, pipe to | mail -s "Job done" user@host inside the at script.

atd must be running. If systemd isn’t managing it, jobs won’t fire.

Permissions. Check /etc/at.allow and /etc/at.deny. If either exists, only listed users can use at.

Using at with a Script File

Instead of interactive mode, pipe a script in directly:

Terminal window
echo '/usr/local/bin/backup.sh >> /var/log/backup.log 2>&1' | at 3:00 AM tomorrow

Or with a heredoc for multi-line:

Terminal window
at 6:00 PM today << 'EOF'
cd /var/www/myapp
git pull origin main
systemctl restart myapp
echo "Deploy done" | mail -s "Deploy status" admin@example.com
EOF

Clean, no interactive prompt, scriptable.

Environment Variables

at doesn’t inherit your current shell environment. It captures the environment at the time you run at and replays it. But PATH is often limited. Safe pattern:

Terminal window
at now + 5 minutes << 'EOF'
export PATH=/usr/local/bin:/usr/bin:/bin
/usr/local/bin/my-script.sh
EOF

Always use absolute paths inside at jobs. Assume nothing is in PATH.

Checking atd Logs

If your job didn’t run, check the daemon:

Terminal window
sudo journalctl -u atd -n 50

Common reasons jobs fail: atd wasn’t running, permissions in /etc/at.allow, or the script path was wrong.

When to Reach For at

It’s humble. It’s reliable. And it does exactly one thing well. Your 2 AM self will be grateful when you realize you didn’t need to write a self-deleting cron job.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it may appear here.


Previous Post
Why Your Docker Logs Are Eating Your Disk
Next Post
Why Your Docker Container Ignores Ctrl+C

Related Posts