
WordPress “cron” isn’t a real cron. By default, it runs only when someone loads a page. That works on low-traffic brochure sites. On a busy VPS, it can cause late scheduled posts, backed-up WooCommerce queues, and CPU spikes.
This WordPress cron job tutorial shows how to disable WP-Cron and replace it with a real server cron. You’ll run it on a fixed schedule. You’ll also write output to a log you can review during incidents.
What you’ll build (and why it fixes real hosting problems)
You’ll make three small changes that fit VPS and dedicated servers:
- Disable WP-Cron’s “run-on-page-load” behavior so WordPress stops firing background requests when traffic arrives.
- Add a real cron job that triggers
wp-cron.phpevery 1–5 minutes (you choose the interval). - Write cron output to a log so you can confirm it’s running and spot errors quickly.
This matters if you host client sites, run WooCommerce, manage memberships, or sell hosting. Reliable scheduling reduces support tickets. It also improves performance and speeds up incident diagnosis.
If you’re moving from shared hosting to a VPS, do this early. It prevents the classic “scheduled email didn’t send” surprise after launch.
Prerequisites checklist (VPS, shared hosting, and control panels)
- WordPress admin access (or file access) to edit
wp-config.php. - Server access: SSH on a VPS/dedicated server, or a control panel (cPanel/WHM, Plesk, DirectAdmin) with Cron Jobs.
- Know your WordPress path (example:
/var/www/example.com/public_htmlor/home/user/public_html). - PHP CLI available (common on VPS/cPanel; custom stacks may have multiple versions installed).
If you’re on shared hosting and can’t create cron jobs, you can’t complete the full swap. That limitation is a common reason business sites move to a VPS.
HostMyCode’s HostMyCode VPS plans are a practical next step when you need control over cron, caching, firewall rules, and logs.
Step 1: Confirm WP-Cron is the culprit (quick diagnostics)
Before you change anything, look for common WP-Cron fingerprints:
- Scheduled posts publish late, or only after someone visits the site.
- WooCommerce orders sit in “pending payment” longer than expected; webhooks/actions feel delayed.
- Burst traffic to
admin-ajax.php/wp-cron.phpin your access logs. - Emails send in clumps instead of a steady trickle.
On Nginx, check the access log for frequent cron hits or spikes:
sudo grep -E "wp-cron\.php|admin-ajax\.php" /var/log/nginx/access.log | tail -n 50
On Apache, try:
sudo grep -E "wp-cron\.php|admin-ajax\.php" /var/log/apache2/access.log | tail -n 50
If the VPS is struggling across the board, stabilize your baseline first.
These pair well with VPS performance troubleshooting and disk pressure checks from disk space troubleshooting.
Step 2: Disable WP-Cron safely (one-line change)
Open wp-config.php and add this line above /* That's all, stop editing! */:
define('DISABLE_WP_CRON', true);
Typical paths:
- Nginx/Apache on VPS:
/var/www/example.com/public_html/wp-config.php - cPanel:
/home/username/public_html/wp-config.php
From SSH, you can do:
cd /var/www/example.com/public_html
sudo nano wp-config.php
Pitfall: This does not remove scheduled events. It only stops WordPress from attempting cron runs during page loads.
In the next steps, you’ll trigger cron from the server.
Step 3: Choose your cron method (curl/wget vs PHP CLI)
You have two solid choices. Choose one and stick with it.
- HTTP trigger (curl/wget): calls
https://example.com/wp-cron.php?doing_wp_cron. It’s easy to paste into a panel. It also depends on HTTP, DNS, and SSL. - PHP CLI trigger: runs
php /path/to/wp-cron.php. It’s usually faster and avoids the web stack. You must use the correct PHP binary.
On most VPS setups, PHP CLI is the clean default.
In some shared or heavily managed environments, the HTTP trigger is simpler to maintain.
Step 4A: Set up a server cron using PHP CLI (recommended)
First, identify the PHP CLI binary you want cron to use:
php -v
which php
On multi-PHP systems (cPanel/CloudLinux, some custom stacks), you may see versioned binaries like /usr/bin/php82 or /opt/cpanel/ea-php83/root/usr/bin/php.
If you’re not sure which one matches the site, check the site’s PHP version in the control panel. Then use the equivalent CLI binary.
Create a log directory that isn’t web-accessible. Good options include a VPS log directory under /var/log, or a cPanel path outside public_html:
sudo mkdir -p /var/log/wordpress
sudo chown root:adm /var/log/wordpress
sudo chmod 750 /var/log/wordpress
Now edit root’s crontab. You can also use the site user’s crontab if you prefer.
Root is fine as long as permissions allow reads.
sudo crontab -e
Add a job that runs every 5 minutes (a good default for most sites):
*/5 * * * * /usr/bin/php -d detect_unicode=0 /var/www/example.com/public_html/wp-cron.php >> /var/log/wordpress/example.com-cron.log 2>&1
Adjust the paths:
/usr/bin/php→ your PHP CLI path/var/www/example.com/public_html→ your WordPress path- log file name per site to keep things readable
Frequency guidance:
- Busy WooCommerce/membership sites: every 1 minute
- Typical business sites: every 5 minutes
- Low-traffic blogs: every 10 minutes
If you want every minute:
* * * * * /usr/bin/php /var/www/example.com/public_html/wp-cron.php >> /var/log/wordpress/example.com-cron.log 2>&1
Why logs matter: “Cron is set” won’t help during an incident. A log that stays clean for 24 hours will.
When it breaks, the log should show the exact error.
Step 4B: Set up a server cron using curl (simple and panel-friendly)
If you prefer an HTTP trigger, use a cron like this:
*/5 * * * * /usr/bin/curl -fsS "https://example.com/wp-cron.php?doing_wp_cron" >> /var/log/wordpress/example.com-cron.log 2>&1
Notes that save time later:
-fmakes curl fail on HTTP 4xx/5xx.-sSkeeps logs quieter while still printing errors.- If your site uses basic auth or IP allowlists, curl must be permitted.
Pitfall: DNS issues, an expired SSL cert, or outbound HTTP blocks can stop this cron. Without logging, it looks like “nothing happened.”
Keep the log.
Step 5: cPanel, Plesk, and DirectAdmin placement (where to paste the cron)
cPanel: cPanel → “Cron Jobs” → add a new cron entry. Use the PHP CLI command if available. On many cPanel servers, you’ll use an EA-PHP binary path.
Plesk: Tools & Settings → Scheduled Tasks (or per-domain Scheduled Tasks). Use the PHP CLI path Plesk provides for the subscription.
DirectAdmin: User Level → Cron Jobs. Add the command and set timing.
If you manage multiple client sites, pick a default interval (5 minutes is common). Document exceptions.
Consistency makes support and audits faster.
Step 6: Verify it actually runs (don’t trust assumptions)
After you save the cron entry, wait 5–10 minutes. Then inspect the log:
sudo tail -n 50 /var/log/wordpress/example.com-cron.log
An empty log isn’t automatically bad. Many runs produce no output.
What you don’t want are PHP fatals, permission errors, or missing-file messages.
Also confirm WP-Cron is no longer being kicked off by visitors. In your access logs, you should see fewer random cron hits.
For an end-to-end test, schedule a WordPress post for 2 minutes in the future. With server cron in place, it should publish on time even if nobody visits the site.
Step 7: Fix common failures (real-world troubleshooting table)
| Symptom | Likely cause | Fix |
|---|---|---|
| Log shows “Could not open input file” | Wrong WordPress path | Use the full path to wp-cron.php; verify with ls -la |
| PHP fatal errors only in cron | Different PHP version via CLI | Use the correct PHP binary (EA-PHP/alt-php); match site version |
| Permission denied writing logs | Log directory not writable | Write logs to a path the cron user can write to (or adjust ownership) |
| Cron runs, but WooCommerce actions still lag | Site cache/objects or plugin queue issues | Check Action Scheduler status; reduce interval to 1 min if needed |
| HTTP cron returns 403/401 | WAF/basic auth/allowlist blocking | Allow server IP or switch to PHP CLI cron |
If firewall rules are too strict, cron breaks. That’s especially true with the HTTP method.
To review baseline rules without locking yourself out, follow this VPS firewall setup guide.
Step 8: Reduce noise and abuse (block public wp-cron.php hits)
Once server cron is running, you can reduce public requests to wp-cron.php.
The goal is simple. Your cron calls it; bots don’t.
Nginx example (inside your server block):
location = /wp-cron.php {
allow 127.0.0.1;
deny all;
}
This works only if your cron uses PHP CLI. If you use curl over HTTP, allow your server’s public IP instead.
Or skip this block.
Apache example (in the virtual host or an include, not always safe in .htaccess):
<Files "wp-cron.php">
Require ip 127.0.0.1
</Files>
Pitfall: If you’re behind a reverse proxy or CDN, don’t add allow/deny rules until you understand how the real client IP is passed through.
If you run an Nginx front-end, configure Real IP first. That keeps logs and allowlists accurate (see this reverse proxy setup tutorial).
Step 9: Make cron predictable for client sites (operational checklist)
- Standard interval: set most sites to every 5 minutes; document exceptions.
- Per-site log: one log file per domain; rotate logs monthly or with logrotate.
- After migrations: re-check cron paths and PHP binary. Migrations often change directory layouts.
- After PHP upgrades: confirm the CLI binary used by cron still exists.
- Watch for “task storms”: if a plugin schedules thousands of actions, cron can amplify load.
If you move sites often, add this to your cutover checklist.
HostMyCode’s migration service helps reduce post-move issues like broken paths, wrong PHP versions, and scheduled tasks that quietly stop.
Step 10: Optional hardening for VPS and dedicated servers (small changes, big support savings)
Two extra steps help prevent cron from turning into “mystery load” later:
- Protect SSH and admin access so attackers can’t plant cron-driven malware. If you use cPanel, pair this with SSH key-only access to cut brute-force noise quickly.
- Keep snapshots before big plugin changes. Cron-heavy plugins (booking, membership, automation) can change workload overnight. For VPS snapshots, see this snapshot tutorial.
Summary: your new “real cron” baseline (copy/paste)
If you want the safe, minimal recipe:
- Add
define('DISABLE_WP_CRON', true);towp-config.php. - Add a cron job (every 5 minutes):
*/5 * * * * /usr/bin/php /path/to/public_html/wp-cron.php >> /var/log/wordpress/site-cron.log 2>&1 - Check the log after 10 minutes, and again after any migration or PHP upgrade.
On a VPS, this removes random background triggers. It replaces them with one auditable schedule.
Support gets simpler, and CPU behavior stays calmer during traffic spikes.
If you want WordPress scheduling you can predict, logs you can review, and the server controls WP-Cron can’t offer, start with a HostMyCode VPS. If you’d rather avoid day-one server setup and keep your time on the site, managed VPS hosting is the cleaner fit for business-critical WordPress.
FAQ
Will disabling WP-Cron break my site?
No, as long as you add a server cron to trigger wp-cron.php. Without the server cron, scheduled posts and background tasks may stop running.
How often should I run the WordPress cron job?
Every 5 minutes is a solid default. For WooCommerce stores with frequent background actions, every 1 minute is common and usually safe on a properly sized VPS.
Should I use curl or PHP CLI?
Prefer PHP CLI on a VPS or dedicated server. It avoids HTTP overhead and doesn’t depend on DNS/SSL for the trigger. Use curl when a control panel makes CLI paths hard to manage.
After a migration, why did cron stop working?
Most failures come from changed file paths or a different PHP binary. Re-check the WordPress directory path and confirm the CLI PHP version matches the site.