Configuration settings for WPEngine?
-
Just added Action Scheduler to a custom plugin on a WPEngine hosted site.
We’ve scheduled a task to run every minute, but it’s averaging 120 seconds between runs going anywhere from 1 to 3 minutes.
What we’ve tried: A) WPE’s alt cron and B) disabling WPE’s alt cron but having a monitoring service hit wp-cron.php every minute. Still, we’re getting 120 second average.
Also: A) Default 30 second action_scheduler_queue_runner_time_limit and B) a 60 second time limit since WPE enforces a 60 limit.
Lastly, I reached out to WPE support and they reassured me that they see their cron running every minute and firing the action_scheduler_run_queue hook every minute. Thus, they suggested asking the plugin developer. Any hints for setup on WPEngine?
The job in question typically runs for 5 seconds:
2026-09-17 21:20:06 +0000
action created
2026-09-17 21:20:11 +0000
action started via WP Cron
2026-09-17 21:20:16 +0000
action complete via WP Cronexample with delay
2026-09-20 19:49:14 +0000
action created
2026-09-20 19:51:08 +0000
action started via WP Cron
2026-09-20 19:51:13 +0000
action complete via WP Cron
-
Hi @vivalex
You’ve already ruled out the cron trigger itself, and WP Engine is right that it fires every minute. The 120-second average comes from how interval actions get their next run time.
If you scheduled this with
as_schedule_recurring_actionand a 60-second interval, here is what happens. When the action finishes, Action Scheduler creates the next one right then and sets it due at that moment plus 60 seconds. Your job takes about 5 seconds, so a run that starts on one cron tick makes the next action due 65 seconds later. The next tick comes 5 seconds too early, finds nothing due, and the action waits for the one after. Every second tick gets skipped, which lands you at 120 seconds. The “action created” line in the log is the creation moment, not the due time, so the action in your delayed example was due at 19:50:14 and ran 54 seconds late, not 114.If you want it keyed to the clock instead, schedule it as a cron action:
as_schedule_cron_action( time(), '* * * * *', 'your_hook' );The next run is always the next minute mark, no matter when the previous one finished. I tested both side by side on a local site with a 5-second job, and the queue runner ticked once a minute. The interval version ran every 121 seconds, the cron version ran once every minute. One tick in my test came up empty and gave a 179-second gap, which is where your 3-minute outliers most likely come from. The time limit filter won’t change any of this. It only caps how long one batch is allowed to run.
If you need tighter timing than one run per minute, the WP CLI runner is the route, triggered by a server cron if your host lets you set one up: https://actionscheduler.org/wp-cli/
Which function are you using to schedule the task at the moment? If it’s
as_schedule_recurring_action, the swap above should be all it takes. Post what you see after the change, and we can go from there.Hi Shameem,
Thank you for such a thorough reply, using
as_schedule_cron_actionis just the ticket. With this approach, is there any chance that ‘your_hook’ will be invoked again though the first invocation’s code is still running? With the sequential approach, this seems impossible, but with cron, if the job gets stuck and starts to run for 10 minutes instead of 5 seconds, is it possible that multiple jobs will run at the same time? If so, what setting should I put into place to ensure only one can run at a time?We are using the default, 1 queue. Does that ensure only one job will run at a time? Sorry if I’m repeating myself, it’s very important to our system.
Hi @vivalex
No need to apologize, this is the right thing to check before your system depends on it.
if the job gets stuck and starts to run for 10 minutes instead of 5 seconds, is it possible that multiple jobs will run at the same time?
Not on its own. Action Scheduler only creates the next cron run after the current one finishes, the same as the interval version.
The exception is a run that lasts longer than 10 times the queue time limit, which is 10 minutes with your 60-second setting. Action Scheduler then treats it as dead and marks it failed. If your code schedules a new copy because nothing is pending, it starts while the old one keeps going, and the old one still schedules its own next run when it finishes. I reproduced that locally with a shortened limit and ended up with two schedules for one hook.
We are using the default, 1 queue. Does that ensure only one job will run at a time?
Yes, for everything short of that exception. The one queue is a limit for the whole site, not per hook. While your job runs, every other queue run sees it in progress and skips. I tested this with a 150-second job on a local site, and the next run started only after the job ended.
what setting should I put into place to ensure only one can run at a time?
Raise that limit above your longest possible run:
add_filter( 'action_scheduler_failure_period', function () {
return 900;
} );It only comes into play when a run can go past the 60-second cap, like with the WP-CLI runner from my earlier reply.
On WP Engine, keep one more thing in place. If PHP stops a WP Cron run at the 60-second limit you mentioned, Action Scheduler marks it failed and doesn’t create the next run, so the schedule stops. Can you please keep an
as_has_scheduled_action( 'your_hook' )check in front of youras_schedule_cron_actioncall on every load? That picks it back up after a killed run.Hey all! On that last item (about scheduling of recurring actions and ensuring they remain scheduled) — you might also be interested in the following documentation:
actionscheduler.org/usage/#ensuring-recurring-actions-remain-scheduled
The key detail being that, “The
action_scheduler_ensure_recurring_actionshook provides a reliable opportunity to check and reschedule the action if necessary.” The benefit of using the hook is that the work of checking if the recurring or cron action is scheduled doesn’t have to happen on every request.Hi Shameem,
Thank you for flagging that exception. I can see how that would lead to duplicate schedules. In my own testing, I actually saw duplicate schedule WITHOUT evidence of a long running job. What could cause that?? I can share data/logs, just let me know what to pull for you. It just doubled out of no where:
send_automated_comms
ID: 8458 Complete 2026-10-06 09:55:00 +0000
(1 day 13 hours ago)
2026-10-06 09:54:14 +0000
action created
2026-10-06 09:57:11 +0000
action started via WP Cron
2026-10-06 09:57:17 +0000
action complete via WP Cron
send_automated_comms
ID: 8457 Complete 2026-10-06 09:55:00 +0000
(1 day 13 hours ago)
2026-10-06 09:54:08 +0000
action created
2026-10-06 09:57:05 +0000
action started via WP Cron
2026-10-06 09:57:11 +0000
action complete via WP Cron
send_automated_comms
ID: 8456 Complete 2026-10-06 09:54:00 +0000
(1 day 13 hours ago)
2026-10-06 09:54:02 +0000
action created
2026-10-06 09:54:08 +0000
action started via WP Cron
2026-10-06 09:54:14 +0000
action complete via WP CronNo evidence of going past the 60 sec cap.
You suggested increasing
action_scheduler_failure_periodbut I cannot know how long the job will run for. Especially since I suspect glitches with WPEngine’s cron to be the cause here.I haven’t seen the “picks it back up after a killed run” problem you mentioned.
Can you recommend a code pattern that addresses these edge cases? Other than setting a 60 sec cap, my code right now does:
// add_action: action_scheduler_init
if (false === as_next_scheduled_action(HOOK)) {
as_schedule_cron_action(time(), "* * * * *", HOOK, ...);
}Barry suggested https://actionscheduler.org/usage/#ensuring-recurring-actions-remain-scheduled – will eliminate the duplicating? Or simply address the “picks it back up after a killed run”?
The chief role of the
action_scheduler_ensure_recurring_actionshook is ensuring a recurring action remains scheduled without having to test for its existence on every request (which is otherwise quite a common pattern).This can however lead to a positive secondary effect in the sense that it reduces how often certain race conditions can occur. Consider the snippet you shared: unless you have other guards in place, two or more concurrent requests may run this code at the same time and the condition (in the if statement) could be satisfied in both cases. That would lead to duplication. To further reduce the potential for this, consider specifying that the action should be unique (see these docs).
-
This reply was modified 1 week ago by
Barry.
Barry – the race condition with the above approach would be that a run and a delayed run would both call
as_next_scheduled_action(HOOK)at the same time & thus schedule duplicate actions? That seems (to me) like it would be rare enough it would not explain what I saw a duplication on my server after just a few days. But what do I know: it is in fact happening somehow!I re-read the usage documentation and will try this approach: replace the on action_scheduler_init hook (since that runs all the the time) with
register_activation_hook(that should just do it once) and add the schedule call toaction_scheduler_ensure_recurring_actions.// added toregister_activation_hook
if (false === as_next_scheduled_action(HOOK)) {
as_schedule_cron_action(time(), "* * * * *", HOOK, ...);
}
// add_action: action_scheduler_ensure_recurring_actions
if (false === as_has_scheduled_action(HOOK)) {
as_schedule_cron_action(time(), "* * * * *", HOOK, ...);
}Added unique=true as well, just for good measure.
Does that look like it’ll never have duplicate actions?
Edit: Just found https://github.com/woocommerce/action-scheduler/issues/698 so I see this is a document & an ongoing issue. Other than
unique: true, what’s the current start-of-the-art for avoiding duplication?That a run and a delayed run would both call
as_next_scheduled_action(HOOK)at the same time & thus schedule duplicate actions? That seems (to me) like it would be rare enough it would not explain what I saw a duplication on my server after just a few days.For sure, in your case it may be exceedingly rare for this particular race condition to be exercised (bear in mind we don’t know too much about where or when the code you shared is running, or how much traffic your site might be fielding, etc), but this is a known source of duplication.
Regarding runs and delayed runs, I want to clarify that a snippet like this…
add_action( 'action_scheduler_init', function () {
// Code to conditionally (re-)schedule a recurring action
} );…will run on all requests, including unrelated frontend requests by regular users, and not just ‘runs’ (I’m assuming by runs you meant invocations of the queue runner, apologies if I misunderstood). Unless, of course, you have other protections around it.
Other than
unique: true, what’s the current start-of-the-art for avoiding duplication?So in terms of what Action Scheduler gives you, there are two things you are already aware of:
- Using the
action_scheduler_ensure_recurring_actionshook - Specifying that the action to be created should be
unique
I expect for most projects those are more than adequate, however they may not currently be 100% bullet-proof. So, some additional things you might layer on top:
- A lock of your own, within your callback.
- Making your callback idempotent (though I understand this is not always possible).
- You can stop also Action Scheduler from doing any queue processing while inside web requests or via WP Cron, and force it to only do this work from a WP CLI-triggered queue runner. In this way, and in an environment you control, you can ensure there is only ever one active queue runner.
What’s hard for me to know from over here is how viable those strategies might be for you: for instance, limiting Action Scheduler to one active queue runner may not scale well, depending on the volume of work you intend to process.
-
This reply was modified 1 week ago by
You must be logged in to reply to this topic.