flock: Preventing Concurrent Runs with File Locks

flock: Preventing Concurrent Runs with File Locks

What Is flock?

Conclusion: flock is a util-linux command that uses a file-based lock to stop a script or job from running twice at the same time, in a single line.

What you'll learn:

  • The pattern for preventing cron job overlap with flock
  • When to use exclusive / shared / non-blocking / timeout modes
  • A drop-in snippet for self-locking a script

The practical patterns

  • Guard a single command -> flock -n /var/lock/job.lock cmd
  • Guard an entire script -> put the FLOCKER boilerplate at the top
  • Want to wait -> -w seconds; fail immediately -> -n

Assumptions (target environment)

  • flock ships with util-linux and is present on virtually every Linux distro
  • Locks are advisory (cooperative): they only apply between processes that use flock
  • Support on NFS / CIFS is limited (covered below)

Why Prevent Concurrent Runs?

Conclusion: If a backup or batch job restarts while the previous run is still going, you risk data corruption, double processing, and runaway load. flock prevents the overlap structurally.

Suppose a batch job runs every 5 minutes via cron, but one run happens to take longer than 5 minutes. The next start overlaps the previous run, and two processes write to the same file at once. The result is typically one of:

  • Corrupted or interleaved output files and logs
  • Duplicate processing (double emails, double billing)
  • Processes piling up and exhausting memory / CPU

You could manage a PID lockfile yourself, but if the process dies via kill -9 or a power loss, a stale lock remains and future runs never start. With flock, the kernel releases the lock automatically when the process exits, so there is no cleanup to do.

How Do You Use flock?

Conclusion: The basic form is flock lockfile command. The lockfile is created automatically if missing, and the lock is released the moment the command exits.

flock has three syntaxes.

# Form 1: lock a file/directory and run a command
flock /var/lock/mytask.lock command args

# Form 2: run a single command through the shell with -c
flock /var/lock/mytask.lock -c 'command1 && command2'

# Form 3: lock an already-open file descriptor number
flock 200

A minimal example. Run echo while holding an exclusive lock:

flock -x /tmp/myapp.lock echo 'running under lock'
  • The lockfile (/tmp/myapp.lock) is created if it doesn't exist
  • -x requests an exclusive lock (it's the default, so it can be omitted)
  • The lock is released as soon as echo finishes

The lockfile is a "key," not "data." It can be empty, and you never need to delete it. Leaving the file in place causes no problem, because the lock state is tied to the open file descriptor, not to the file's existence.

What Are the Key flock Options?

Conclusion: The four to know are -n (don't wait), -w (time limit), -s (shared), and -E (exit code on failure).

Option Meaning
-x, --exclusive Exclusive (write) lock. Default
-s, --shared Shared (read) lock. Multiple holders allowed
-n, --nonblock Fail immediately instead of waiting (exit status 1)
-w, --timeout sec Wait up to N seconds, then fail. Fractions allowed (-w 0.5)
-u, --unlock Drop the lock explicitly
-E, --conflict-exit-code N Exit code on -n / -w failure (default 1)
-o, --close Close the fd before running the command (child won't hold it)
-c, --command Run a single command through the shell (sh -c)

-w 0 is equivalent to --nonblock (zero seconds of waiting means no waiting).

How Do You Stop a cron Job From Overlapping?

Conclusion: Wrap the cron line in flock -n lockfile. If the previous run is still going, this run exits quietly and no overlap occurs.

This is the most common flock use case. Write the crontab line like this:

# Back up every 5 minutes. Skip this run if the previous one is still going.
*/5 * * * * /usr/bin/flock -n /var/lock/backup.lock /opt/scripts/backup.sh
  • -n means exit immediately on overlap (skip rather than wait)
  • If the previous job has finished, the lock is acquired and the job runs
  • By convention, put the lockfile in /var/lock/ or /run/lock/

If you'd rather wait a little when runs overlap, use -w:

# Wait up to 30 seconds, then give up
*/5 * * * * /usr/bin/flock -w 30 /var/lock/backup.lock /opt/scripts/backup.sh

Inside cron, write flock as an absolute path (/usr/bin/flock) so it works even when PATH is minimal.

How Do You Make a Script Self-Locking?

Conclusion: Put a file-descriptor block or the FLOCKER boilerplate at the top of the script, and it guards itself against concurrent runs no matter how it's invoked.

Instead of writing flock on every cron line, build the lock into the script itself. Two common patterns.

Pattern 1: File descriptor approach

#!/bin/bash
exec 200>/var/lock/myscript.lock
flock -n 200 || { echo "already running"; exit 1; }

# --- everything below runs as an exclusive critical section ---
echo "do work..."
sleep 10
  • exec 200>... opens the lockfile on fd 200 (200 is a convention; any free number from 9-255 works)
  • flock -n 200 locks that fd; if it can't, the || branch exits
  • When the script ends, the fd closes and the lock releases automatically

Pattern 2: FLOCKER boilerplate (from the man page)

#!/bin/bash
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :

# --- everything below always runs under the lock ---
echo "do work..."

This uses the script itself as the lockfile and checks the FLOCKER environment variable to decide whether it has already re-executed through flock. Paste it at the top and self-locking is done.

When you lock the script file itself (flock -en "$0"), it doesn't play well with scripts that rewrite themselves via > (self-updating scripts). It's safer to lock a dedicated file you never overwrite.

Non-Blocking vs Timeout: Which Do You Use?

Conclusion: Use -n if it's fine to drop the overlapping run; use -w seconds if a short wait lets the work proceed. Check the exit code to detect failure.

# Fail immediately (for batches where skipping an overlap is fine)
flock -n /var/lock/job.lock ./job.sh

# Wait up to 10 seconds (for work where a brief contention should be absorbed)
flock -w 10 /var/lock/job.lock ./job.sh

Detect whether the lock failed by checking the exit code. When -n / -w fails, the exit code is 1 by default and can be changed with -E.

flock -n -E 99 /var/lock/job.lock ./job.sh
if [ $? -eq 99 ]; then
    echo "skipped because another process is running"
fi

If the lock succeeds and the command runs, flock's exit code is the command's own exit code. Pick an -E value your command never returns, so you can tell a lock failure apart from a real command result.

What Are the Common flock Pitfalls?

Conclusion: The main traps are no support on NFS, losing the lock when you recreate the data file with >, and the lockfile's location.

2. Recreating the data file drops the lock

The lock is tied to the open file descriptor. If you lock the output data file and later rebuild it with >, the inode changes and the lock becomes meaningless.

# Bad: locking data.txt while recreating data.txt
flock data.txt sh -c '> data.txt; generate >> data.txt'

Lock a dedicated lockfile you never overwrite.

3. flock does not detect deadlock

If multiple locks are taken in a crossing order they can deadlock, but flock itself won't detect it. Keep it to one lock per script and avoid designs that span multiple locks.

Avoid these, and flock becomes a powerful, robust way to shut down concurrent runs in just a few characters.

Summary and Next Reading