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


11 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