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, che è fornito da un pacchetto distinto dal 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. A ottobre 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 ignora la shell di login, quindi l'esecuzione funziona.
Ora verificare che WordPress stesso riconosca la costante del passaggio 1:
sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com eval 'var_dump( DISABLE_WP_CRON );'Questo stampa bool(true). Un errore irreversibile 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 errore. * * * * * sembra più sicuro di cinque minuti, ma su un sito trafficato riporta alla situazione iniziale. Se un'esecuzione dura più dell'intervallo, quella successiva inizia mentre la prima è ancora in corso. Dopo dieci minuti ci sono dieci processi PHP, ognuno con la propria memoria e la propria connessione al database.
Cerca direttamente un accumulo di processi:
ps -eo etimes,user,args | grep '[c]ron event run'etimes indica 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. Puoi verificare quest'ultimo caso con sudo dmesg -T | grep -i 'killed process'.
WP-CLI esegue direttamente i callback degli eventi invece di richiedere wp-cron.php, ma con --due-now l'attuale WP-CLI rispetta comunque il transient doing_cron, lo stesso lock che WordPress usa per impedire l'avvio di esecuzioni duplicate. Imposta questo lock quando un'esecuzione inizia. Se un'altra esecuzione lo detiene già, la nuova esecuzione scrive Warning: A cron event run is already in progress; skipping. nel log e termina. Il lock dura solo WP_CRON_LOCK_TIMEOUT, cioè 60 secondi per impostazione predefinita. Di conseguenza, un'esecuzione che dura più di un minuto non è più protetta dal lock e quella successiva può iniziare contemporaneamente. flock -n nella voce del passaggio 3 mantiene il lock per tutta la durata del processo. È questo che impedisce la sovrapposizione delle esecuzioni lente. Un'esecuzione ignorata da flock termina immediatamente e senza messaggi, per progettazione. La sovrapposizione deve essere gestita nel crontab, non dal kernel dell'host: anche il posizionamento dei task che tiene conto della cache, aggiunto nel kernel Linux 7.2 decide soltanto su quale core eseguire un processo, non quanti processi avviare.
Scegli l'intervallo in base alla pianificazione più breve da cui dipendi effettivamente e misura 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 post pianificato per le 09:00 viene normalmente pubblicato entro le 09:05, purché l'esecuzione precedente sia terminata. Se alle 09:05 un'esecuzione precedente detiene ancora il lock, quella invocazione viene ignorata e il post attende la successiva esecuzione che riesce ad acquisire il lock. Quindici minuti sono adeguati per un sito che non gestisce attività urgenti. Un intervallo di un minuto è adatto a negozi online e plugin basati su code che ne hanno effettivamente bisogno, e solo dopo aver 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 degli articoli 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 pianificati esattamente come prima. Un articolo programmato per le 09:00 viene pubblicato alla prima esecuzione di cron successiva alle 09:00 che riesce effettivamente ad acquisire il lock; con un intervallo di cinque minuti, normalmente viene quindi pubblicato entro le 09:05. Se un'esecuzione precedente e lenta mantiene ancora il lock, quella chiamata viene saltata e la pubblicazione attende l'esecuzione successiva. Se imposti la costante e non aggiungi la voce cron, l'articolo 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 in cui PHP scrive, 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; forzandone l'esecuzione tramite --allow-root, invece, vengono creati file di proprietà di root in wp-content, che il web server non potrà scrivere in seguito.
Con quale frequenza system cron deve eseguire il cron di WordPress?
Ogni cinque minuti è sufficiente per la maggior parte dei siti. Imposta l'intervallo in base alla pianificazione più breve su cui fai realmente affidamento e mantienilo abbastanza superiore al tempo richiesto da una singola esecuzione. Puoi misurare questo tempo anteponendo time al comando WP-CLI. Su un sito occupato, gli intervalli di un minuto sovrappongono le esecuzioni, a meno che flock non le protegga.
Perché wp cron test non funziona dopo che ho disabilitato 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 del cron di sistema: leggi /var/log/wp-cron/example.log oppure pianifica un evento di controllo con wp cron event schedule e verifica che sia scomparso da wp cron event list dopo l'esecuzione successiva.
Ho bisogno di WP-CLI o è sufficiente usare curl verso wp-cron.php?
curl funziona ed è la scelta 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 del web server, 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.