flock: The Cron Mutex You're Not Using

Cron will start your job again while the last run is still going. Two lines with flock stop the overlap — and the lock can't go stale.

Share

Cron has exactly one guarantee: it starts your job on time. It has zero opinions about whether the previous run finished. Your nightly sync usually takes four minutes — then one slow night it takes six, and at minute five cron fires a second copy. Two processes now write the same rows, the same files, the same API calls.

This isn't hypothetical. Overlap bugs appear precisely when things are already bad: slow disks, a big batch, a hung upstream. The failure correlates with your worst days.

Why this matters

Every overlapping run is a duplicate-side-effect generator: double emails, double charges, half-written exports, two pg_dumps fighting over one file. And the homegrown fixes are often worse than the bug. PID files go stale after a kill -9 or a reboot, and then the job refuses to run at all. You want a lock that dies with the process holding it — which is exactly what the kernel already knows how to do.

How it works

flock takes an advisory lock on a file. The clever part is what the kernel ties the lock to: the open file descriptor, not the file's contents. When your process exits — cleanly, via SIGKILL, via OOM — the descriptor closes and the lock evaporates. No stale locks, no cleanup scripts.

The CLI wrapper handles simple cases: flock -n /tmp/job.lock ./job runs the command under the lock and exits with status 1 if the lock is already held. The -n flag means "don't wait." For scripts, open a descriptor yourself with exec 9>lockfile, then call flock -n 9. The lock now lives exactly as long as your script, and every child process runs inside it.

Where this helps

  • Overrunning batch jobs — an ETL that occasionally blows past its schedule stops double-inserting rows.
  • Backups — two dump jobs racing to the same output path produce a backup you'll only discover is broken during the restore.
  • Housekeeping — retention, compaction, and cache-warm scripts that must not fight each other.
  • Multi-host cron — put the lockfile on shared storage and several servers running the same job effectively elect a winner.

Watch out

  • The lock is advisory. It only protects code that takes it. A process writing the same data directly will not be stopped.
  • -n skips the run entirely. Correct for idempotent catch-up jobs, wrong where every invocation must happen — use -w 60 to wait up to a minute instead.
  • flock over NFS depends on server support and is historically flaky. Test yours before trusting it across hosts.
  • systemd timers already refuse to start a unit that's still running. If you're on timers, you may have this for free.

Try it yourself

Drop these lines at the top of any cron-run script. Run it in two terminals to watch the second copy exit instantly.

#!/usr/bin/env bash
# crontab: 0 2 * * * /usr/local/bin/nightly-etl.sh
exec 9>/tmp/nightly-etl.lock
if ! flock -n 9; then
  echo "$(date -Is) previous run still active, skipping" >>&2
  exit 0
fi

# everything below can no longer overlap with itself
echo "running etl..."
sleep 300   # simulate a slow job

TL;DR

  • Cron starts jobs on schedule; it never checks that the previous run finished.
  • Overlapping runs mean duplicate writes — and they strike exactly when your systems are slow.
  • Add two lines, exec 9>… and flock -n 9, to every cron-run script today.