Every programming language out there has one. A library written for the sole purpose of being able to run tasks on a schedule. That’s great and all, but in a lot of scenarios, it’s really unnecessary. Unix-like operating systems can do this (usually) out of the box, and they actually do it quite well.
For the sake of this post, I’m going to assume that you have the following commands already available to you as they are fairly standard issue with most Linux distributions. If they aren’t available, feel free to install them via your package manager of choice.
If you’re into
docker, you can always spin up a container and play around in there:
$ docker run -it debian:latest bash
Once the container is up and running and you’re greeted with a prompt, you can install the commands used in this article:
$ EDITOR=vim # we need an editor, may as well be a good one ;) $ apt update $ apt install -y at cron vim $ atd # to start the `at` daemon
cron, initially released in May 1975 (44 years ago at the time of this writing), is a tried and true time-based job scheduler. The name was inspired by the Greek word for time, which is chronos in English.
Before you try editing the
crontab, let’s take a look at what’s already in there:
$ crontab -l
This will display the currently installed
crontab. On a fresh system, the file should be loaded with a ton of information of how to configured a task inside of the
To edit the current user’s
crontab, you can issue the following command:
$ crontab -e
Which will bring up the file for the current user in your editor of choice (for me, it’s
This should bring up the same thing we just saw in our editor. The format of the entries in the
crontab file are, from left to right:
- Day of the month
- Day of the week
- The command you’d like to run
For any of the “time” entries above, you can simply specify a wild card character,
* to tell
cron to “execute this every…”.
The most you can run a
cron job is every minute, which is represented by:
* * * * * /some/awesome/command
Every minute of every day can be excessive in a lot of ways, so here’s a few, less aggressive examples:
*/15 * * * * # Every 15 minutes 0 * * * * # Hourly at the top of the hour 15 6 * * * # Daily at 6:15 AM 44 4 * * 5 # Every Friday at 4:44 AM 0 0 1 * * # Midnight on the first of the month 0 0 1 1 * # Midnight on the first of the year
Once everything is how you’d like it, simply exit out of your editor (
vim) and the new tasks will be installed and run at the defined schedule.
Made a mistake and don’t want to save the new
crontab? Simply exit your editor without saving the file, and
cron will detect that no modifications were made, and won’t replace the currently installed
crontab. If you’re using
vim, that just means omitting the
w and simply hitting
If remembering the format of the time values for
cron isn’t at the top of your TODO list, there are some great resources out there to help. One of my favorite’s is crontab guru which allows you to define times to see what they translate to, but also provides a nice list of standard time strings that you can easily copy and paste.
If you have a script that has to be run as root, you can repeat the aforementioned command by using
su -c depending on which you have available:
$ sudo crontab -e # If you have sudo $ su -c 'crontab -e' # If you don't
There’s nothing wrong with managing your
crontab this way, but speaking from experience, it’s quite easy to slip
-r into the command and lose your currently installed
You could very well alias
crontab -i to ensure you’re prompted before a catastrophic removal, or you could start loading your
crontab from a file:
$ crontab /path/to/crontab
The advantage here is that you could very well include the
crontab in a project’s repository. The file will then be under version control with distributed backups by design.
If you wanted to take the current
crontab and save it to a file, you can use the
-l flag from earlier to send the contents to a file:
$ crontab -l > /path/to/crontab
cron utility is great for scheduling repeating tasks, down to the minute, but isn’t necessarily suited for those times that we simply want to run a task later. For one-off tasks, you can use the
at command to schedule a one time only tasks:
$ at now + 2 minutes
Will greet you with the
at> prompt which you can enter one or more commands into:
$ at> echo "Hello World!" > /tmp/output.txt
Once you’ve entered in the command or commands you’d like to run, simply press
CTRL+D to exit. Upon exiting,
at will let you know the job number and what time the task will execute.
After the time has passed, you can use
cat to verify that our
at command ran:
$ cat /tmp/output.txt # Hello World!
at also has a couple of other commands that you can use,
atq to show the pending jobs you have for the current user and
atrm to remove a job by it’s ID.
Both of these commands are also conveniently aliased to arguments that can be passed to
at directly. Let’s say we scheduled a command for next month:
$ at 12:34 PM next month
at> prompt, enter in the following, or the command of your choosing:
$ $ at> echo "This is next month's problem"
CTRL-D to save and exit the
at> prompt. Now that our task has been added to the queue, we can use the following commands to interact with it:
# List the scheduled tasks $ at -l # Show the task to be run by ID $ at -c 1 # Removes the task by ID $ at -r 1 # ALSO removes the task by ID $ at -d 1
Unfortunately, there’s not a way to edit a task once it’s been added to the
at schedule so in that scenario, you will need to delete a task and then re-add it.
A few other notable arguments that
at can accept are:
# Load from a file, skips the at> prompt $ at tomorrow -f /path/to/script # Send mail to the current user $ at tomorrow -m
at command is even available on Microsoft Windows machines via the
at.exe command. The arguments are a bit more “Windowsy” so YMMV.
The command-line is pretty powerful stuff and a lot of the utilities out there have been battle tested for decades. While older isn’t always better, utilizing these tried and true technologies allows you the freedom to switch between languages easier since you’re not tightly coupled to specific packages.
The caveat with leveraging
cron instead of a language specific library is that you are limited to running every minute at it’s fastest. Language specific schedulers are implemented more as a daemon or long-running task which allows them the flexibility of running every second (and faster) if need be.