Disattivare wp-cron e usare cron di sistema
WP-Cron si avvia solo al caricamento di una pagina: sui siti poco visitati si blocca. Usa WP-CLI e cron di sistema, poi verifica l’esecuzione degli eventi.
Che cos’è wp-cron e perché viene sostituito da cron di sistema
WP-Cron è lo scheduler delle attività integrato in WordPress e viene eseguito soltanto quando qualcuno richiede una pagina. WordPress non avvia autonomamente alcuna attività. A ogni richiesta che non viene servita dalla cache, WordPress legge l’elenco dei job pianificati e, se uno è in scadenza, invia una seconda richiesta HTTP a /wp-cron.php per eseguire l’attività. Spostare questo job in cron di sistema garantisce un’esecuzione prevedibile a intervalli fissi, indipendentemente dal fatto che il sito abbia ricevuto mille visite in quel minuto oppure nessuna.
Il lavoro effettivo viene svolto da 2 righe: una costante in wp-config.php e una voce crontab. Tutto il resto di questa guida riguarda gli aspetti che queste 2 righe non indicano: con quale utente deve essere eseguito il job, come verificare che gli eventi pianificati siano stati realmente eseguiti e i 3 modi in cui la configurazione può fallire senza visualizzare alcun messaggio sul sito.
Gli esempi usano /srv/www/example.com come directory di WordPress e www-data come utente del server web. Sostituisci questi percorsi e questo utente con i valori del tuo ambiente in ogni punto.
Quale visitatore ha attivato il costo di cron su un sito trafficato
Ogni richiesta non servita dalla cache paga il controllo. WordPress carica l'opzione cron, confronta i timestamp e, quando 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 job pianificato lento occupa un quinto della capacità PHP per tutta la sua durata. Inoltre, è molto probabile che venga attivato durante il minuto di maggiore traffico, perché è il momento in cui si verificano più caricamenti di pagina.
WordPress limita i duplicati. Acquisisce un lock con durata WP_CRON_LOCK_TIMEOUT, pari a 60 secondi per impostazione predefinita, così i visitatori simultanei non avviano ciascuno un'esecuzione. Il lock limita i duplicati. Non sposta l'elaborazione fuori dal percorso della richiesta.
Prima di decidere che il problema è rilevante, misura con quale frequenza si verifica sul tuo server. Ogni richiesta loopback compare nell'access log del web server:
sudo grep -c 'wp-cron.php' /var/log/nginx/access.logApache scrive invece in /var/log/apache2/access.log. Diverse migliaia di richieste al giorno rappresentano un costo reale. È un valore che devi misurare sul tuo server, non leggere in un articolo, proprio come faresti per eseguire un benchmark su un VPS prima e dopo qualsiasi altra modifica.
La cache cambia il quadro. Se una page cache serve la maggior parte delle richieste come HTML statico, PHP non viene eseguito per quelle richieste e il controllo 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 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 queste 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 sono in ritardo: la dashboard non mostra aggiornamenti disponibili mentre una release di sicurezza è già stata pubblicata. Le email degli ordini, le notifiche di rinnovo e gli avvisi di scadenza vengono inviati in ritardo.
Nessuno di questi casi produce un errore nei log. 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
Apri /srv/www/example.com/wp-config.php e aggiungi la costante:
define( 'DISABLE_WP_CRON', true );Inseriscila prima della riga /* That's all, stop editing! Happy publishing. */, perché la riga sotto quel commento richiede wp-settings.php e wp-settings.php è il punto in cui WordPress collega 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.
Verifica 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 processi alla coda, esattamente come prima. Impedisce soltanto ai caricamenti delle pagine di elaborare quella coda. Di conseguenza, la coda non viene più elaborata finché non completi il passaggio 3.
La costante non blocca neppure le richieste dirette a /wp-cron.php. Chiunque può ancora richiedere quell'URL. Di solito non è un problema, perché il file esegue soltanto le attività scadute. Il blocco nella configurazione del web server è facoltativo. Se lo attivi, anche il fallback curl alla fine di questa guida smetterà 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 dalla 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 file 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. Un server aggiornato supera quindi ampiamente questo requisito. In seguito, eseguire l'aggiornamento 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. In questo caso non usarlo. Il motivo è descritto 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 l'esecuzione ha esito positivo.
Ora verificare che WordPress stesso 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 );'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 che PHP scrive. Controlla entrambi i lati:
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 le risposte sono www-data. Se hai assegnato al sito un pool PHP-FPM dedicato con un utente specifico, come avviene normalmente in una configurazione per sito basata su uno stack LAMP su Ubuntu 24.04, usa quell’utente per tutti i passaggi seguenti.
Crea una directory per i log in cui l’utente possa scrivere:
sudo install -d -o www-data -g www-data -m 750 /var/log/wp-cronModifica il crontab dell’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>&1Esaminiamo i singoli elementi. */5 esegue il comando ogni cinque minuti. flock -n acquisisce un file di lock e termina immediatamente se un’esecuzione precedente lo mantiene ancora. /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 solo gli eventi la cui scadenza è arrivata, invece di elaborare ogni evento presente 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 che il file sia stato salvato:
sudo crontab -u www-data -lPer più siti, usa una riga per ciascuno e distribuisci i minuti di avvio, in modo che non vengano eseguiti 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 salvataggio corretto di una riga nel crontab non dimostra nulla. Procedere dal controllo meno costoso fino a quello che fornisce una conferma conclusiva.
Per prima cosa, cron ha avviato il comando? Cron scrive nel journal usando la propria unità:
journalctl -u cron.service --since "15 min ago" | grep wpUna voce corretta è simile a questa, eliminando dall'inizio il timestamp e il nome host:
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 indica se il comando è terminato correttamente.
Secondo controllo: WordPress ha eseguito qualcosa? Leggere il file di log:
sudo tail -n 20 /var/log/wp-cron/example.logWP-CLI stampa una riga per ogni evento, seguita da un 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, che è proprio lo scopo di 2>&1. Nella maggior parte delle esecuzioni non ci saranno eventi da elaborare e verrà scritto poco. Leggere quindi il file dopo un'esecuzione per la quale si sa che erano presenti attività in attesa.
Terzo controllo: verificare il funzionamento dall'inizio alla fine. Pianificare un evento marcatore e osservarne la scomparsa:
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_checkAttendere un intervallo, quindi eseguire 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 usare wp cron test per questo controllo. Quel comando verifica se funziona l'avvio attivato dai visitatori e restituisce un errore quando DISABLE_WP_CRON è true. Su un server configurato correttamente, l'errore è l'output previsto, non un problema.
L'alternativa con il timer di systemd
Se le altre attività pianificate del server vengono già eseguite come servizi e timer systemd, inserisci anche WordPress in questa struttura. Ogni esecuzione comparirà in systemctl list-timers e l'output verrà scritto nel journal invece che in 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-nowQuindi /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 elaborare la coda due volte; le esecuzioni duplicate di un'attività di posta elettronica o di gestione degli 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 non funziona. Se inserisci il job nel crontab di root, WP-CLI si interrompe prima di eseguire qualsiasi operazione:
Error: YIKES! It looks like you're running this as root.La coda non viene mai eseguita e, se non hai reindirizzato l'output, non visualizzi il messaggio. La correzione pericolosa consiste nell'aggiungere --allow-root, perché tutti i file scritti da un plugin durante quell'esecuzione apparterrebbero a root. La richiesta web successiva viene eseguita come www-data, non può scrivere in quelle directory e il sito inizia a segnalare messaggi come:
Unable to create directory wp-content/uploads/2026/08. Is its parent directory writable by the server?Ripristina la proprietà dei file, 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, quindi eliminare la riga da uno non modifica l'altro. Controllali entrambi:
sudo crontab -u root -l
sudo crontab -u www-data -lPerché cron segnala wp: not found
Questo è il secondo errore. Cron assegna ai job degli utenti un PATH molto ridotto, /usr/bin:/bin. WP-CLI viene installato in /usr/local/bin, che non è incluso in quell’elenco. Il job viene avviato, termina con errore in una frazione di secondo e nel log resta una sola riga:
/bin/sh: 1: wp: not foundControlla direttamente l’ambiente di cron invece di procedere per tentativi. Aggiungi temporaneamente questa riga:
*/5 * * * * env > /tmp/cron-env.txt 2>&1Dopo un intervallo, leggi /tmp/cron-env.txt e poi rimuovi la riga. Il valore di PATH= in quel file corrisponde esattamente all’ambiente ricevuto dal job.
Le soluzioni sono due. Usa il percorso assoluto /usr/local/bin/wp, come nel passaggio 3. Oppure imposta PATH una sola volta all’inizio del crontab, prima di ogni riga 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 accade con build personalizzate e build fornite dai pannelli di controllo, viene restituito:
/usr/bin/env: 'php': No such file or directoryIn questo caso richiama 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.
Controlla direttamente l'accumulo dei processi:
ps -eo etimes,user,args | grep '[c]ron event run'etimes indica l'età del processo in secondi. Una riga è normale. Più righe con un'età molto superiore al tuo intervallo indicano che le esecuzioni si stanno sovrapponendo. 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 confermarlo con sudo dmesg -T | grep -i 'killed process'.
WP-CLI esegue direttamente le callback degli eventi invece di richiedere wp-cron.php. Pertanto, il blocco di 60 secondi che WordPress usa per impedire l'avvio di processi duplicati non si applica in questo caso. flock -n nella voce del passaggio 3 impedisce ora le sovrapposizioni. Un'esecuzione saltata termina immediatamente e senza messaggi, per progettazione.
Scegli l'intervallo in base alla pianificazione più ravvicinata da cui dipendi realmente 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 valore predefinito ragionevole: un articolo pianificato per le 09:00 viene pubblicato entro le 09:05. Quindici minuti vanno bene per un sito che non contiene attività vincolate a un orario preciso. Un minuto è adatto ai negozi e ai plugin basati su code che ne hanno realmente bisogno, ma solo dopo aver verificato che un'esecuzione termini in pochi secondi.
Se non puoi installare WP-CLI
Alcuni hosting bloccano gli strumenti da shell. Una semplice richiesta HTTP a wp-cron.php esegue la stessa coda, ma attraverso l'intero stack web:
*/5 * * * * /usr/bin/curl -sS --max-time 120 "https://example.com/wp-cron.php?doing_wp_cron" > /dev/nullCosa perdi, in concreto:
- L'esecuzione è limitata dai timeout del server web e delle richieste 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 tramite Certbot su nginx. - La cache delle pagine non deve memorizzare nella cache
wp-cron.php, altrimenti le richieste cron ricevono una risposta memorizzata e non viene eseguito nulla. - Non ricevi alcun output per evento. L'unica prova che un'attività è stata eseguita è il suo effetto.
-sS mantiene curl silenzioso in caso di esito positivo, continuando però a visualizzare gli errori. È il comportamento corretto per un'attività cron.
Cos’altro deve essere pianificato sul server
Dopo avere affidato a cron di sistema la coda di WordPress, mantieni nello stesso punto anche le altre attività ricorrenti del server, così puoi controllarle in modo centralizzato. Le patch di sicurezza del sistema operativo devono essere gestite dagli aggiornamenti automatici e non da una riga di cron che devi mantenere manualmente. Gli aggiornamenti di plugin e temi di WordPress richiedono una decisione diversa: wp plugin update --all in un crontab può facilmente causare il malfunzionamento di un sito in produzione alle 3:00, senza che nessuno lo controlli. Eseguili quindi deliberatamente, 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 impostato per le 09:00 viene pubblicato alla prima esecuzione di cron dopo le 09:00; con un intervallo di cinque minuti, quindi, viene pubblicato entro le 09:05. Se imposti la costante e non aggiungi mai 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 scritti da PHP, che in un'installazione Ubuntu predefinita è www-data. Verificalo con stat -c '%U %G' /srv/www/example.com/wp-content/uploads e confrontalo 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, lascia file di proprietà di root in wp-content, che il server Web non può scrivere in seguito.
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 su cui fai effettivamente affidamento e mantienilo sufficientemente superiore al tempo richiesto da una singola esecuzione. Puoi misurare questo tempo anteponendo time al comando WP-CLI. Su un sito sotto carico, gli intervalli di un minuto possono sovrapporre le 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 marcatore con wp cron event schedule e verifica che sia scomparso da wp cron event list dopo l'esecuzione successiva.
È necessario WP-CLI o è sufficiente curl verso wp-cron.php?
curl funziona ed è la soluzione corretta quando non puoi installare WP-CLI. È più lento perché carica WordPress tramite il server Web ed è soggetto al timeout della richiesta. WP-CLI esegue gli eventi in un processo PHP dalla 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.