Disattivare WP-Cron e usare il cron di sistema
WP-Cron dipende dalle visite e può bloccarsi o accumularsi. Disattivalo, configura il cron di sistema con WP-CLI e verifica l'esecuzione degli eventi.
Che cos'è wp-cron e perché il cron di sistema lo sostituisce
WP-Cron è lo scheduler delle attività integrato in WordPress e viene eseguito solo quando qualcuno richiede una pagina. WordPress non avvia autonomamente alcuna attività. A ogni richiesta non servita dalla cache, WordPress legge l'elenco dei job pianificati e, se uno è in scadenza, invia una seconda richiesta HTTP a se stesso all'indirizzo /wp-cron.php per eseguire l'attività. Spostare questo job nel cron di sistema garantisce un'esecuzione prevedibile a intervalli fissi, indipendentemente dal fatto che in quel minuto il sito abbia ricevuto mille visite oppure nessuna.
Il lavoro effettivo richiede due righe: una costante in wp-config.php e una voce crontab. Tutto il resto di questa guida riguarda gli aspetti che queste due righe non indicano. Occorre stabilire con quale utente deve essere eseguito il job, verificare che gli eventi pianificati siano stati effettivamente eseguiti e conoscere i tre modi in cui la configurazione può non funzionare senza mostrare errori sul sito.
Gli esempi usano /srv/www/example.com come directory di WordPress e www-data come utente del server web. Sostituire ovunque questi valori con i propri percorsi e il proprio utente.
Quale visitatore ha attivato i costi di cron su un sito trafficato
Ogni richiesta non servita dalla cache paga il costo del controllo. WordPress carica l'opzione cron, confronta i timestamp e, quando c'è un'attività in scadenza, chiama spawn_cron(), che invia una richiesta loopback non bloccante a /wp-cron.php. Il visitatore non attende il risultato. Lo fa un worker PHP. Su un piccolo VPS che esegue PHP-FPM con pm.max_children = 5, un'attività pianificata lenta occupa un quinto della capacità PHP per tutta la sua durata. Inoltre, è molto probabile che venga attivata durante il minuto di maggiore traffico, perché è il momento in cui vengono caricate più pagine.
WordPress limita i duplicati. Acquisisce un lock con durata pari a WP_CRON_LOCK_TIMEOUT, 60 secondi per impostazione predefinita, così i visitatori simultanei non avviano ciascuno un'esecuzione. Il lock limita le duplicazioni. Non sposta l'attività fuori dal percorso della richiesta.
Prima di decidere che il problema è rilevante, conta la frequenza con cui si verifica sul tuo server. Ogni loopback compare nel log degli accessi del server web:
sudo grep -c 'wp-cron.php' /var/log/nginx/access.logApache scrive invece in /var/log/apache2/access.log. Un conteggio nell'ordine delle migliaia al giorno rappresenta un costo reale. È un valore che dovresti misurare sul tuo server, invece di ricavarlo da un articolo, proprio come faresti per eseguire il benchmark di un VPS prima e dopo qualsiasi altra modifica.
La cache cambia il quadro. Se una cache delle pagine serve la maggior parte delle richieste come HTML statico, PHP non viene eseguito per quelle richieste e il controllo di cron non avviene. Un sito trafficato con una cache efficace inizia a comportarsi come il sito poco trafficato descritto di seguito.
Quale visitatore ha attivato cron causando problemi su un sito poco frequentato
Senza visitatori non viene eseguito alcun cron. Un sito che riceve poche visite al giorno esegue i processi pianificati poche volte al giorno, nei momenti casuali in cui arrivano quelle visite.
I sintomi sono sempre gli stessi. Un articolo pianificato per le 09:00 resta nell'elenco degli articoli con lo stato Missed schedule finché qualcuno non carica una pagina. I plugin di backup saltano l'esecuzione notturna. I controlli degli aggiornamenti accumulano ritardo, quindi la dashboard non mostra aggiornamenti disponibili anche se è già stata pubblicata una release di sicurezza. Le email relative agli ordini, le notifiche di rinnovo e gli avvisi di scadenza vengono inviati in ritardo.
Nessuno di questi eventi registra un errore. Dal punto di vista di WordPress, il processo non era in ritardo, perché non era mai stato avviato.
Passaggio 1: disattivare il trigger delle visite in wp-config.php
Aprire /srv/www/example.com/wp-config.php e aggiungere la costante:
define( 'DISABLE_WP_CRON', true );Inserirla sopra la riga con /* That's all, stop editing! Happy publishing. */, perché la riga immediatamente sotto quel commento richiede wp-settings.php e wp-settings.php è il punto in cui WordPress associa il controllo di cron a init. Una costante definita dopo quel require viene impostata troppo tardi per modificare il comportamento. Il file appare corretto, ma il trigger continua a essere eseguito.
Verificare che la riga si trovi nella posizione prevista:
grep -n "DISABLE_WP_CRON\|stop editing" /srv/www/example.com/wp-config.phpLa costante non impedisce la pianificazione degli eventi. I plugin continuano ad aggiungere job alla coda, esattamente come prima. Impedisce soltanto che i caricamenti delle pagine elaborino la coda. Di conseguenza, la coda non viene più eseguita finché non si completa il passaggio 3.
Non blocca neppure le richieste dirette a /wp-cron.php. Chiunque può ancora richiedere quell'URL. Di norma non è un problema, perché il file esegue soltanto le attività in scadenza. Il blocco nella configurazione del web server è facoltativo. Se lo si applica, anche il fallback curl vicino alla fine di questa guida smette di funzionare.
Passaggio 2: installare WP-CLI
WP-CLI è lo strumento ufficiale a riga di comando per WordPress. Richiede il binario PHP per la riga di comando, fornito da un pacchetto separato rispetto al modulo PHP del server web.
php -v
sudo apt install -y php-cliInstallare WP-CLI dal build phar, come raccomandato dalla guida ufficiale all'installazione:
cd /tmp
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
php wp-cli.phar --info
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
wp --infophp wp-cli.phar --info stampa il percorso del binario PHP, la versione di PHP e la versione di WP-CLI. Se stampa tutti e tre i valori, il phar funziona. Ad agosto 2026, la guida all'installazione indica PHP 7.2.24 come versione minima e Ubuntu 24.04 include PHP 8.3, quindi un server aggiornato supera ampiamente questo requisito. In seguito, aggiornare con sudo wp cli update.
Eseguire WP-CLI con l'utente del sito, mai come root:
sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com core versionCome root, WP-CLI rifiuta di avviarsi:
Error: YIKES! It looks like you're running this as root.Suggerisce --allow-root. Non usarlo in questo caso. Il motivo è spiegato nella prima modalità di errore riportata di seguito.
Si noti inoltre che sudo -u www-data -i non funziona, perché la shell di login di quell'account è /usr/sbin/nologin e viene restituito This account is currently not available. Passare il comando direttamente a sudo -u evita la shell di login, quindi il comando viene eseguito correttamente.
Ora verificare che WordPress rilevi la costante del passaggio 1:
sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com eval 'var_dump( DISABLE_WP_CRON );'Viene stampato bool(true). Un errore fatale relativo a una costante non definita indica che la riga define() non viene raggiunta. Di solito significa che è stata inserita dopo la direttiva require.
Passaggio 3: aggiungere la voce cron con l'utente corretto
L'utente corretto è quello proprietario dei file scritti da PHP. Verifica entrambe le estremità:
stat -c '%U %G' /srv/www/example.com/wp-content/uploads
grep -E '^(user|group) = ' /etc/php/8.3/fpm/pool.d/www.confIn un'installazione Ubuntu predefinita, entrambe restituiscono www-data. Se hai assegnato al sito un proprio pool PHP-FPM con un utente dedicato, come avviene normalmente in una configurazione per sito basata su uno stack LAMP su Ubuntu 24.04, usa quell'utente per tutte le operazioni seguenti.
Crea una directory per i log scrivibile da quell'utente:
sudo install -d -o www-data -g www-data -m 750 /var/log/wp-cronModifica il crontab di quell'utente:
sudo crontab -u www-data -eAggiungi una riga:
*/5 * * * * /usr/bin/flock -n /run/lock/wp-cron-example.lock /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now >> /var/log/wp-cron/example.log 2>&1Analizziamo i componenti. */5 esegue il comando ogni cinque minuti. flock -n acquisisce un file di lock e termina immediatamente se un'esecuzione precedente lo mantiene ancora aperto. /usr/local/bin/wp è il percorso assoluto richiesto da cron. --path consente al comando di essere eseguito da qualsiasi directory di lavoro. --due-now elabora soltanto gli eventi la cui ora è arrivata, invece di elaborare tutti gli eventi presenti nella coda. Il redirect invia l'output normale e gli errori a un unico file che puoi leggere.
In pratica, il redirect è indispensabile. Cron invia l'output di un job all'utente che lo esegue, ma nella maggior parte delle immagini VPS non è installato alcun mail transfer agent. Cron registra quindi (CRON) info (No MTA installed, discarding output) e scarta l'output. Un file conserva le informazioni necessarie per l'analisi.
Verifica il file salvato:
sudo crontab -u www-data -lPer più siti, usa una riga per ciascuno e distribuisci i minuti di esecuzione in modo che non vengano avviati tutti contemporaneamente:
*/5 * * * * /usr/bin/flock -n /run/lock/wp-cron-one.lock /usr/local/bin/wp --path=/srv/www/one.example.com cron event run --due-now >> /var/log/wp-cron/one.log 2>&1
2-59/5 * * * * /usr/bin/flock -n /run/lock/wp-cron-two.lock /usr/local/bin/wp --path=/srv/www/two.example.com cron event run --due-now >> /var/log/wp-cron/two.log 2>&1Il log cresce indefinitamente se non lo ruoti. Scrivi /etc/logrotate.d/wp-cron:
/var/log/wp-cron/*.log {
weekly
rotate 4
missingok
notifempty
compress
create 640 www-data www-data
}Verifica che la sintassi venga analizzata senza eseguire alcuna modifica: sudo logrotate --debug /etc/logrotate.d/wp-cron.
Passaggio 4: verificare che gli eventi pianificati siano stati realmente eseguiti
Il fatto che una riga crontab sia stata salvata correttamente non dimostra nulla. Procedete dal controllo meno costoso a quello che fornisce una conferma effettiva.
Per prima cosa, cron ha avviato il comando? Cron scrive nel journal tramite la propria unit:
journalctl -u cron.service --since "15 min ago" | grep wpUna voce corretta ha questo aspetto, senza il timestamp e il nome host iniziali:
CRON[24913]: (www-data) CMD (/usr/bin/flock -n /run/lock/wp-cron-example.lock /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now >> /var/log/wp-cron/example.log 2>&1)Quella riga indica che cron ha avviato il comando come www-data. Non dice nulla sull'esito del comando.
Secondo controllo: WordPress ha eseguito qualcosa? Leggete il file di log:
sudo tail -n 20 /var/log/wp-cron/example.logWP-CLI stampa una riga per ogni evento, quindi il totale:
Executed the cron event 'wp_version_check' in 0.418s.
Success: Executed a total of 2 cron events.Gli errori vengono scritti nello stesso file. Questo è lo scopo di 2>&1. Nella maggior parte delle esecuzioni non ci saranno eventi da eseguire e verrà scritto molto poco. Leggete quindi il file dopo un'esecuzione in cui sapete che c'erano attività in attesa.
Terzo controllo: verificate il flusso completo. Pianificate un evento marcatore e osservate quando scompare:
sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event schedule wp_cli_cron_check now
sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event list --fields=hook,next_run_relative --format=csv | grep wp_cli_cron_checkAttendete un intervallo, quindi eseguite di nuovo il comando di elenco. L'hook non è più presente perché un evento una tantum viene rimosso dalla coda quando viene eseguito. Nessun plugin registra un callback con quel nome di hook, quindi la sua esecuzione non produce altri effetti sul sito. Se l'hook è ancora elencato dopo due intervalli, la coda non viene eseguita. I primi due controlli indicano se il problema riguarda cron o WP-CLI.
Non usate wp cron test per questa verifica. Quel comando controlla se il processo di avvio attivato da un visitatore funziona e restituisce un errore quando DISABLE_WP_CRON è vero. Su un server configurato correttamente, l'errore è il risultato previsto e non indica un problema.
L’alternativa con timer systemd
Se il resto delle attività pianificate del server viene già eseguito tramite servizi e timer systemd, aggiungi anche WordPress. Ogni esecuzione sarà quindi visibile in systemctl list-timers e l’output verrà inviato al journal invece che a un file da ruotare.
Scrivi /etc/systemd/system/wp-cron-example.service:
[Unit]
Description=Run due WordPress cron events for example.com
[Service]
Type=oneshot
User=www-data
Group=www-data
ExecStart=/usr/local/bin/wp --path=/srv/www/example.com cron event run --due-nowPoi /etc/systemd/system/wp-cron-example.timer:
[Unit]
Description=Run WordPress cron for example.com every 5 minutes
[Timer]
OnCalendar=*:0/5
AccuracySec=30s
Persistent=true
[Install]
WantedBy=timers.targetsudo systemctl daemon-reload
sudo systemctl enable --now wp-cron-example.timer
systemctl list-timers wp-cron-example.timer
journalctl -u wp-cron-example.service -n 20systemd non esegue contemporaneamente due istanze dello stesso servizio, quindi questa versione non richiede flock. Persistent=true consente di recuperare un’esecuzione persa mentre la macchina era spenta, cosa che una voce crontab non può fare.
Scegli crontab oppure il timer. Eseguirli entrambi sullo stesso sito significa svuotare la coda due volte; le esecuzioni duplicate di un’attività relativa a email o ordini sono visibili ai clienti.
Perché il job cron non deve essere eseguito come root
Questo è il primo dei tre modi in cui la configurazione può non funzionare. Se inserisci il job nel crontab di root, WP-CLI si arresta prima di eseguire qualsiasi operazione:
Error: YIKES! It looks like you're running this as root.La coda non viene mai eseguita. Se non hai reindirizzato l'output, non vedrai il messaggio. La correzione pericolosa consiste nell'aggiungere --allow-root, perché in questo modo ogni file scritto da un plugin durante l'esecuzione apparterrà a root. La richiesta web successiva viene eseguita come www-data, non può scrivere in quelle directory e il sito inizia a mostrare messaggi come:
Unable to create directory wp-content/uploads/2026/08. Is its parent directory writable by the server?Correggi il proprietario, quindi sposta il job:
sudo chown -R www-data:www-data /srv/www/example.com/wp-contentIl crontab di root e il crontab di www-data sono file distinti. Eliminare la riga da uno non modifica l'altro. Controlla entrambi:
sudo crontab -u root -l
sudo crontab -u www-data -lPerché cron segnala wp: not found
Questo è il secondo errore. Cron fornisce ai job degli utenti un PATH molto breve, /usr/bin:/bin. WP-CLI viene installato in /usr/local/bin, che non è incluso in quell’elenco. Il job viene avviato, termina con un errore in una frazione di secondo e nel log resta una sola riga:
/bin/sh: 1: wp: not foundVerificate direttamente l’ambiente di cron invece di procedere per ipotesi. Aggiungete temporaneamente questa riga:
*/5 * * * * env > /tmp/cron-env.txt 2>&1Dopo un intervallo, leggete /tmp/cron-env.txt e poi rimuovete la riga. Il valore PATH= contenuto in quel file corrisponde esattamente all’ambiente ricevuto dal job.
Sono disponibili due soluzioni. Usate il percorso assoluto /usr/local/bin/wp, come nel passaggio 3. In alternativa, impostate PATH una sola volta all’inizio del crontab, prima di tutte le righe dei job:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binLo stesso problema si presenta a un livello inferiore. Il file phar wp inizia con #!/usr/bin/env php, quindi anche la shell deve poter trovare php. Se PHP si trova al di fuori di /usr/bin, come può accadere con build personalizzate e build dei pannelli di controllo, viene restituito:
/usr/bin/env: 'php': No such file or directoryIn questo caso, richiamate esplicitamente l’interprete, ad esempio /usr/local/bin/php /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now.
Perché un intervallo di un minuto ricrea il problema originale
Questo è il terzo problema. * * * * * sembra più sicuro di cinque minuti, ma su un sito sotto carico riporta alla situazione di partenza. Se un'esecuzione dura più dell'intervallo, quella successiva inizia mentre la prima è ancora in corso. Dopo dieci minuti ci sono dieci processi PHP, ciascuno con la propria memoria e la propria connessione al database.
Verificate direttamente l'accumulo:
ps -eo etimes,user,args | grep '[c]ron event run'etimes è l'età del processo in secondi. Una riga indica una situazione normale. Diverse righe con età molto superiori all'intervallo indicano che le esecuzioni si stanno accumulando; su un VPS di piccole dimensioni questo può causare un errore MySQL Too many connections oppure il kernel può terminare PHP per recuperare memoria. Potete confermarlo con sudo dmesg -T | grep -i 'killed process'.
WP-CLI esegue direttamente le callback degli eventi invece di richiedere wp-cron.php, quindi il blocco di 60 secondi che WordPress usa per impedire l'avvio di processi duplicati non si applica. flock -n nella voce del passaggio 3 impedisce ora le esecuzioni sovrapposte. Un'esecuzione saltata termina immediatamente e senza messaggi, per progettazione. Le esecuzioni sovrapposte sono un problema da risolvere nel crontab, non dal kernel dell'host: anche il posizionamento delle attività basato sulla cache aggiunto nel kernel Linux 7.2 decide soltanto su quale core viene eseguito un processo, non quanti processi avete avviato.
Scegliete l'intervallo in base alla pianificazione più breve da cui dipendete realmente e misurate prima la durata di un'esecuzione:
time sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-nowCinque minuti sono un'impostazione predefinita ragionevole: un articolo pianificato per le 09:00 viene pubblicato entro le 09:05. Quindici minuti vanno bene per un sito privo di attività con vincoli temporali. Un minuto è adatto ai negozi online e ai plugin basati su code che ne hanno realmente bisogno, e soltanto dopo avere verificato che un'esecuzione termini in pochi secondi.
Se non è possibile installare WP-CLI
Alcuni provider bloccano gli strumenti da shell. Una semplice richiesta HTTP a wp-cron.php esegue la stessa coda, ma attraversa l'intero stack web:
*/5 * * * * /usr/bin/curl -sS --max-time 120 "https://example.com/wp-cron.php?doing_wp_cron" > /dev/nullCosa si perde, in concreto:
- L'esecuzione è soggetta ai timeout del web server e di PHP-FPM, quindi un'attività lunga può essere interrotta a metà.
- Il certificato deve essere valido, altrimenti
curlsi interrompe conSSL certificate problem. Mantieni quindi attivi i rinnovi usando Certbot su nginx. - La cache delle pagine non deve memorizzare
wp-cron.php. In caso contrario, le richieste cron ricevono una risposta memorizzata nella cache e non viene eseguito nulla. - Non viene prodotto alcun output per evento. L'unica prova che un'attività è stata eseguita è l'effetto prodotto.
-sS mantiene curl silenzioso in caso di successo, continuando però a visualizzare gli errori. È il comportamento desiderato in un'attività cron.
Cos'altro deve rientrare nella pianificazione del server
Dopo aver affidato la coda di WordPress a system cron, mantieni nello stesso posto anche le altre attività ricorrenti del server, così puoi controllarle da un unico punto. Le patch di sicurezza del sistema operativo vanno gestite tramite aggiornamenti automatici e non con una riga cron mantenuta manualmente. Gli aggiornamenti di plugin e temi di WordPress richiedono invece una decisione diversa: wp plugin update --all in un crontab può facilmente causare un'interruzione del sito in produzione alle 3 di notte, senza che nessuno lo controlli. Eseguili quindi intenzionalmente, oppure dopo una fase di staging e un backup.
FAQ
La disabilitazione di WP-Cron impedisce la pubblicazione dei post programmati?
No, purché un altro processo esegua la coda. DISABLE_WP_CRON impedisce soltanto che il caricamento delle pagine attivi la coda. Gli eventi continuano a essere programmati esattamente come prima. Un post impostato per le 09:00 viene pubblicato alla prima esecuzione di cron successiva alle 09:00, quindi un intervallo di cinque minuti lo pubblica entro le 09:05. Se imposti la costante e non aggiungi mai la voce cron, il post resta nell'elenco con lo stato Missed schedule finché un processo non esegue la coda.
Quale utente deve eseguire il job cron di WordPress?
L'utente proprietario dei file scritti da PHP, che in un'installazione Ubuntu predefinita è www-data. Verifica con stat -c '%U %G' /srv/www/example.com/wp-content/uploads e confronta il risultato con la riga user = nella configurazione del pool PHP-FPM. Se esegui il job come root, WP-CLI si interrompe con un errore YIKES; se lo forzi tramite --allow-root, vengono creati file di proprietà di root in wp-content, che il web server non può più scrivere.
Con quale frequenza system cron deve eseguire WordPress cron?
Ogni cinque minuti è adatto alla maggior parte dei siti. Imposta l'intervallo in base alla pianificazione più breve da cui dipendi realmente e mantienilo significativamente superiore al tempo necessario per una singola esecuzione. Puoi misurare questo tempo anteponendo time al comando WP-CLI. Su un sito sotto carico, intervalli di un minuto possono sovrapporre più esecuzioni, a meno che flock non le protegga.
Perché wp cron test non funziona dopo la disabilitazione di WP-Cron?
Perché quel comando verifica l'avvio attivato dai visitatori e segnala un errore quando DISABLE_WP_CRON è impostato su true. Questo è il risultato corretto su un server configurato in questo modo. Controlla invece il percorso di system cron: leggi /var/log/wp-cron/example.log oppure pianifica un evento marker con wp cron event schedule e verifica che sia scomparso da wp cron event list dopo l'esecuzione successiva.
È necessario WP-CLI o è sufficiente usare curl per wp-cron.php?
curl funziona ed è la soluzione corretta quando non puoi installare WP-CLI. È più lento perché carica WordPress tramite il web server ed è soggetto al timeout della richiesta. WP-CLI esegue gli eventi in un processo PHP da riga di comando, senza timeout web, e stampa una riga per ogni evento con la relativa durata. In questo modo il log indica esattamente cosa è stato eseguito e quanto tempo ha richiesto.