Codebusters

How to Stop a PHP Cron Job Running Twice at Once

Saturday 10 October 2026

A cron job that takes longer than its schedule is a nasty one. If a script runs every minute and one run takes 90 seconds, you now have two copies running at once. Next minute there are three. They can send the same email twice, overwrite each other’s files, or hammer an API until it blocks you. Here is how I stop it in PHP, and I’ve tested every version below.

The fix: a lock file with flock

The idea is that every run tries to grab a lock on a file. If it gets it, it carries on. If another run already has it, it exits straight away. PHP’s flock() does this, and the LOCK_NB flag means “don’t wait, just tell me no”:

$lock = fopen(__DIR__ . '/job.lock', 'c');
if (!flock($lock, LOCK_EX | LOCK_NB)) {
    echo date('H:i:s') . " Previous run still going, skipping\n";
    exit(0);
}

echo date('H:i:s') . " Started (pid " . getmypid() . ")\n";
sleep(5);   // the slow bit
echo date('H:i:s') . " Finished\n";

Here is what happens when I start two copies one second apart (the slow bit is a 5 second sleep):

04:48:05 Started (pid 1899172)
04:48:06 Previous run still going, skipping
04:48:10 Finished

I then went further and started 10 copies at the same moment. With the lock, 1 started and 9 skipped. Without the lock, I used a script that only prints “Started” and sleeps for 2 seconds, and all 10 ran. So that’s 10 copies of your job at once, if you’re unlucky.

What if the script crashes?

This is why I use flock() and not the older trick of creating a file and deleting it at the end. If the script is killed halfway, the file-based way leaves a stale file behind and your job never runs again until you notice. With flock() the lock belongs to the running process, so when the process dies, the lock goes with it.

I tested it by starting the script, killing it with kill -9 after a second (about as rough as it gets), and running it again straight away. It started normally, with no stale lock to clear.

Doing it in the crontab instead

If you’d rather not touch the script, Linux has a flock command that does the same thing. Put it in front of your normal line:

* * * * * /usr/bin/flock -n /home/me/locks/job.lock /usr/bin/php /home/me/scripts/job.php

The -n means “don’t wait”, the same as LOCK_NB. I tried it by holding the lock with a 4 second sleep and running the command again. It didn’t run, and it exited with code 1, so you can detect that if you want to log it.

The path to flock was /usr/bin/flock on my server, but run which flock to check yours. The folder for the lock file needs to exist first, so create it before you try!

Which should you use?

I put the lock inside the script when I want a message in my log saying a run was skipped, as the script above does. I use the crontab version for quick jobs I don’t want to edit. Both do the job. I use the same idea in a couple of my Python scripts too, where it’s fcntl.flock().

The test I’d suggest is the one I used. Add a sleep(5), start two copies, and see which one gives up. If both run, your lock isn’t working.