WP-Cron only runs when someone visits

WP-Cron is not cron. It is a check performed on page load: if a scheduled task is overdue, WordPress fires a loopback request to run it. On a quiet site the “hourly” job runs whenever the next visitor happens to arrive, which may be days.

# wp-config.php
define( 'DISABLE_WP_CRON', true );

# crontab -e — a real schedule, and no loopback request
*/5 * * * * cd /var/www/shop && wp cron event run --due-now >/dev/null 2>&1

The other failure mode is the opposite: on a busy site every request pays the check, and a slow task can fire concurrently because there is no locking worth the name. Moving it to system cron fixes both, and makes the schedule something you can actually reason about. Leaving DISABLE_WP_CRON set without adding the crontab entry means nothing scheduled ever runs again.