Perché Cron può eseguire il PHP “sbagliato”
Quando un sito gira con una versione di PHP nel web server e i job schedulati ne usano un’altra, il problema non è Cron in sé: è l’ambiente minimo con cui Cron avvia i processi. Nel contesto di hosting questo capita spesso dopo un aggiornamento del sistema, l’attivazione di più versioni di PHP, la presenza di PHP-FPM separati o l’uso di pannelli che gestiscono il CLI in modo indipendente dal runtime web.
Il punto chiave è semplice: il comando eseguito da Cron deve richiamare esplicitamente il binario PHP desiderato. Affidarsi a php “generico” funziona solo se PATH è coerente, e su un ambiente reale non conviene mai darlo per scontato.
Se il sito usa PHP 8.2 via web ma Cron continua a lanciare PHP 7.4, i sintomi tipici sono errori di sintassi su funzioni deprecate, librerie mancanti, differenze nel comportamento di estensioni come intl o mbstring, oppure task che falliscono senza toccare il frontend. La diagnosi va fatta verificando il binario effettivo, non solo la versione “attesa” dal pannello o dal virtual host.
Verifica prima quale PHP vede davvero Cron
La prima cosa da fare è confrontare il PHP disponibile in shell interattiva con quello che Cron userà. In molte installazioni la shell dell’utente e l’ambiente non interattivo di Cron non coincidono, quindi il test va fatto in modo esplicito.
Controlla i binari presenti e la versione esposta da ciascuno:
which php
php -v
ls -l /usr/bin/php /usr/bin/php8.1 /usr/bin/php8.2 /opt/cpanel/ea-php81/root/usr/bin/php 2>/dev/null
Su sistemi con più versioni potresti vedere percorsi diversi in base al gestore installato. Su Debian e Ubuntu spesso i binari stanno in /usr/bin/php8.1, /usr/bin/php8.2 e il link generico /usr/bin/php punta a una sola versione. Su ambienti con cPanel o Plesk il binario può stare in percorsi specifici del pannello.
Per capire che cosa vede Cron, crea un task di prova che scriva versione e percorso del binario in un file temporaneo:
* * * * * /usr/bin/php8.2 -r 'echo PHP_VERSION, " | ", PHP_BINARY, PHP_EOL;' >> /tmp/php-cron-test.log 2>&1
Dopo un minuto, verifica il file:
tail -n 5 /tmp/php-cron-test.log
Se l’output mostra la versione attesa e il percorso corretto, il problema è altrove. Se invece il job non produce nulla, la causa è spesso un path errato, permessi insufficienti o un ambiente troppo scarno per trovare il binario.
La soluzione più robusta: usare il binario assoluto
La pratica corretta è chiamare sempre il PHP con percorso completo. È il modo più stabile per evitare che un update del sistema cambi il comportamento dei task schedulati senza avvisarti.
Esempio base con script PHP lanciato da Cron:
/usr/bin/php8.2 /var/www/example.com/artisan schedule:run >> /var/log/example-schedule.log 2>&1
Se lavori con WordPress, il pattern è simile:
/usr/bin/php8.2 /var/www/example.com/wp-cron.php >> /var/log/wp-cron.log 2>&1
Se il progetto usa Composer, spesso conviene richiamare lo script applicativo tramite il binario PHP del runtime corretto, non tramite php generico. Questo evita anche di ereditare estensioni o opzioni diverse tra CLI e FPM.
Un dettaglio che in hosting fa la differenza: alcuni pannelli offrono una selezione della versione PHP per dominio, ma quella scelta può applicarsi solo a FPM/Apache e non al CLI. In quel caso il cron del pannello può usare un binario diverso da quello del sito. Se il pannello ha un selettore CLI dedicato, va usato quello; altrimenti il percorso assoluto resta la strada più affidabile.
Se vuoi evitare sorprese, isola anche PATH e variabili d’ambiente
Cron non eredita l’ambiente completo della tua shell. Questo significa che alias, funzioni, profile interattivi e spesso anche PATH personalizzato non sono disponibili. Se il tuo script si appoggia a comandi esterni come php, composer, mysql o jq, devi dichiarare ciò che serve in modo esplicito.
Una tecnica pratica è definire PATH all’inizio della crontab, oppure nello script wrapper:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Se il binario PHP non sta in un path standard, puoi evitare ogni ambiguità usando direttamente il percorso completo. È meglio di un PATH troppo largo, perché riduce il rischio di prendere il binario sbagliato dopo un aggiornamento o un cambio di ordine nelle directory.
Per verificare se Cron sta usando l’ambiente che pensi, aggiungi un test temporaneo:
* * * * * env | sort > /tmp/cron-env.txt 2>&1
Poi controlla il file e cerca PATH, SHELL e eventuali variabili applicative. Se mancano, non è un’anomalia: è il comportamento normale di Cron.
Quando la versione PHP è corretta ma il job continua a fallire
Se hai già puntato il binario giusto e il problema resta, il guasto probabilmente non è la versione ma il contesto di esecuzione. Le cause più frequenti sono differenze di php.ini, estensioni non caricate, permessi file, working directory sbagliata o dipendenze da variabili d’ambiente che in Cron non esistono.
Per distinguere il problema, fai stampare al job i dettagli del runtime:
/usr/bin/php8.2 -i | egrep 'Loaded Configuration File|Scan this dir for additional .ini files|memory_limit|date.timezone'
Confronta l’output con quello del PHP usato dal sito. Su molte installazioni il CLI legge un file php.ini diverso da quello di FPM. Questo spiega perché un task schedulato può non trovare una estensione o avere limiti di memoria diversi rispetto alla richiesta web.
Per un controllo più mirato, usa uno script minimale che stampi versione, estensioni e directory corrente:
<?php
printf("version=%s\n", PHP_VERSION);
printf("binary=%s\n", PHP_BINARY);
printf("cwd=%s\n", getcwd());
printf("ext_pdo=%s\n", extension_loaded('pdo') ? 'yes' : 'no');
Salvalo come, per esempio, /var/www/example.com/check.php e lancialo con il binario voluto:
/usr/bin/php8.2 /var/www/example.com/check.php
Se il file funziona a mano ma non da Cron, il problema è quasi sempre nel modo in cui il job viene lanciato: directory errata, utente diverso o output non gestito. In questi casi conviene usare uno script wrapper invece di mettere logica complessa direttamente nella riga di Cron.
Wrapper shell: utile quando il job cresce e non vuoi una crontab fragile
Quando il comando diventa più articolato, è più pulito spostarlo in uno script shell dedicato. Così puoi fissare percorso del PHP, directory di lavoro, log e controlli preliminari in un solo punto versionabile.
Esempio di wrapper:
#!/bin/sh
set -eu
cd /var/www/example.com
exec /usr/bin/php8.2 artisan schedule:run >> /var/log/example-schedule.log 2>&1
Rendi eseguibile il file e richiamalo da Cron:
chmod 750 /usr/local/bin/example-schedule.sh
* * * * * /usr/local/bin/example-schedule.sh
Il vantaggio è pratico: se domani devi cambiare versione PHP, tocchi un solo file. Se vuoi aggiungere un lock per evitare esecuzioni sovrapposte, lo fai lì dentro senza riscrivere tutte le entry di Cron.
Un esempio utile per evitare overlap è usare flock:
* * * * * flock -n /var/lock/example-schedule.lock /usr/bin/php8.2 /var/www/example.com/artisan schedule:run >> /var/log/example-schedule.log 2>&1
Qui il rischio operativo è basso, ma va comunque verificato che il lock file sia scrivibile dall’utente corretto. Se non lo è, il job salta senza eseguire il comando principale.
Su pannelli hosting: dove cercare il selettore giusto
In un ambiente con pannello di controllo, spesso la parte più fragile non è il task in sé, ma il fatto che il pannello distingua tra PHP del sito e PHP da shell. Alcuni pannelli permettono di scegliere il binario per dominio, altri espongono un comando già pronto, altri ancora richiedono la modifica manuale della crontab dell’utente.
Il criterio operativo è questo: cerca la sezione che gestisce i cron dell’utente e verifica se il pannello propone il percorso del binario PHP. Se c’è, usalo. Se non c’è, individua il binario installato dal pacchetto corretto e crea una entry esplicita. Evita di affidarti a link simbolici generici se il sistema ospita più major version.
Nei pannelli moderni spesso è possibile vedere sia il task schedulato sia l’output dell’ultimo run. Quell’output è la prima cosa da leggere quando un job non gira: è più utile di una supposizione sulla versione PHP. Se il pannello conserva i log del cron, controlla l’ultimo errore e confrontalo con il comando lanciato.
Timezone, memoria e limiti: tre dettagli che rompono i job senza fare rumore
Una versione PHP corretta non basta se il job dipende da parametri runtime incoerenti. La timezone è un classico: il sito può essere configurato in una timezone e il CLI in un’altra, con effetti su scadenze, report e sincronizzazioni. Se il tuo script usa date relative o confronti temporali, verifica il valore di date.timezone.
Controlla così:
/usr/bin/php8.2 -i | grep -i '^date.timezone'
La memoria è il secondo punto critico. Un task che nel browser funziona può fallire in cron perché il CLI ha un memory_limit diverso o perché il job elabora più dati senza timeout intermedi. Se serve, imposta il limite in modo esplicito nel comando:
/usr/bin/php8.2 -d memory_limit=512M /var/www/example.com/artisan schedule:run
È preferibile farlo solo dopo aver verificato il consumo reale, non come scorciatoia permanente. Se il job richiede memoria in modo stabile, è meglio adeguare il codice o il batch, non solo alzare il limite.
Infine, attenzione alle estensioni. Il fatto che php -m mostri una libreria non significa che il binario usato da Cron abbia la stessa lista. Controlla con:
/usr/bin/php8.2 -m | sort
Se manca un modulo necessario, la soluzione non è “forzare Cron”, ma installare o abilitare l’estensione nel ramo PHP corretto.
Procedura pratica consigliata, senza ambiguità
Se devi impostare o correggere un cron per usare una versione specifica di PHP, la sequenza operativa più solida è questa.
- Individua il binario esatto del PHP richiesto dal progetto, ad esempio
/usr/bin/php8.2o il percorso fornito dal pannello hosting. - Verifica che il binario risponda con la versione attesa usando
/usr/bin/php8.2 -ve, se serve,/usr/bin/php8.2 -i. - Riscrivi il task di Cron usando il percorso assoluto del binario, non
phpgenerico. - Aggiungi redirezione stdout e stderr verso un file di log dedicato, così puoi leggere subito eventuali errori.
- Se il job dipende da directory, variabili o lock, spostalo in uno script wrapper versionato e lancia quello da Cron.
- Controlla il primo run e confronta output, estensioni, timezone e memoria con il runtime del sito.
Un esempio completo e realistico:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin * * * * * cd /var/www/example.com && /usr/bin/php8.2 artisan schedule:run >> /var/log/example-schedule.log 2>&1
Se il progetto richiede un file di configurazione PHP specifico per il CLI, documentalo chiaramente. In hosting condiviso è facile dimenticare che il cron gira con il profilo utente, ma non con le stesse impostazioni del web server. Questo è il motivo per cui un task può funzionare in staging e fallire in produzione, o viceversa.
Checklist finale per non lasciare il problema in sospeso
Prima di considerare chiuso l’intervento, verifica questi punti in modo esplicito:
- Il comando Cron richiama il binario PHP corretto con percorso assoluto.
- L’output del job finisce in un log leggibile, ad esempio
/var/log/example-schedule.log. - La versione vista da
php -vcoincide con quella usata dal job schedulato. - Le estensioni richieste sono presenti in
php -m. - Timezone e
memory_limitsono coerenti con il comportamento atteso del task. - Se usi un pannello, hai verificato che la selezione PHP valga anche per il CLI o per i cron dell’utente.
Se una di queste verifiche non torna, il problema non è “Cron che non sa usare PHP”, ma una configurazione non allineata tra shell, pannello e runtime applicativo. La correzione strutturale è sempre la stessa: rendere esplicito il binario, ridurre la dipendenza dall’ambiente e controllare il comportamento con un log verificabile.
Assunzione operativa: l’ambiente è Linux recente con accesso shell all’utente che possiede il cron; se il pannello impone percorsi diversi, sostituisci il binario esatto indicato dal provider.
Commenti (0)
Nessun commento ancora.
Segnala contenuto
Elimina commento
Eliminare definitivamente questo commento?
L'azione non si può annullare.