Skip to main content

Deploy cron jobs to many servers from one script folder

A lightweight way to manage scheduled jobs on a handful of Linux servers without Ansible. Keep one folder of scripts and cron files on an admin box, copy it to every server, and let each server install the cron files meant for it.

Folder layout

/opt/scripts/
  backup_scripts/...
  crontab_scripts/
    cron0hourly            # all servers
    cronloadmonitor        # all servers
    web01_croncertbot      # only on the host whose short name is "web01"
    db01_cronbackupmysql   # only on db01
  profile.d/*.sh           # login scripts for every server
  • Naming rule: files starting with cron go to every server. Files named <shorthost>_<name> go only to that host.
  • Every file uses /etc/cron.d format, which includes the user field:
    */5 * * * * root /opt/scripts/loadmon.sh >/dev/null 2>&1
    
  • Put shell logic in scripts, never in a cron file. Cronie skips the whole file if one line isn't a valid cron entry.

The installer (runs on each server)

Install only files with your own prefix, and remove only your own stale files. Don't wipe /etc/cron.d, because packages put their own jobs there (0hourly, raid-check, monitoring agents).

#!/bin/bash
set -euo pipefail
[[ $EUID -eq 0 ]] || exit 1
SRC=/opt/scripts/crontab_scripts
host=$(hostname -s | tr '[:upper:]' '[:lower:]')
want=()
for f in "$SRC"/cron* "$SRC"/"${host}"_*; do
  [[ -f $f ]] || continue
  name="optscripts-$(basename "$f")"
  install -m 644 "$f" "/etc/cron.d/$name"; want+=("$name")
done
for f in /etc/cron.d/optscripts-*; do
  [[ -e $f ]] || continue
  [[ " ${want[*]} " == *" $(basename "$f") "* ]] || rm -f "$f"
done
for f in /opt/scripts/profile.d/*.sh; do
  install -C -m 644 "$f" /etc/profile.d/
done

Run the installer from its own cron file, so you never overwrite root's personal crontab:

# /etc/cron.d/optscripts-installer
*/15 * * * * root /opt/scripts/crontab_scripts/install_cron.sh >/dev/null 2>&1

If you put a file in /var/spool/cron/ instead, it must be mode 0600, or cronie refuses it ("BAD FILE MODE").

Pushing the folder

From the admin box, loop over a host list:

while read -r h; do
  rsync -a --delete --chmod=D755,F755 --exclude 'secrets/' /opt/scripts/ "root@$h:/opt/scripts/" \
    || echo "push to $h failed"
done < /opt/scripts/hosts.txt
  • Keep anything containing secrets out of the pushed folder.
  • --chmod stops loose file modes from being copied everywhere.
  • Fix ownership at the source: chown -R root:root /opt/scripts.

Why cron0hourly?

The installer above leaves stock files alone. If you use a "replace everything" approach instead, remember that the RHEL cronie package's /etc/cron.d/0hourly is what runs /etc/cron.hourly. Put an equivalent back:

01 * * * * root run-parts /etc/cron.hourly

Better long-term

Once you have more than a few servers, use Ansible (the cron and copy modules) or a git pull on each host.