
WordPress can look fine on the surface while background tasks fail quietly. Scheduled posts don’t publish. WooCommerce emails arrive late. Backups run at odd hours.
In many cases, the cause is cron. This WordPress cron troubleshooting tutorial helps you confirm wp-cron.php is the culprit. Then it shows how to replace WordPress’s pseudo-cron with a real system cron on a VPS.
The steps below assume you manage your own server (Ubuntu/Debian/Rocky/AlmaLinux) running Nginx or Apache. If you’d rather spend less time on day-to-day ops, managed VPS hosting from HostMyCode is built for WordPress + WooCommerce stacks where background jobs must run on time, not only when traffic shows up.
What this WordPress cron troubleshooting tutorial fixes (and why it matters)
wp-cron.php is traffic-driven. WordPress only checks and runs scheduled events when something hits the site.
On low-traffic sites, that leads to missed schedules. On busy sites, it can spawn repeatedly, pile up PHP workers, and spike CPU.
- Missed schedules: scheduled posts, cache warmups, and plugin jobs don’t run.
- Delayed email: order receipts, password resets, or contact form mail arrives late.
- Resource spikes: concurrent requests trigger cron repeatedly, saturating PHP-FPM/Apache.
- Timeout chains: slow cron blocks other PHP requests, causing 502/504 errors.
If you’re already seeing intermittent gateway errors, pair this guide with HostMyCode’s VPS troubleshooting for 502/504 on Nginx + PHP-FPM. It helps confirm whether cron is the trigger.
Prerequisites and a safe “before you touch anything” checklist
Before you change cron behavior, grab a quick baseline. It makes the “before vs after” clear.
- Confirm you can run WP-CLI: either installed globally or via your hosting stack.
- Record current cron status: list scheduled events and spot backlog.
- Check server time and timezone: clock drift often looks like “missed schedule.”
- Make a quick backup: at least your
wp-config.phpand a database snapshot.
If you need a restore plan first, follow VPS backup restore tutorial and come back here. Cron changes are usually low-risk, but rollback is still your safety net.
Step 1 — Verify it’s a cron problem (not mail, DNS, or TLS)
Cron gets blamed when the real issue is elsewhere. Common culprits include email deliverability, MX routing, and TLS failures.
Rule out the obvious before you change schedules:
- Email arrives, but late: cron is a likely cause.
- Email never arrives: check SPF/DKIM/DMARC and routing first.
- Webhook or API jobs fail: could be TLS or firewall rules.
For email-specific failures, use HostMyCode’s email routing troubleshooting tutorial and email authentication setup guide.
Once delivery is clean, come back and focus on cron timing.
Step 2 — Inspect WordPress scheduled events with WP-CLI
SSH into your server. Then cd into the WordPress docroot and list scheduled events.
Common docroots include /var/www/html, /var/www/example.com/public, or a control-panel-managed home directory.
cd /var/www/example.com/public
wp cron event list --fields=hook,next_run,recurrence --format=table
Scan the output for patterns that match your symptoms:
- Events stuck in the past (the
next_runtime already passed). - Duplicate hooks that shouldn’t be scheduled repeatedly.
- High-frequency jobs running every minute on a small VPS.
To get a quick feel for what fires most often:
wp cron event list --format=csv | head -n 50
If WP-CLI isn’t available, install it (safe on most VPS setups):
curl -L -o /usr/local/bin/wp https://github.com/wp-cli/wp-cli/releases/latest/download/wp-cli.phar
chmod +x /usr/local/bin/wp
wp --info
Step 3 — Confirm whether wp-cron.php is getting hammered
On busy sites, wp-cron.php can run far more often than you expect. This is common when caching doesn’t bypass WordPress early enough.
Nginx access log check (adjust path):
grep -E "wp-cron\.php" /var/log/nginx/access.log | tail -n 50
Apache access log check:
grep -E "wp-cron\.php" /var/log/apache2/access.log | tail -n 50
Dozens of hits per minute from many IPs often means pseudo-cron is being triggered constantly.
Almost none can mean the opposite problem. The site doesn’t get enough traffic to trigger cron reliably.
While you’re checking, look at PHP pressure on the box:
ps -eo pid,pcpu,pmem,cmd --sort=-pcpu | head
On PHP-FPM systems, also check pool settings (common locations):
grep -R "pm\.max_children\|pm\.max_requests\|pm\.status_path" /etc/php/*/fpm/pool.d/*.conf
Step 4 — Fix the most common cause: disable WP pseudo-cron and add a real system cron
The standard production fix is straightforward. Disable WordPress’s traffic-driven cron. Then run due events on a predictable OS schedule.
4.1 Disable pseudo-cron in wp-config.php
Edit wp-config.php and add this line above “That’s all, stop editing!”
define('DISABLE_WP_CRON', true);
Make sure you didn’t add it twice. Duplicate definitions can derail troubleshooting.
4.2 Add a system cron entry (recommended: every 5 minutes)
Set the interval based on what the site actually does. Every 5 minutes is a solid default.
Every minute can make sense for WooCommerce or memberships. Only do that if the VPS has CPU and PHP workers to spare.
Run:
crontab -e
Add one of these (use one, not both):
Option A: WP-CLI (cleaner logs, better control)
*/5 * * * * cd /var/www/example.com/public && /usr/local/bin/wp cron event run --due-now --quiet
Option B: curl/wget to wp-cron.php (works even without WP-CLI)
*/5 * * * * curl -sS https://example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
On a VPS, Option A is usually the better choice. It avoids HTTP/TLS overhead. It also keeps the “did it run?” answer in CLI behavior, not web logs.
4.3 Verify cron is running
On Ubuntu/Debian, cron is usually enabled by default:
systemctl status cron
On AlmaLinux/Rocky, the service is crond:
systemctl status crond
Then force-run due events and confirm the backlog clears:
cd /var/www/example.com/public
wp cron event run --due-now
wp cron event list --fields=hook,next_run --format=table | head -n 30
Step 5 — Stop “missed schedule” errors caused by time drift and timezone mismatch
If the server clock is wrong, or PHP and WordPress disagree on timezone, jobs can “miss” their window.
Check system time:
timedatectl status
Confirm NTP sync is active (Ubuntu typically uses systemd-timesyncd; many servers use chrony):
timedatectl timesync-status || true
systemctl status systemd-timesyncd || true
systemctl status chronyd || true
In WordPress admin, verify Settings → General → Timezone. Use a named timezone (for example, Asia/Kolkata) instead of a raw UTC offset. Offsets won’t handle DST changes correctly.
Step 6 — Find and fix the plugin hook that’s flooding cron
Sometimes cron isn’t failing. It’s drowning.
A common pattern is a plugin that schedules a frequent event (often every minute). The callback is slow or stuck, so the queue grows. Then everything else becomes “late.”
List events and look for very frequent recurrences:
cd /var/www/example.com/public
wp cron event list --fields=hook,recurrence,next_run --format=table | sed -n '1,60p'
Then search your codebase for the hook name:
grep -R "my_hook_name" -n wp-content/plugins wp-content/mu-plugins wp-content/themes 2>/dev/null | head
Once you identify the noisy hook, these are the realistic fixes:
- Update the plugin/theme (cron bugs are often fixed upstream).
- Reduce frequency if the plugin exposes settings (common for indexing, analytics, and digests).
- Disable the feature temporarily to confirm it’s the cause.
If you need to remove a single scheduled event (use carefully):
wp cron event delete my_hook_name
Repeating events may come back on the next page load if the plugin reschedules them automatically. Treat that as confirmation you need a settings change or a schedule patch, not a one-time delete.
Step 7 — Prevent wp-cron.php from being triggered by the public web
After you switch to a real system cron, you don’t need random visitors (or bots) hitting wp-cron.php. It’s usually not a security emergency.
It is extra noise. On a small VPS, it also wastes CPU.
Nginx: block direct access to wp-cron.php (recommended after system cron is in place)
Add this to your server block (often in /etc/nginx/sites-available/example.com or an include used by your WordPress vhost):
location = /wp-cron.php {
deny all;
access_log off;
log_not_found off;
}
Test and reload:
nginx -t && systemctl reload nginx
Apache: restrict wp-cron.php
In the site’s virtual host, add:
<Files "wp-cron.php">
Require all denied
</Files>
Reload Apache:
apachectl configtest && systemctl reload apache2
Important: Don’t block wp-cron.php until your system cron is confirmed working. Otherwise you’ll recreate the “missed schedule” issue immediately.
Step 8 — Fix cron-induced 502/504 errors by limiting concurrency
On some stacks, cron runs can overlap. A long job starts, and the next tick starts another run.
Under load, that can saturate PHP-FPM and briefly take the site down.
A simple lock wrapper prevents overlap without extra tooling. Create a script:
nano /usr/local/sbin/wp-cron-due-now.sh
Paste (adjust path and PHP/WP binary if needed):
#!/bin/sh
set -eu
LOCK=/var/lock/wp-cron-example.lock
DOCROOT=/var/www/example.com/public
WP=/usr/local/bin/wp
# Run at most one instance.
if ! mkdir "$LOCK" 2>/dev/null; then
exit 0
fi
trap 'rmdir "$LOCK"' EXIT
cd "$DOCROOT"
$WP cron event run --due-now --quiet
Make it executable:
chmod 0755 /usr/local/sbin/wp-cron-due-now.sh
Then change crontab to:
*/5 * * * * /usr/local/sbin/wp-cron-due-now.sh
This keeps cron from stacking up. It also reduces the chance of cron-driven 502/504 spikes.
Step 9 — Quick diagnostics for WooCommerce and membership sites
Commerce sites lean heavily on background work. That includes stock sync, payment callbacks, transactional email, cleanup, and retries.
Two checks catch most “it’s slow and weird” cron stories:
- Action Scheduler backlog: WooCommerce uses Action Scheduler tables and jobs. Backlog often looks like delayed emails and stuck refunds.
- Object cache interactions: a misconfigured persistent cache can cause cron locks to hang around.
You don’t need to jump into the database right away. Start by scanning logs for repeated errors from action-scheduler in:
- Nginx:
/var/log/nginx/error.log - Apache:
/var/log/apache2/error.log - PHP-FPM: often
/var/log/php8.3-fpm.logor pool-specific logs - WordPress:
wp-content/debug.log(only if enabled)
If you don’t already review logs regularly, set that up first. HostMyCode’s log monitoring setup guide tutorial gives you a baseline. It also helps catch cron-related issues before customers do.
Step 10 — Validate the fix with a practical test plan
Don’t settle for “seems better.” Run a quick test and keep receipts.
- Create a scheduled post for 5–10 minutes in the future. Confirm it publishes on time.
- Trigger a known email (password reset, test order, form submission) and confirm it sends immediately.
- Watch CPU during cron ticks for 10–15 minutes.
Commands you can use while testing:
# Watch cron service logs (Ubuntu/Debian)
grep CRON /var/log/syslog | tail -n 30
# Watch top CPU consumers
top -o %CPU
If your system uses systemd logging for cron, check:
journalctl -u cron -n 50 --no-pager || journalctl -u crond -n 50 --no-pager
Common pitfalls (the stuff that wastes an afternoon)
- Blocking wp-cron.php before adding system cron: guarantees missed schedules.
- Wrong docroot in cron: WP-CLI runs but targets the wrong site or fails silently.
- Running cron as root with wrong permissions: can create root-owned files inside
wp-content. - Too-frequent cron on a small VPS: every minute isn’t “free” under load.
- Plugin schedules runaway tasks: fixing server cron won’t fix a broken hook.
Where HostMyCode fits: choosing the right hosting for reliable background jobs
Cron problems tend to show up as a site grows. You add plugins, traffic increases, and scheduled work expands.
If you’re hitting resource ceilings, moving from shared hosting to a VPS often helps. You control cron cadence, PHP workers, and caching.
If you want hands-on control, start with a HostMyCode VPS and keep cron and performance tuning in your hands.
If you’d rather offload patching, monitoring, and response work, HostMyCode’s managed VPS hosting is a better fit for stores and client sites where missed jobs quickly turn into support tickets (or lost revenue).
If your WordPress site relies on scheduled work (WooCommerce emails, backups, cache warmups), a VPS with a real system cron makes the difference between “eventually runs” and predictable operations. HostMyCode can provision a VPS sized for your traffic and background workload, or manage it end-to-end with managed VPS hosting and proactive monitoring. If you prefer hands-on control at a lower cost, choose a HostMyCode VPS and apply the cron setup in this guide.
FAQ
Should I run WordPress cron every minute?
Only if you have a clear reason. Start with every 5 minutes, then move to 1 minute for stores or membership sites where delays are customer-visible. After changing it, measure CPU usage and watch PHP worker saturation.
Is it safe to disable WP-Cron?
Yes—if you replace it with a real cron job. If you disable it without a system cron, scheduled tasks will stop running.
Why do I see wp-cron.php called even after disabling it?
Some plugins request wp-cron.php directly, and bots can still hit the URL. Once your system cron is working, blocking direct web access cuts down noise and avoids wasted CPU.
Can I do this on shared hosting?
Sometimes. If your shared host provides a cron manager (like cPanel “Cron Jobs”), you can disable WP-Cron and schedule WP-CLI or a curl call. If you don’t have cron access, you’re stuck with traffic-driven behavior.
What if WP-CLI cron runs fail in crontab but work over SSH?
Crontab runs with a minimal environment. Use full paths (like /usr/local/bin/wp), cd into the docroot, and temporarily log output to a file. That usually exposes missing PATH, missing PHP extensions, or permission issues.
Summary
In 2026, WordPress sites break more often from background job drift than from obvious front-end failures. Disable traffic-driven cron, run due events on a real schedule, prevent overlaps with a lock, and block public access to wp-cron.php once everything is stable.
If you want predictable scheduling, steadier CPU, and cleaner troubleshooting boundaries, run WordPress on a VPS where you control the scheduler and the stack—starting with a HostMyCode VPS.