قراءة 2 دقيقة

Disable WP-Cron across every WordPress site on cPanel

An idempotent bash script that disables WP-Cron across every WordPress install on a cPanel server and replaces it with staggered system cron entries.

Disable WP-Cron across every WordPress site on a cPanel server

When wp-cron.php is stacking and PHP-FPM is melting, you do not want to hand-edit thirty wp-config.php files. This script does it fleet-wide, idempotently, with a dry-run flag and a stagger so the replacement system crons do not all fire on the same minute.

The script

Drop it at /usr/local/sbin/disable-wp-cron-fleet.sh, chmod 750, and run as root.

#!/bin/bash
# Disable WP-Cron across every WordPress install under /home/*/public_html
# (and addon docroots), then schedule a system cron with a per-site
# stagger so wp-cron runs every 15 minutes, not on every page hit.
#
# Idempotent: re-running adds nothing; removing the marker re-applies.
# Dry-run: pass --dry-run to print actions without changing anything.
 
set -euo pipefail
 
DRY_RUN=0
LOG=/var/log/disable-wp-cron-fleet.log
MARKER='// SGUARD: WP-Cron disabled by disable-wp-cron-fleet.sh'
 
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=1
 
log() { echo "[$(date -Is)] $*" | tee -a "$LOG"; }
 
run() {
  if [[ $DRY_RUN -eq 1 ]]; then
    log "DRY-RUN: $*"
  else
    log "RUN: $*"
    eval "$@"
  fi
}
 
stagger_minute() {
  # Stable per-site minute in [0,14] derived from the cPanel user.
  local user=$1
  printf '%d' $(( $(echo -n "$user" | cksum | awk '{print $1}') % 15 ))
}
# Iterate cPanel users from /etc/trueuserdomains so we skip system
# users and reseller sub-accounts cleanly.
while IFS=: read -r domain user; do
  user=${user# }
  home=$(getent passwd "$user" | cut -d: -f6)
  [[ -z "$home" ]] && continue
 
  # Find every wp-config.php under this user's home. Addon domains
  # live under public_html/<addon>/ so a single -path covers them.
  while IFS= read -r -d '' wpc; do
    docroot=$(dirname "$wpc")
    [[ -f "$docroot/wp-load.php" ]] || continue
 
    if grep -qF "$MARKER" "$wpc"; then
      log "skip $wpc (already disabled)"
      continue
    fi
 
    # Insert the constant just before "/* That's all" so it lands
    # before WordPress includes wp-settings.php.
    run "sed -i.bak \"/\\/\\* That's all/i $MARKER\\\\ndefine('DISABLE_WP_CRON', true);\" '$wpc'"
 
    minute=$(stagger_minute "$user")
    cronline="$minute,$((minute+15)),$((minute+30)),$((minute+45)) * * * * cd $docroot && /usr/local/bin/php -d memory_limit=512M wp-cron.php >/dev/null 2>&1"
 
    # Append to the user's crontab without clobbering existing entries.
    run "(crontab -u '$user' -l 2>/dev/null; echo '# SGUARD wp-cron stagger'; echo \"$cronline\") | crontab -u '$user' -"
    log "enabled system cron for $user at minute $minute (stagger 15m)"
  done < <(find "$home/public_html" -maxdepth 4 -name wp-config.php -print0 2>/dev/null)
done < /etc/trueuserdomains
 
log "done"

What it does, line by line

/etc/trueuserdomains is cPanel's authoritative domain-to-cPanel-user map. We read it instead of ls /home to skip system accounts and avoid editing the wrong tree.

getent passwd resolves the home directory the cPanel user actually uses, so the script keeps working on servers where homedirs are not under /home/.

grep -qF "$MARKER" is the idempotency gate. The marker comment is unique enough that re-runs become no-ops, and a sysadmin who deletes the marker triggers re-apply on the next pass.

stagger_minute derives a stable 0..14 minute from a checksum of the cPanel username. Same user, same minute, every run. The system cron then fires four times an hour at m, m+15, m+30, m+45.

Dry-run mode

disable-wp-cron-fleet.sh --dry-run | tee /tmp/wp-cron-plan.txt

Every action prints with a DRY-RUN: prefix and the log file captures the same lines. Review the diff against your fleet before the real run.

Logging

/var/log/disable-wp-cron-fleet.log collects every action with an ISO-8601 timestamp. Add it to your logrotate config alongside the other ops scripts.

The full incident this script came out of:

How ServerGuard uses this

Our wp-cron-stacking use case runs this script in dry-run mode first, sends the plan for human approval, then applies it. Real cron, real stagger, no repeat alerts.

شارك هذه المقالة

XLinkedInEmail
  • قراءة 8 دقيقة

    Hardening every WordPress site on cPanel in one loop

    Hardening every WordPress site on cPanel in one loop You manage twenty-seven WordPress sites on one cPanel server. A clean hardening pass on a single site (disable xmlrpc, lock down file editing, force SSL on the admin, security headers int

  • قراءة 17 دقيقة

    WordPress WP-Cron stacking on cPanel: a complete fix

    WordPress WP-Cron stacking on cPanel: a complete fix The page came in at 09:02 local time on a Tuesday. Every WordPress site on was returning 500s for roughly forty seconds, then quietly recovered, then went down again at 09:07, then again

  • قراءة 14 دقيقة

    Patchman activation breaks PHP sites: memory_limit gotcha

    Patchman activation breaks PHP sites: memorylimit gotcha The ticket landed mid-morning. Thirteen WordPress sites on were intermittently returning 500s. Not all at once, not on a clean five-minute beat, not correlated with traffic. The sites