Now, if you’ve been following along, you’ve got the basics of crontab -e down. You can make a single script run at 3:17 AM on the second Tuesday of every month that emails you a picture of a cat. Wonderful. But what about when you graduate from being a user who schedules tasks to being the system administrator who has to manage them? Or when you need to install a package that needs its own scheduled job? You don’t want that package mucking about in your personal crontab, and you certainly don’t want to edit its crontab as root. Enter the system’s scheduling directory: /etc/cron.d.

This isn’t magic; it’s just better organization. Think of /etc/cron.d/ as a drop-box. You, or any software package you install (like apt on Debian-based systems), can toss a file in there, and the cron daemon will automatically pick it up and treat it just like a system-wide crontab. It’s a way to compartmentalize. Your personal crontab is for your stuff. The system’s main crontab (/etc/crontab) is for, well, the system’s stuff. And /etc/cron.d/ is for everything else that needs a clean, separate home.

The format inside a /etc/cron.d/ file is almost identical to a user crontab, but with one crucial, face-slapping difference: it must include the user field. This is because the system has no idea what user you want this job to run as, so you have to tell it. Forgetting this is a classic mistake that will leave you staring at syslog wondering why your script didn’t run.

The Anatomy of a cron.d File

Let’s say you’re installing a monitoring agent that needs to run a script every five minutes. The package would create a file like /etc/cron.d/my-monitor-agent. It would look like this:

# Run the monitoring agent every 5 minutes
*/5 * * * *   monitor-agent  /usr/local/bin/my-agent --collect

See that monitor-agent user field? That’s the key. It tells cron to run the command as the monitor-agent user, which is a dedicated user you’d create for this purpose (because you’re not a barbarian who runs everything as root, right?).

You can also use comments and environment variables just like in a regular crontab:

# Set the PATH so we know what we're working with
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# Run the database backup at 2 AM every day
0 2 * * *   postgres  /opt/scripts/db-backup.sh > /var/log/db-backup.log 2>&1

# Clean up temp files every Sunday at 3:15 AM
15 3 * * 0   root  /opt/scripts/cleanup-tmp.sh

The Magical cron.{daily,weekly,monthly} Directories

Here’s where the designers decided to be helpful, and in doing so, created a system that is brilliantly simple and occasionally, utterly baffling to newcomers. Instead of forcing you to write a cron schedule for 0 2 * * * for a daily job, you can just drop a script into /etc/cron.daily/. The system will run all scripts in that directory once a day. Magic!

Except it’s not magic, it’s just an entry in the main /etc/crontab:

# /etc/crontab - look at the bottom!
# m h dom mon dow user  command
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6    * * 7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6    1 * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )

Ah, now we see the machinery. The system cron runs run-parts on each directory at the specified time. run-parts is a fussy little utility that runs every executable script in a directory, in alphabetical order.

The Gotchas (Because of course there are gotchas):

  1. No Dotfiles: run-parts will explicitly ignore any file with a dot (.) in it. So backup.sh will run, but backup.sh or .my-secret-job will be ignored. This is ostensibly for safety, but mostly to annoy you.
  2. It’s Just a Script: The files in these directories aren’t special cron files; they are regular old executable shell scripts. They must have the execute bit set (chmod +x /etc/cron.daily/my-script).
  3. No User Field: You can’t specify a user inside the script in these directories. The scripts are run as root (as defined in the crontab above). If you need to run as another user, you have to handle that inside the script itself using sudo -u or similar. This is a huge pitfall.

Best Practices: Don’t Shoot Yourself in the Foot

  • Use the Right Tool: Use cron.d for tasks that need a specific, non-standard schedule (e.g., every 5 minutes) or need to run as a specific user. Use cron.daily for things that genuinely need to happen once a day and are okay running as root.
  • Name Files Carefully: In cron.d, prefix your filenames to avoid conflicts with other packages (e.g., myapp-maintenance, not logrotate). In the cron.* directories, you can use numbering (e.g., 00logrotate, 10myapp-backup) to control execution order since run-parts runs them alphabetically.
  • Log Everything: Your scripts in cron.daily are running silently. Make them output something to a logfile so you know they ran and whether they succeeded. set -e at the top of your bash script is a good start to bail on errors.
  • Beware of Anacron: Notice the test -x /usr/sbin/anacron in the system crontab? That’s for laptops and desktops that might be off at 6 AM. Anacron handles running those daily/weekly jobs when the machine wakes up. It’s great, but just be aware it’s part of the chain on some systems.

The cron.d and cron.* directories are what separate a messy, personal hackspace from a well-managed system. Use them. Your future self, the one who doesn’t have to remember which of twelve personal crontabs contains the backup job, will thank you.