API & Automation·7 min read·

Automating Daily Server Chores with Cron-Style Orchestration

Automate daily server chores with cron and systemd timers that drive backups, patching, and cert renewal, plus a fleet-wide converge pass.

NB

Netbay Cloud Team

Netbay Engineering

On this page

Day-two operations are mostly repetitive chores: nightly backups, weekly patching, cert renewal, log trimming, status checks. Each is trivial in isolation and collectively they eat weekends when done by hand. The trick is to make them scheduled, logged, and re-runnable, so a failure stays visible in a log file instead of being discovered months later. This post shows the classic cron route and the more modern systemd timer route, then the fleet-wide equivalent.

The Classic: Crontab

A cron line needs three pieces: a schedule, a command, and an output destination. The last one is what everyone forgets — without redirection, cron mails output to the local root mailbox and nobody reads it:

ini
0 2 * * * /usr/local/bin/backup-db.sh >> /var/log/backup-db.log 2>&1
15 4 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx" >> /var/log/cert-renew.log 2>&1
0 3 * * 6 ansible-playbook -i /srv/iac/hosts.ini /srv/iac/patch.yml >> /var/log/ansible-patch.log 2>&1

The first field is the minute, then hour, day of month, month, and weekday. The third line is the interesting one: instead of a copy-paste shell script per machine, the job is an Ansible playbook stored in version control, so the chore itself is code. Every scheduled thing should point at a script or playbook that is idempotent, because a missed run on a crashed night will be re-run later whether you planned for it or not.

The Modern Route: systemd Timers

Systemd timers replace cron's old machinery with unit files, and they add two practical wins: Persistent=true runs a missed job after a reboot, and OnBootSec helps you stagger jobs across a fleet:

ini
[Unit]
Description=Nightly database backup

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target
bash
# ship the service and timer units, then activate
sudo cp backup-db.{service,timer} /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now backup-db.timer
systemctl list-timers

RandomizedDelaySec spreads the start within a window, which matters when fifty instances would otherwise hit the backup server at the same second. The timer fires the matching backup-db.service unit, so per-service logging and status reporting come from the usual systemctl mechanisms.

Chore Discipline That Scales

Three habits keep scheduled automation trustworthy as the fleet grows:

  • **Every job writes a log you actually watch** — a spare disk that is quietly full is a backup that silently did not happen.
  • **Converge, then schedule** — run the same playbook manually after a failed run; the playbook should repair, not append a second copy of a line.
  • **Alert on absence, not just failure** — if the 2 AM job simply never fires, a missing log entry is your only clue, so a heartbeat check beats any wall of logs.
A day of scheduled chores 02:00 03:00 04:15 backup-db.sh log: backup-db.log ansible patch.yml log: ansible-patch.log certbot renew then reload nginx logs + heartbeat check absence of a log entry is itself an alert

Takeaway

Automate the chores, log everything, and make each job its own small idempotent playbook rather than a growing root shell script. Start with one job — your nightly backup — and let the pattern spread until the weekend patch pass is the least exciting thing on your calendar. Netbay instances make ideal test subjects for this: provision a box, wire a timer, break it, and re-provision on netbayhosts.in without anyone noticing.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance