Back to tutorials
Tutorial

Fix WordPress Cron Not Running on a VPS

Fix WordPress cron on a VPS by disabling WP-Cron and running wp-cron.php via system cron. Includes wp-cli checks and log verification.

By Anurag Singh
Updated on Nov 03, 2026
Category: Tutorial
Share article
Fix WordPress Cron Not Running on a VPS

Missed scheduled posts, WooCommerce subscriptions that don’t renew, and backups that never start are common signs you need to fix WordPress cron on a VPS. WP-Cron isn’t a real daemon. WordPress only “ticks” cron when something loads the site.

That design creates two problems. Low-traffic sites run late because nothing triggers WP-Cron. High-traffic sites can trigger cron too often and waste CPU.

This guide replaces WP-Cron with a real system scheduler (classic cron or a systemd timer). It also shows how to verify the job runs. The commands work as-is on Ubuntu/Debian. On AlmaLinux/Rocky, expect small user or path tweaks.

Prerequisites (what you need before you start)

  • A WordPress site running on a VPS or dedicated server (root or sudo access).
  • SSH access and the ability to edit wp-config.php.
  • Know your WordPress path (example: /var/www/example.com/public_html).
  • WP-CLI installed (recommended). If you don’t have it, you can still proceed using curl or php.

Predictable scheduled tasks require control over cron, PHP, and logs. That’s much easier on a VPS like a HostMyCode VPS. It also beats working around shared hosting limits.

Why WP-Cron fails on VPS sites (and how to confirm it)

WP-Cron depends on page hits. No hits means no cron.

Even with traffic, caching layers, security rules, and failed loopback requests can prevent WordPress from triggering scheduled events.

Start with a couple quick checks:

  • Look for “missed schedule” behavior: scheduled posts stay in “Scheduled” past publish time, the Action Scheduler queue grows, or plugins show “last run: never”.
  • Check loopback health: in wp-admin go to Tools → Site Health and look for “Loopback request” warnings.

Confirm whether WP-Cron is currently enabled

From your server, go to your WordPress directory. Confirm wp-config.php is there:

cd /var/www/example.com/public_html
ls -la wp-config.php

Then check whether DISABLE_WP_CRON is already set:

grep -n "DISABLE_WP_CRON" wp-config.php || true

If you see define('DISABLE_WP_CRON', true);, WP-Cron is already off. If not, you’ll add it after you set up the system scheduler.

Optional: Inspect WordPress cron events with WP-CLI

If WP-CLI is installed, use it to see what’s due right now:

cd /var/www/example.com/public_html
wp cron event list --due-now --fields=hook,next_run_relative --allow-root

If overdue events keep piling up, WordPress isn’t triggering cron reliably.

Related troubleshooting: if your site throws upstream errors during cron runs, see Nginx 502 Bad Gateway Fix on Ubuntu VPS. If uploads or scheduled import tasks fail with large payloads, check Nginx 413 Request Entity Too Large Fix On VPS.

Fix WordPress cron by replacing it with a real system cron

You’re aiming for two changes. First, stop WordPress from running cron on page hits. Second, run due events on a steady OS schedule.

For most sites, every 2–5 minutes is a good starting point.

Step 1: Choose your cron runner method

  • Best (recommended): WP-CLI: wp cron event run --due-now (runs only due events).
  • Simple fallback: call wp-cron.php via curl (works even without WP-CLI).
  • Direct PHP execution: php /path/to/wp-cron.php (works but can behave differently across setups).

Step 2: Disable WP-Cron inside WordPress

Edit wp-config.php. Add this line above the “stop editing” comment:

define('DISABLE_WP_CRON', true);

Example command using nano:

nano /var/www/example.com/public_html/wp-config.php

Then confirm it’s in place:

grep -n "DISABLE_WP_CRON" /var/www/example.com/public_html/wp-config.php

Step 3A (recommended): Add a WP-CLI cron job

First, confirm where wp lives on your server:

command -v wp
wp --info | head

Next, run WP-CLI as the same user your web server uses. Common values:

  • Ubuntu/Debian (Nginx/Apache): www-data
  • AlmaLinux/Rocky (httpd): apache
  • Some setups: nginx

If you’re not sure, check what’s running:

ps aux | egrep "(php-fpm|apache2|httpd|nginx)" | head -n 5

Now create a dedicated cron file. On Ubuntu/Debian:

sudo nano /etc/cron.d/wordpress-cron

Add a line like this (adjust the user and WordPress path):

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

*/2 * * * * www-data cd /var/www/example.com/public_html && wp cron event run --due-now --quiet

This setup is popular for a reason:

  • Every 2 minutes is responsive without constantly waking PHP.
  • Running as the web user keeps file permissions and cache behavior consistent.
  • --due-now avoids running non-due tasks on busy sites.

On AlmaLinux/Rocky, the same /etc/cron.d approach works. Swap the user to apache if that’s what PHP runs as:

*/2 * * * * apache cd /var/www/example.com/public_html && wp cron event run --due-now --quiet

Step 3B (fallback): Use curl to hit wp-cron.php

If WP-CLI isn’t available, trigger cron with a local HTTP request. This keeps the request on the server. It also avoids relying on external uptime monitors.

Create:

sudo nano /etc/cron.d/wordpress-cron

Add:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

*/2 * * * * www-data curl -fsS "https://example.com/wp-cron.php?doing_wp_cron" >/dev/null

If your site redirects HTTP→HTTPS, stick with HTTPS. If TLS is broken, fix TLS before relying on this method.

Step 4: Prevent overlapping runs (important on slow sites)

On sites with heavy Action Scheduler workloads (WooCommerce, membership plugins, backup jobs), runs can overlap.

Overlaps create duplicated work, lock contention, and avoidable load spikes.

Use flock so only one run can happen at a time:

*/2 * * * * www-data flock -n /tmp/wp-cron.lock -c 'cd /var/www/example.com/public_html && wp cron event run --due-now --quiet'

A common self-inflicted issue is scheduling every minute on an undersized server without flock. You’ll usually see PHP-FPM processes pile up and MySQL CPU climb.

Verify the cron job is running (don’t skip this)

Adding a cron entry takes 30 seconds. Verifying it executes is what fixes missed schedules.

Check cron syntax and permissions

  • File should be owned by root: ls -la /etc/cron.d/wordpress-cron
  • Permissions should not be executable; typical is 644.
  • There must be a newline at the end of the file. Missing newline can break parsing.
sudo ls -la /etc/cron.d/wordpress-cron
sudo tail -n 5 /etc/cron.d/wordpress-cron

Watch cron logs while a run happens

On Ubuntu/Debian, cron messages usually land in syslog:

sudo grep -i CRON /var/log/syslog | tail -n 30

Or follow them live:

sudo tail -f /var/log/syslog | grep -i cron

On AlmaLinux/Rocky, use:

sudo journalctl -u crond -n 50 --no-pager
sudo journalctl -u crond -f

Add a temporary log to confirm WP-CLI executes

If you want proof beyond “cron ran,” log the command output for a few minutes:

*/2 * * * * www-data flock -n /tmp/wp-cron.lock -c 'cd /var/www/example.com/public_html && wp cron event run --due-now --quiet' >> /var/log/wp-cron.log 2>&1

Create the log file and set permissions (Ubuntu/Debian example):

sudo touch /var/log/wp-cron.log
sudo chown www-data:www-data /var/log/wp-cron.log
sudo chmod 640 /var/log/wp-cron.log

Then check it:

sudo tail -n 50 /var/log/wp-cron.log

After you’ve proven it works, remove the extra logging so it doesn’t grow forever.

Trigger due events manually for a clean test

Schedule a post for 2–3 minutes from now. Confirm it publishes on time.

If you run WooCommerce, also confirm the Action Scheduler queue starts draining.

With WP-CLI, you can also run:

cd /var/www/example.com/public_html
wp cron event run --due-now --allow-root

If that command throws errors, your cron job will error too. Fix the WP-CLI/PHP environment before you tune schedules.

Harden cron execution (security + stability)

Cron is part of your production surface area. Treat it like production code.

Keep it predictable, least privilege, and observable.

Run as the least-privileged user

Avoid running WordPress cron as root unless you have no choice. Using www-data/apache reduces the blast radius. It also prevents file ownership surprises.

Keep PHP timeouts realistic

When long-running jobs hit timeouts, you get half-finished backups, stuck queues, and retries that never catch up.

  • For PHP-FPM, check: /etc/php/8.3/fpm/pool.d/www.conf (Ubuntu path; version may differ).
  • Look at request_terminate_timeout and compare it to your real job runtimes.

If you’re guessing, measure first. Netdata makes it obvious when you’re saturating CPU, PHP-FPM, or MySQL. See Run Netdata on a VPS for Live Server Monitoring.

Make sure cron can reach WordPress (loopback and DNS)

If you use the curl method, the server must resolve the domain and reach it locally.

  • Test DNS: dig +short example.com
  • Test HTTP from the server: curl -I https://example.com/wp-cron.php

If you recently moved DNS and behavior feels inconsistent, double-check the migration steps.

This guide helps avoid common propagation pitfalls: Migrate a Domain to Cloudflare DNS Without Downtime.

Common mistakes (and quick fixes)

  • Disabling WP-Cron but forgetting to add a system cron: scheduled tasks stop completely. Fix: add the cron entry first, then disable WP-Cron.
  • Using the wrong WordPress path: WP-CLI fails with “This does not seem to be a WordPress install.” Fix: cd into the directory with wp-config.php.
  • Running WP-CLI as root: creates root-owned files in wp-content. Fix: run as web user; repair ownership: chown -R www-data:www-data wp-content.
  • Overlapping runs: CPU spikes, database lock waits, duplicate work. Fix: add flock and slow the schedule to every 2–5 minutes.
  • Blocking loopback/cron endpoints: security plugins or WAF blocks wp-cron.php. Fix: allowlist local requests or switch to WP-CLI runner.

Optional: Use a systemd timer instead of cron (cleaner on modern Linux)

If your server uses systemd (common on Ubuntu, Debian, AlmaLinux, Rocky), timers can be easier to manage than traditional cron.

Logging is also cleaner. This example runs WordPress cron every 2 minutes using WP-CLI.

Create a service unit

sudo nano /etc/systemd/system/wordpress-cron.service
[Unit]
Description=Run WordPress scheduled tasks (WP-CLI)

[Service]
Type=oneshot
User=www-data
WorkingDirectory=/var/www/example.com/public_html
ExecStart=/usr/local/bin/wp cron event run --due-now --quiet

Create a timer unit

sudo nano /etc/systemd/system/wordpress-cron.timer
[Unit]
Description=Timer for WordPress cron

[Timer]
OnBootSec=2min
OnUnitActiveSec=2min
AccuracySec=10s
Persistent=true

[Install]
WantedBy=timers.target

Enable and verify

sudo systemctl daemon-reload
sudo systemctl enable --now wordpress-cron.timer
sudo systemctl list-timers --all | grep wordpress-cron
sudo systemctl status wordpress-cron.timer --no-pager

Check logs for actual runs:

sudo journalctl -u wordpress-cron.service -n 50 --no-pager

Backup safety: confirm scheduled backups actually complete

A cron fix only helps if your backups finish. Your restore process also has to work.

After cron is stable, do a quick restore test. Restore your database dump into a staging database. Or restore wp-content into a separate directory and confirm the site boots.

If you’re still building your backup routine, HostMyCode’s MariaDB backup guide is a solid baseline: Install MariaDB and Create Secure Backups on Ubuntu.

Summary: the reliable way to keep WordPress schedules running

To fix WordPress cron on a VPS, turn off the hit-driven scheduler. Then run due events from the operating system on a short interval.

WP-CLI plus flock is the most consistent approach.

When you need to troubleshoot, system logs give you a paper trail.

If you want a WordPress setup where you can control cron, PHP-FPM, and performance tuning without shared-host friction, start with a HostMyCode VPS. Or step up to managed VPS hosting if you’d rather offload server maintenance.

If scheduled posts and backups can’t slip, run WordPress where you control the scheduler and can see the logs. A HostMyCode VPS gives you root access for proper cron or systemd timers, and managed VPS hosting makes sense if you want the platform maintained while you focus on the site.

FAQ

Should I run WordPress cron every minute?

Usually no. Start with every 2–5 minutes. If you have time-sensitive WooCommerce tasks, go to every minute but add flock to prevent overlapping runs.

Is it safe to disable WP-Cron?

Yes, as long as you replace it with a real scheduler (cron or systemd timer). Disabling WP-Cron without a replacement stops scheduled work entirely.

WP-CLI works in SSH, but cron fails. Why?

Cron runs with a smaller environment. Fix it by setting PATH in /etc/cron.d/... and using full paths (example /usr/local/bin/wp). Also run it as the correct user.

Can caching stop WP-Cron from firing?

Yes. Full-page caches can reduce “real” page hits or interfere with loopback requests. A system cron calling WP-CLI bypasses most of these issues.

How do I confirm WooCommerce Action Scheduler is working again?

In wp-admin, check Tools → Scheduled Actions and watch the pending queue shrink after your system cron runs. If the queue grows, check cron logs and PHP/MySQL resource limits.

Fix WordPress Cron Not Running on a VPS | HostMyCode