CronDrive
Guides

How to prevent overlapping cron jobs with flock

Updated 16 October 2026

Cron doesn’t check whether the previous run finished. If a job that runs every 5 minutes takes 7, two copies run at the same time, and they can process the same data twice or block each other. flock, from util-linux, lets only one run at a time.

Skip the run if the previous one is still running

*/5 * * * * flock -n /tmp/sync.lock /path/to/sync.sh

flock takes a lock on the file and runs the command. With -n, if another run holds the lock, it exits right away without running it. The lock is released when the command ends, also if it fails.

  • Use one lock file per job, the same for all of its runs.
  • The crontab’s user must be able to create it. /tmp works for any user; on Debian and Ubuntu, /run/lock too.
  • It only coordinates the runs that share the file. Local lock files on different servers or containers don’t coordinate with each other.

To check it before using it in your job, hold the lock for 30 seconds in one terminal and try again in another:

flock -n /tmp/test.lock sleep 30
flock -n -E 75 /tmp/test.lock echo "it ran"; echo "exit code $?"

While the first one holds the lock, the second ends with exit code 75 without running echo. When the first one ends, the same command prints it ran.

Wait instead of skipping

*/5 * * * * flock -w 120 /tmp/sync.lock /path/to/sync.sh

With -w 120, flock waits up to 120 seconds for the lock. If the previous run ends in that time, this one runs right after; if not, it exits without running. Without -n or -w, it waits as long as it takes, and the runs of a slow job pile up.

Inside the script

When the job is a script of yours, it can take the lock itself:

#!/bin/sh
exec 9> /tmp/sync.lock
if ! flock -n 9; then
  echo "The previous run is still running"
  exit 75
fi

# ... the job

The lock lasts until the script ends, and the crontab line stays as it was.

Tell a skipped run from a failed one

With -n, a skipped run exits with code 1, like many errors. -E sets another code, so that the logs and the alerts can tell them apart:

*/5 * * * * flock -n -E 75 /tmp/sync.lock /path/to/sync.sh

75 is a convention, the code for “temporary failure, try again later”: choose one that your script doesn’t use, so that it only means that the lock was taken. BusyBox’s flock, in Alpine images, has -n but neither -w nor -E: use the form inside the script to choose the code.

Other schedulers

  • A systemd timer doesn’t start its service again while the previous run is still active.
  • A Kubernetes CronJob decides with spec.concurrencyPolicy: Forbid skips the new run, and Replace stops the one still running.

See the runs that overlap

flock prevents the overlap, but it doesn’t tell you how often a job takes longer than the time between runs. With CronDrive’s calls in the line, the history shows when a run starts while the previous one is still running, and the job can alert on it.

Put flock around the command, between the two calls. The start call comes before flock, so CronDrive records every attempt, also the ones that flock skips: the history can show an attempt that overlapped while only one copy of the script ran. That attempt ends with code 75, so it shows up as failed with code 75, apart from the real failures.

*/5 * * * * curl -fsS -m 10 https://job.crondrive.com/<token>/start/$$; flock -n -E 75 /tmp/sync.lock /path/to/sync.sh; curl -fsS -m 10 https://job.crondrive.com/<token>/end/$?/$$

CronDrive emails you, or posts to Slack, when a job with this schedule fails, or doesn't start or finish on time. Free for up to 15 jobs.

What you'd see with this schedule

An example with the last runs of this schedule and made-up results: one failed and one didn't run.

Times in UTC.

Due
Saturday 17 October 2026
01:30 On time Started 01:30, finished 01:31, took 1 min.
01:25 Didn't run Alerts sent at 01:26 and 01:29.
01:20 On time Started 01:20, finished 01:22, took 2 min.
01:15 Failed with code 1 Alert sent at 01:15. Started 01:15, finished 01:15, took <1 min.
01:10 On time Started 01:10, finished 01:11, took 1 min.
01:05 On time Started 01:05, finished 01:07, took 2 min.

The alert of the failed run

Email from CronDrive, Sat 17 Oct, 01:15

Failed (exit code 1): "Your job"

The job "Your job" failed with exit code 1.

If you connect Slack, the same message goes to your channel.

The change in your crontab

*/5 * * * * curl -fsS -m 10 https://job.crondrive.com/uG2Exw/start/$$; /path/to/job.sh; curl -fsS -m 10 https://job.crondrive.com/uG2Exw/end/$?/$$

Two calls around your command, in the same line. Your job's line comes with its own token.

Monitor this schedule