Automation

Stop Using Cron the Hard Way: A Practical Automation Stack

· 5 min read · #tools #writing #automation #personal-automation

Cron is fine. Cron alone is the problem. Here is the small stack I use to turn 'I should run that every morning' into something that actually runs, logs, and tells me when it broke.

The case against raw cronI am not against cron. Cron is a 50-year-old daemon that has run more scheduled work than any tool in computing, and it is going to outlive most of the things I write about. The problem is not cron. The problem is using cron as a stack.When you schedule a script with raw crontab, you get a run schedule and silence. If the script runs and succeeds, silence. If the script runs and fails, silence. If the script does not run at all because the host rebooted, silence. The only feedback is the next time you happen to look at the output file (or, more likely, the customer who expected a thing to happen yesterday and it did not).That is the failure mode. The cron job is fine. The lack of a feedback loop is the bug.The minimum viable automation stackAfter years of running anywhere from 12 to 80 cron jobs on any given host, here is the smallest stack that has held up. Five pieces. Each is boring on purpose.The job itself. A single-purpose script (Python, Bash, Node, whatever). One file. One entry point. Idempotent. (Run it twice in a row, you get the same result, not double work.)The scheduler. Cron, systemd timers, GitHub Actions, a managed scheduler, whatever your platform has. The scheduler is not the system; the scheduler is the trigger.Structured logging. One line per run, machine-readable. JSON is fine. Plain text with a fixed prefix is fine. The point is that you can grep for OK and FAIL and find them.A health check. A single file (health.json, status.txt, whatever) on the host that says "last run was at X, last status was Y, next run is at Z." Updated by the job. Readable by anything that wants to know.An alert path. If the job's last status is FAIL, something needs to wake a human. Email, SMS, a Telegram bot, a Discord webhook, a PagerDuty ping. Pick the channel you will actually check. Set the threshold. (I default to "alert on any FAIL, not on timeouts.")That is the stack. Five things. None of them is new. Together, they make cron survivable.Why idempotency is the actual hard partOf the five, the one most people skip is idempotency. The job is "supposed to run every hour," and so when it runs twice in a row (e.g. because the previous run was slow and overlapped the next), it does the work twice. That is the bug that turns a cron job into a 3am page.Idempotency means: running the job twice in a row produces the same end state as running it once. The classic way is a state file the job writes at the end: state.json with {"last_run_id": "...", "completed_at": "..."}. The job reads the state at the start, decides whether the work is already done, and exits if it is.This is not a thing you bolt on later. If the job is not idempotent from day one, retrofitting idempotency is harder than writing the job in the first place. Make the job idempotent. Then make the scheduler run it whenever.A working exampleHere is a real one. I run a job every morning at 6:30am that scrapes a small list of pages, summarizes each with an LLM, and emails me a digest. The pieces, named:

    scrape_digest.py     # the job
    crontab entry:        # the scheduler
      30 6 * * * /usr/bin/python3 /opt/jobs/scrape_digest.py >> /var/log/digest.log 2>&1
    /var/log/digest.log   # the log
    /opt/jobs/state.json  # the health check
    /var/log/digest.log   # (yes, same file; the last line is the status)
    /opt/jobs/alert.sh    # the alert path: emails me if the log's last line != "OK"

That is 60 lines of code and 7 lines of cron. It has run every morning for 11 months. The three times it has failed, I have known within an hour, not within a week.What to skipSkip Airflow unless you have 10+ jobs. Airflow is a great tool for a team running a lot of jobs. It is also 200 dependencies and a Postgres database to babysit. For a handful of jobs, the five-piece stack above is faster to set up, easier to debug, and easier to delete.Skip writing a custom scheduler. Cron is not the problem. The lack of logging and alerts around cron is the problem. Add logging and alerts. Do not write your own scheduler because you are irritated that cron does not do them.Skip "no-code" automation tools for things that run unattended. Zapier and Make are great for human-in-the-loop workflows. They are not great for things that have to run at 3am without you. For those, you want a real script on a real host. The script is two pages of Python. The no-code tool is a bill that gets larger every month and a flow chart you cannot grep.The fallback planIf the host goes down, the job does not run. That is a fact of cron. The fallback is either: (a) a second host that runs the same job (with a lock file so only one of them actually does the work), or (b) a managed scheduler (GitHub Actions, AWS EventBridge, Google Cloud Scheduler) that runs the job on infrastructure someone else maintains.For personal projects, (b) is almost always the right call. For work projects, (a) is usually right. The five-piece stack works the same way on both; only the trigger changes.The honest versionI built this stack over the course of about four years. The first version was just cron, no logs, no alerts, no health check. I would find out the job had not run for a week when the customer asked why the report was stale. I added the log. I added the health check. I added the alert. Each one took maybe an hour. The first time the alert woke me up for a real failure, I saved what would have been a 9am escalation. The first time alone was worth the four years of building it.If you are starting from scratch, build the full five-piece stack. Do not start with just the cron entry. The cron entry is the cheapest part. The logging and alerting are the parts that pay for the system.Want the prompt pack that does this for blog posts?The Blog Writing Factory is the same five-piece idea (job, schedule, log, health check, alert) applied to writing. Ten prompts, one ZIP, $14.Buy on Gumroad. $14

Send me the rough edges if you try it. I read every message.