Fixed-time job scheduling with UNIX 'at' command
This weekend, I was working on a fun project that required a fixed-time job scheduler to run a curl command at a future timestamp. I was aiming to find the simplest solution that could just get the job done. I've also been exploring Google Bard recently and wanted to see how it stacks up against other LLM tools like ChatGPT, BingChat, or Anthropic's Claude in terms of resolving programming queries.
So, I asked Bard:
What's the simplest solution I could get away with to run a shell command at a future datetime?
It introduced me to the UNIX at command that does exactly what I needed. Cron wouldn't be a good fit for this particular use case, and I wasn't aware of the existence of at before. So I started probing the model and wanted to document my findings for future reference. Also, the final hacky solution that allowed me to schedule jobs remotely can be found at the end of this post.
The insipid definition
The command at in UNIX is used to schedule one-time jobs or commands to be executed at a specific time in the future. Internally, the system maintains a queue that adds a new entry when a job is scheduled, and once it gets executed, the job is removed from the queue.
NOTE: By default, the jobs will be scheduled using the targeted machine's local timezone.
Prerequisites
The command isn't included in GNU coreutils, so you might have to install it separately on your machine.
Debian-ish
On a Debian-flavored Linux machine, run:
Then check the status of atd daemon. This daemon executes the scheduled jobs.
If the service isn't running, then you can start the daemon with this command:
MacOS
On MacOS, scheduled jobs are carried out by atrun and it's disabled by default. I had to fiddle around quite a bit to make it work on my MacBook Pro running MacOS Ventura. First, you'll need to launch the daemon with the following command:
This will start the atrun daemon. Or enable it for future bootups by modifying /System/Library/LaunchDaemons/com.apple.atrun.plist to have:
On modern MacOS like Ventura, unfortunately, this requires disabling SIP. Next, you'll need to provide full disk access to atrun. To do so:
- Open Spotlight and type in Allow full disk access.
- On the left panel, click on Allow applications to access all user files.

- On the right panel, add /usr/libexec/atrun to the list of allowed apps. Press cmd + shift + g and type in the full path of atrun.

You can learn more about making atrun work on MacOS. Although I'm using MacOS for development, In my particular case, making at work on MacOS wasn't the first priority because I deployed the final solution to an Ubuntu container.
A few examples
The following sections demonstrates some examples of scheduling commands to be executed in a few different scenarios.
Schedule at a specific time
To schedule a command to be executed at a specific time, use this command syntax:
For example, to schedule the command ls -lah >> foo.txt to be executed at 3:00 PM local time, you'd use the following command:
Pressing tells at that you have finished entering the command, and it should schedule the job to run at the specified time. You'll see that at 3.00PM local time, a file named foo.txt containing the output of ls -l will be created.
Schedule after a certain period of time
To schedule a command to run in a specific amount of time from now, use:
For example, to schedule ps aux >> foo.txt to run in 2 minutes from now, you'd use the following command:
This will schedule the command to run in two minutes in the current local time.
Schedule a script run
You can also run a script containing multiple commands at a specific time. To do this, create a script file that houses the commands you want to run, and then use at to schedule the script to be executed at the desired time.
For example, suppose you have a script file called script.sh that contains a curl command which makes an API call and saves the output to a file. You can schedule it as such:
The script will be executed in a minute from now. You can check the content of foo.json 1 minute later:
Schedule in a non-interactive manner
What if you don't want to create a new script file and also don't want to schedule a command interactively as shown before? You can echo the desired command and pipe it to at like this:
We can also run multi-line commands in a single go by taking advantage of the heredoc format:
In either case, 1 minute later, you'll see that a foo.txt file will be created in your local directory with the following content:
This command above uses at to schedule the execution of a dig command for the domain name rednafi.com. In this case, dig performs a DNS lookup, and the scheduled time is set to be 1 minute from now in the current local time. The output of the command is then appended to the file foo.txt. The <<EOF syntax is used for input redirection, which allows the command to be specified in a heredoc format without requiring you to enter the command in interactive mode as before.
Schedule with UNIX timestamp
You can schedule jobs using a UNIX timestamp with the -t flag. The at command requires a timestamp in the format [[[mm]dd]HH]MM[[cc]yy][.ss]]. Here's an example that uses the date command to generate the current datetime, adds a 30-second offset to it, formats it to the at's expected format, and schedules a job.
On Linux, run:
On MacOS, run:
View and manage scheduled jobs
To view a list of scheduled jobs, use the following command:
This will display a list of all the tasks that are currently scheduled.
To remove a scheduled task, use the following command:
The job number is the number assigned to the task when it was scheduled. You can find the job number by running the atq command. If you need to clear all the pending jobs, use this:
A hacky way to schedule jobs remotely
This is a hacky and probably dangerous way to do remote job scheduling. However, the beauty of side projects is that nobody's here to tell you what to do and it's a fun way to play with hazmats.
I needed a way to quickly prop up a service that'd allow me to schedule webhook API calls at a fixed point in time in the future. So I exposed a simple NodeJS server that'd allow me to schedule an API call with at and execute the command at the desired datetime. Here's the complete server:
This endpoint takes in a shell command and just runs it on the server; bad idea, right? But the endpoint is secured behind a Bearer token and I'm the only one who's going to use this. Security by obscurity!
Before running the server, you'll need to install express and once you've done it, you can start the server with the following command:
Now, from a different console panel, you can schedule a remote task as follows:
This will return:
In my case, I needed to POST a payload at a certain time in the future:
Further reading
Discussion in the ATmosphere