How to Use flock with Cron to Prevent Concurrent Script Execution in Linux


2 views

When scheduling scripts via cron that shouldn't run concurrently, flock provides an elegant solution. The lock file management is automatic - you don't need to manually delete lock files as the kernel releases them when:

  • The holding process terminates
  • All file descriptors to the lock are closed

The magic happens at the kernel level through advisory file locking. The empty lock file serves as a synchronization point. Here's what happens under the hood:

1. First process opens/creates the lock file
2. Acquires an exclusive lock (fcntl-style)
3. Subsequent processes block or fail (depending on -w/-n options)
4. Kernel automatically releases lock when process exits

For your cron use case, here are three robust implementation patterns:

# Basic blocking (wait indefinitely)
*/20 * * * * root flock /var/cron.lock -c "/usr/bin/myscript"

# Non-blocking (fail if locked)
*/20 * * * * root flock -n /var/cron.lock -c "/usr/bin/myscript || echo 'Script locked'"

# Timeout version (wait 60 sec max)
*/20 * * * * root flock -w 60 /var/cron.lock -c "/usr/bin/myscript"

For your specific startup race condition, consider this improved approach:

# In upstart config
exec flock -n /var/cron.lock /usr/bin/myscript

# In crontab
*/20 * * * * root flock -w 0 /var/cron.lock /usr/bin/myscript

To verify locking is working:

# Check lock status
lslocks | grep cron.lock

# Alternative method
fuser -v /var/cron.lock

# Test without cron
flock -x /var/cron.lock -c "sleep 30" &
flock -n /var/cron.lock echo $?  # Should return 1
  • Lock files should be on persistent storage (not /tmp)
  • Set appropriate permissions (usually 644)
  • Consider symbolic links for lock file management
  • For network scenarios, use NFSv4+ (supports proper file locking)

When using flock with cron jobs, the lock file (/var/cron.lock in your example) doesn't require manual cleanup. The kernel automatically releases the lock when either:

  1. The process holding the lock terminates (even if crashes)
  2. The file descriptor is closed

The magic happens at the kernel level through advisory locking. Here's what occurs during execution:

# When your cron runs:
flock -w 0 /var/cron.lock /usr/bin/myscript

# Internally:
1. Opens /var/cron.lock (creates if nonexistent)
2. Attempts to acquire an exclusive lock (blocking with -w 0)
3. If successful, executes myscript while maintaining lock
4. Releases lock automatically upon script completion

To check which process holds the lock (Linux-specific):

sudo lslocks | grep '/var/cron.lock'
# Or for real-time monitoring:
sudo watch -n 1 'lsof /var/cron.lock'

For your upstart+cron scenario, consider this enhanced approach:

# In upstart config:
exec flock -n /var/cron.lock -c "/usr/bin/myscript --startup"

# In crontab:
*/20 * * * * root flock -n /var/cron.lock -c "/usr/bin/myscript --cron"

Commonly used parameters:

-n, --nonblock  # Fail immediately if locked
-w, --wait      # Seconds to wait (0 means fail immediately)
-E, --conflict-exit-code # Custom exit code when cannot acquire lock
-o, --close     # Release lock after fork/exec

Here's a complete implementation:

#!/bin/bash
LOCK_FILE="/var/lock/dbbackup.lock"

(
  flock -n 9 || exit 1
  
  # Critical section
  pg_dump -U postgres mydb > /backups/db_$(date +%F).sql
  gzip /backups/db_*.sql
  
) 9>${LOCK_FILE}

# Lock released automatically here
  • Permission issues: Ensure cron user has write access to lock file directory
  • NFS warning: Avoid using flock on network filesystems
  • Stale locks: Kernel cleans them up, but you can add flock -n || rm -f /var/cron.lock as fallback

Instead of wrapping in cron, embed locking directly:

#!/bin/bash
LOCKFILE=/tmp/myscript.lock

exec 200>"$LOCKFILE"
flock -n 200 || {
  echo "Script already running"
  exit 1
}

# Main script logic here
sleep 10

# Lock released when script exits