Prevent overlapping cron jobs with flock
Use a local lock to skip or wait for overlapping runs, and understand when distributed coordination is required.
Cron normally starts a new process whenever a schedule matches, even if the previous run is still active. On Linux, the util-linux flock command can make cooperating invocations share a lock. Decide first whether an overlapping run should be skipped or should wait.
Skip a run when the lock is held
Provision a private directory such as /var/lib/myjob for the job account, then use one stable lock path:
*/5 * * * * /usr/bin/flock -n /var/lib/myjob/run.lock /opt/jobs/task.sh >> /var/log/jobs/task.log 2>&1
Check the executable location on your distribution. -n makes lock acquisition nonblocking: if another invocation holds the lock, this invocation does not run the command. All entry points, including manual runs, must use the same locking convention.
Do not delete the lock file to “clear” an active lock. Locks apply to the open file, and deleting/recreating its pathname can allow another process to lock a different file. The file can remain after a successful run; its existence does not mean the lock is held.
Record skipped runs explicitly
A wrapper can distinguish lock acquisition from the job's own exit code:
#!/bin/sh
exec 9>/var/lib/myjob/run.lock || exit 1
if ! /usr/bin/flock -n 9; then
printf 'job=task event=lock-unavailable\n' >&2
exit 1
fi
/opt/jobs/task.sh
status=$?
exit "$status"
Here “lock unavailable” can include an acquisition error as well as contention; inspect permissions and filesystem support if it is unexpected. The open descriptor keeps the lock until it closes. Descendant processes that inherit it can keep the lock alive after the wrapper exits, so avoid unintended background work or manage descriptor inheritance deliberately.
Wait only when queuing makes sense
Without -n, flock waits. A bounded wait can use:
flock -w 30 /var/lib/myjob/run.lock /opt/jobs/task.sh
Repeated scheduled arrivals can accumulate waiting processes. A timeout on lock acquisition limits waiting for the lock; it does not limit how long the command runs after acquisition. If a job needs a runtime limit, define a separate termination and recovery policy.
Choose the right coordination boundary
Local locking is suitable for cooperating processes that can reliably lock the same filesystem object. Network filesystem behavior depends on protocol, mount settings and implementation; do not assume a local-path example supplies a distributed lease across hosts.
For distributed jobs, use a scheduler or coordination mechanism designed for that scope. Lease expiry alone is not sufficient when an old worker can keep writing; fencing and idempotent operations may be required. Kubernetes CronJob concurrencyPolicy: Forbid applies to Jobs from that same CronJob, not every equivalent task in a cluster, and does not provide exactly-once execution.
A systemd timer does not start a new instance of its associated service while that unit is already active. That can avoid overlap for that unit, but independently named services or other launch paths still need coordination.
Common questions
Can I use a PID file instead? A naive existence check followed by file creation is racy, and stale PIDs can be reused. Prefer a supported atomic locking mechanism.
Does flock work on macOS? This article uses Linux util-linux flock; it is not a standard macOS command.
What if a skipped run matters? Record the skip and use a queue or catch-up mechanism if every unit of work must eventually execute. A lock prevents overlap; it does not provide durable scheduling.
References
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan