Daemonizing the WordPress cron.

Over the years, I occasionally come across projects that require a cron job. Be it to send out scheduled emails, import data from external sources, or simply do a daily backup.

As many would know, the best wordpress hosting service has a built in cron system. By default, this runs during each page load allowing the cron to be active without needing to setup a server side cron job. The downside, is that it requires a user to actually load the site, making exact scheduling unreliable if the site isn’t very active at the scheduled time.

In this little “tutorial”, I’ll show how I setup a type of cron daemon that doesn’t require server access or configs.  While not a true daemon, it does give a very similar result.

What is a daemon?

In simple terms, it’s a program running in the background without any user interaction. This is exactly what we need, since we can’t rely on a user loading the site at any given point. So having a program running in the background is an ideal way to ensure a task is done on time.

When you talk about Cron, and Jobs, we are talking about a daemon program called Cron that runs jobs that are defined in a crontab at a given interval. So Cron is a daemon.

Why not use Cron then?

Since Cron is a server based utility, it requires access to the server to set up. Many hosts don’t allow this kind of access or limit the interval times you can set. If you have a schedule that needs to run every 10 min, and the host gives a minimum of 1 hour, then it won’t be sufficient for your needs.

So how do you daemonize the cron?

Quite simply, you make sure the script keeps running. And we do this by making a remote call to itself on script shutdown.

The first step is to check if DOING_CRON is defined. This indicates that wp-cron.php is running, which is the process we want to hook into.

Now that we know when the cron is running, we’ll need to ensure that we only have a single instance running at a time. To do this, we’ll set a transient and check it’s state.

What we now have, is a bit of code that knows when the cron is running and has been run before. What we want to do now, is tell the script to call itself when it’s finished.
For this we’ll use register_shutdown_function.
This function will be called anytime the script exits. This includes errors and timeouts. So it’s fairly certain that the cron will start again regardless.

If we look at wp-cron.php, we’ll see ignore_user_abort(true). This tells PHP to keep going regardless if the browser is closed or disconnected.
The default cron uses a function called spawn_cron() which does a wp_remote_post() to wp-cron.php. It also sets the request to non-blocking and a timeout of 0.01 seconds. This calls the cron script and disconnects as not to keep the visitor waiting. The ignore_user_abort() ensures that the cron will keep running.

We’ll be doing this same thing, but on shutdown. But first, we want to set the transient to false so that it will repeat the cycle. We’ll also add in a small delay to give a little bit of time to allow the transient to save and give you a setting for the cycle time.

Now simply call https://example.com/wp-cron.php?doing_wp_cron to start the daemon.

Taking it further

You’ll also want to add define('DISABLE_WP_CRON', true ); to your wp-config.php to disable the default cron ( spawn_cron() ).

This is just the working code. It obviously doesn’t have controls for starting and stopping, or any form of logging. From here, it can be broken into a plugin, which will allow you to stop it by simply disabling the plugin.

Below is an example of wrapping it into a plugin.

Caveats

Some hosts may limit the amount of visitors, and this method may be counted as a visitor for each call. So it might be seen as a new visitor every second.

As mentioned, this is only the working code and does not have any form of monitoring. So you’ll probably need to build something in to help you keep control of it.

2 Comments

  1. That is a really interesting approach. But why only sleep for 1 second? Seems that would hammer the server, probably needlessly. Why not once ever 60 seconds, or at least 15 seconds?

    • For my uses I needed this kind of resolution. You are correct that 60 seconds would be better and that’s why I mentioned in the code that you can set the cycle time.
      If you do make it 6o though, you might also need to increase the max execution time to cover both the wait and potential tasks.

Leave a Reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: