In un server Linux non managed, i log sono l’unica fonte di verità sullo stato di salute del sistema. Ogni applicativo — dal web server al database, da PHP al firewall — produce registri che documentano errori, warning e anomalie. Ignorarli significa operare alla cieca; analizzarli sistematicamente permette di intercettare problemi prima che impattino sulle performance o sulla disponibilità del sito.
Questa guida documenta il processo di analisi che eseguo periodicamente sui server che gestisco: per ogni log trovi la posizione nel filesystem, la configurazione corretta, i pattern di errore da cercare e i comandi per automatizzare il monitoraggio. L’ambiente di riferimento è uno stack LEMP (Linux, Nginx, MySQL, PHP-FPM) su Ubuntu, ma la metodologia è applicabile a qualsiasi server Linux con adattamenti minimi.
Architettura dei log in Linux
Prima di analizzare i singoli log, è fondamentale comprendere come Linux gestisce il logging a livello di sistema. Nelle distribuzioni moderne basate su systemd, coesistono due meccanismi principali.
systemd-journald è il sistema di logging binario integrato in systemd. Raccoglie i log di tutti i servizi gestiti tramite unit file (Nginx, MySQL, PHP-FPM, cron) e li archivia in formato binario in /var/log/journal/. Si interroga con journalctl.
rsyslog/syslog è il demone di logging tradizionale che scrive file di testo in /var/log/. Molti applicativi scrivono direttamente i propri log in questa directory, indipendentemente da journald.
La struttura tipica di /var/log/ su un server LEMP include:
/var/log/
├── syslog # Log di sistema generale
├── auth.log # Autenticazione SSH, sudo
├── nginx/
│ ├── access.log # Richieste HTTP
│ └── error.log # Errori web server
├── mysql/
│ └── error.log # Errori database
├── php8.3-fpm.log # Errori PHP-FPM
├── fail2ban.log # Ban e unban IP
├── ufw.log # Firewall
├── letsencrypt/
│ └── letsencrypt.log # Rinnovo certificati
└── journal/ # Log binari systemd
Logrotate è il componente che gestisce la rotazione automatica dei log, evitando che i file crescano indefinitamente. La configurazione globale è in /etc/logrotate.conf, mentre le regole per i singoli servizi si trovano in /etc/logrotate.d/. Verifica che la rotazione sia attiva per tutti i log critici:
# Verifica configurazione logrotate per nginx
cat /etc/logrotate.d/nginx
# Test manuale della rotazione (dry-run)
logrotate -d /etc/logrotate.d/nginx
# Forza rotazione immediata
logrotate -f /etc/logrotate.d/nginx
Un errore comune è non configurare logrotate per i log applicativi custom (come il debug.log di WordPress), lasciandoli crescere fino a occupare tutto lo spazio disco disponibile.
WordPress debug.log
Percorso: /var/www/sito/wp-content/debug.log
Il debug log di WordPress registra errori PHP, notice e warning generati dal core, dai plugin e dal tema. Per abilitarlo, aggiungi queste costanti in wp-config.php:
// Abilita modalità debug
define( 'WP_DEBUG', true );
// Salva gli errori in /wp-content/debug.log
define( 'WP_DEBUG_LOG', true );
// Non mostrare errori nel frontend (MAI in produzione)
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );
Nota: in produzione WP_DEBUG_DISPLAY deve essere sempre false. Mostrare errori PHP nel frontend espone informazioni sensibili (path del filesystem, versioni software, nomi di tabelle del database) e compromette l’esperienza utente.
Cosa cercare nel debug.log
Non tutti gli errori hanno lo stesso peso. Ecco i pattern critici da identificare:
- Fatal error — Il processo PHP si interrompe. Causa pagine bianche o errori 500. Richiede intervento immediato.
- Warning — Qualcosa non funziona come previsto ma l’esecuzione continua. Spesso causato da funzioni deprecate nei plugin.
- Notice: Undefined variable/index — Codice che accede a variabili non inizializzate. Comune nei plugin scritti male, raramente critico ma segnale di scarsa qualità del codice.
- Deprecated — Funzioni PHP o WordPress che verranno rimosse nelle versioni future. Da risolvere prima degli aggiornamenti major.
Comandi per un’analisi rapida:
# Conta errori per tipologia
grep -oP '(Fatal error|Warning|Notice|Deprecated)' debug.log | sort | uniq -c | sort -rn
# Ultimi 50 errori con timestamp
tail -50 debug.log
# Errori fatali delle ultime 24 ore
awk -v d="$(date -d '24 hours ago' '+%d-%b-%Y')" '$0 ~ d && /Fatal error/' debug.log
# Plugin che generano più errori
grep -oP 'plugins/\K[^/]+' debug.log | sort | uniq -c | sort -rn | head -10
L’ultimo comando è particolarmente utile: identifica quali plugin sono responsabili della maggior parte degli errori, permettendo di valutare se sostituirli o aggiornarli con priorità.
Nota: configura logrotate per il debug.log. Senza rotazione, su siti con molto traffico questo file può raggiungere diversi GB in poche settimane. Crea il file /etc/logrotate.d/wordpress-debug:
/var/www/sito/wp-content/debug.log {
weekly
rotate 4
compress
missingok
notifempty
create 0640 www-data www-data
}
PHP-FPM error log
Percorso: /var/log/php8.3-fpm.log (il nome varia con la versione PHP installata)
Il log di PHP-FPM registra errori a livello di processo PHP, non catturati dal debug.log di WordPress. Include crash dei worker, errori di memoria, timeout e problemi di configurazione del pool.
Pattern critici
- WARNING: [pool www] child exited on signal 11 (SIGSEGV) — Segmentation fault di un worker PHP. Indica un bug nel codice PHP, in un’estensione o un problema di memoria. Se ricorrente, è un problema grave.
- WARNING: [pool www] server reached pm.max_children — Tutti i processi PHP sono occupati, le nuove richieste vengono messe in coda. Segnala che il pool è sottodimensionato rispetto al traffico o che alcune richieste sono troppo lente.
- NOTICE: [pool www] child PID exited with code 255 — Un worker è terminato con errore. Spesso associato a fatal error PHP o esaurimento memoria.
- WARNING: [pool www] child PID has been slow to allocate memory — Segnale di pressione sulla memoria del sistema.
Configurazione del pool PHP-FPM
Il file di configurazione del pool si trova in /etc/php/8.3/fpm/pool.d/www.conf. I parametri chiave da monitorare:
# Visualizza lo stato del pool in tempo reale
# (richiede pm.status_path configurato in www.conf)
curl -s http://127.0.0.1/status?full
# Verifica processi PHP-FPM attivi
ps aux | grep php-fpm | grep -v grep | wc -l
# Memoria totale usata da PHP-FPM
ps -C php-fpm8.3 -o rss= | awk '{sum+=$1} END {printf "%.0f MB\n", sum/1024}'
Se il log mostra frequenti max_children reached, calcola il numero ottimale di worker con questa formula:
# Memoria media per processo PHP-FPM (in MB)
ps -C php-fpm8.3 -o rss= | awk '{sum+=$1; n++} END {printf "Media: %.0f MB\n", sum/n/1024}'
# Formula: max_children = (RAM disponibile per PHP) / (memoria media per processo)
# Esempio: 2048 MB disponibili / 64 MB per processo = 32 max_children
Nginx error log e access log
Percorso: /var/log/nginx/error.log e /var/log/nginx/access.log
Il log degli errori Nginx registra problemi di configurazione, errori upstream (PHP-FPM), timeout e problemi di connessione. Ogni entry ha un livello di severità che segue una scala definita:
| Livello | Significato | Azione richiesta |
|---|---|---|
| emerg | Sistema inutilizzabile | Intervento immediato |
| alert | Azione immediata necessaria | Intervento urgente |
| crit | Condizioni critiche | Risolvere al più presto |
| error | Condizioni di errore | Investigare e risolvere |
| warn | Condizioni anomale non bloccanti | Monitorare |
| notice | Evento normale significativo | Informativo |
| info | Messaggio informativo | Informativo |
| debug | Informazioni di debug | Solo in sviluppo |
Il livello di logging si configura nella direttiva error_log del file /etc/nginx/nginx.conf:
# Livello default: error. Per debug temporaneo usa warn o notice
error_log /var/log/nginx/error.log warn;
Errori Nginx più comuni
- upstream timed out (110: Connection timed out) — PHP-FPM non risponde entro il timeout configurato. Causa: script PHP troppo lenti, pool PHP saturo, o query MySQL bloccanti. Verifica
fastcgi_read_timeoute il log PHP-FPM. - connect() to unix:/run/php/php-fpm.sock failed (2: No such file or directory) — Il socket PHP-FPM non esiste. PHP-FPM è crashato o non è in esecuzione. Controlla con
systemctl status php8.3-fpm. - connect() to unix:/run/php/php-fpm.sock failed (11: Resource temporarily unavailable) — Il backlog del socket è pieno. Tutti i worker PHP sono occupati. Aumenta
listen.backloginwww.confepm.max_children. - open() “/var/www/…/file” failed (2: No such file or directory) — Risorsa richiesta inesistente. Se sono file statici (immagini, CSS, JS) può indicare link rotti o file mancanti dopo un deploy.
- SSL_do_handshake() failed — Problema con il certificato TLS. Verifica validità e catena del certificato.
# Conta errori per tipologia nelle ultime 24h
awk -v d="$(date -d '24 hours ago' '+%Y/%m/%d')" '$0 >= d' /var/log/nginx/error.log | \
grep -oP '\[(\w+)\]' | sort | uniq -c | sort -rn
# Top 10 URL che generano errori 404 (da access.log)
awk '$9 == 404 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
# Richieste per status code
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
Per un’analisi visuale dell’access log, GoAccess resta lo strumento migliore. Genera report HTML interattivi direttamente dal terminale:
# Report HTML con analisi completa
goaccess /var/log/nginx/access.log -o /var/www/report.html --log-format=COMBINED
# Monitoraggio in tempo reale
goaccess /var/log/nginx/access.log --log-format=COMBINED
Per approfondire l’analisi del log del web server, inclusa l’identificazione di pattern di crawling dei bot, consulta la guida all’analisi del log del web server.
Se dall’access log emergono volumi anomali da user-agent come facebookexternalhit, meta-externalagent o Amazonbot, il passo successivo è la diagnosi e mitigazione dei crawler Meta e Amazon: bot legittimi che possono generare carichi da DDoS.
MySQL error log e slow query log
Percorso: /var/log/mysql/error.log
MySQL (o MariaDB) produce tre log distinti, ciascuno con uno scopo specifico:
- Error log — Errori di avvio/arresto del server, crash, problemi di replicazione e corruzione delle tabelle. Sempre abilitato di default.
- General query log — Registra ogni singola query eseguita. Utile solo per debug temporaneo perché genera un volume enorme di dati e impatta le performance.
- Slow query log — Registra le query che superano una soglia di tempo configurabile. È lo strumento più utile per l’ottimizzazione delle performance del database.
Abilitare lo slow query log
Lo slow query log è il singolo strumento più efficace per identificare colli di bottiglia nel database. Per abilitarlo, aggiungi queste righe in /etc/mysql/mysql.conf.d/mysqld.cnf (MySQL 8.x) o /etc/mysql/mariadb.conf.d/50-server.cnf (MariaDB):
[mysqld]
# Abilita slow query log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
# Soglia in secondi (query più lente di questo valore vengono loggate)
long_query_time = 1
# Logga anche query che non usano indici
log_queries_not_using_indexes = 1
# Limita il rate delle query senza indice per evitare flood del log
log_throttle_queries_not_using_indexes = 60
Dopo la modifica, riavvia il servizio:
systemctl restart mysql
Analisi dello slow query log
Il tool nativo mysqldumpslow aggrega le query lente per pattern, ma per analisi più approfondite pt-query-digest (parte del toolkit Percona) è nettamente superiore:
# Analisi con mysqldumpslow (incluso in MySQL)
mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log
# Analisi avanzata con pt-query-digest (Percona Toolkit)
pt-query-digest /var/log/mysql/mysql-slow.log
# Installazione Percona Toolkit su Ubuntu
apt install percona-toolkit
pt-query-digest produce un report dettagliato che mostra per ogni query: tempo totale, tempo medio, numero di esecuzioni e distribuzione. Le query in cima al report sono le candidate principali per l’ottimizzazione — spesso basta aggiungere un indice mancante per risolvere il problema.
Errori critici nel MySQL error log
- InnoDB: page_cleaner: 1000ms intended loop took X ms — Il sistema I/O è sotto stress. Disco lento o troppe operazioni di scrittura concorrenti.
- Table ‘wp_options’ is marked as crashed — Tabella corrotta, richiede riparazione con
REPAIR TABLEomysqlcheck. - Too many connections — Superato il limite
max_connections. Può indicare un pool di connessioni PHP mal configurato o query troppo lente che tengono occupate le connessioni. - Aborted connection — Connessioni interrotte dal client. Frequente con timeout PHP-FPM troppo bassi o instabilità di rete.
Systemd journal e cron
Il journal di systemd è il punto di accesso centralizzato per i log di tutti i servizi gestiti dal sistema init. A differenza dei file di testo tradizionali, il journal è un database binario indicizzato che permette query strutturate.
Comandi journalctl essenziali
# Log di un servizio specifico
journalctl -u nginx --since "1 hour ago"
journalctl -u php8.3-fpm --since today
journalctl -u mysql --since "2024-01-01" --until "2024-01-02"
# Solo errori e warning (priorità 0-4)
journalctl -p err --since "24 hours ago"
# Follow in tempo reale (equivalente di tail -f)
journalctl -u nginx -f
# Spazio occupato dal journal
journalctl --disk-usage
# Pulizia log più vecchi di 30 giorni
journalctl --vacuum-time=30d
Monitoraggio cron job
I cron job registrano la propria esecuzione nel syslog. Se un job critico fallisce silenziosamente (exit code non zero senza output stderr), potresti non accorgertene per giorni. Per monitorare efficacemente:
# Filtra log cron dal journal
journalctl -u cron --since "24 hours ago"
# Filtra solo cron da syslog (sistemi senza journal persistente)
grep CRON /var/log/syslog | tail -50
# Verifica che il cron di WordPress (wp-cron) venga eseguito
grep wp-cron /var/log/nginx/access.log | tail -5
Nota: se usi un cron di sistema al posto di wp-cron (pratica consigliata per siti ad alto traffico), assicurati di disabilitare wp-cron in wp-config.php con define('DISABLE_WP_CRON', true); e configura il cron di sistema per chiamare wp-cron.php a intervalli regolari.
Log di sicurezza: Fail2ban, UFW e auth.log
I log di sicurezza meritano un’attenzione particolare. Un server esposto su internet riceve tentativi di accesso non autorizzato costantemente — l’analisi di questi log permette di valutare l’efficacia delle misure di protezione attive.
auth.log
Percorso: /var/log/auth.log
Registra tutti i tentativi di autenticazione: login SSH (riusciti e falliti), comandi sudo e altre operazioni che richiedono credenziali.
# Tentativi SSH falliti
grep "Failed password" /var/log/auth.log | tail -20
# IP con più tentativi falliti
grep "Failed password" /var/log/auth.log | grep -oP '\d+\.\d+\.\d+\.\d+' | sort | uniq -c | sort -rn | head -10
# Login SSH riusciti
grep "Accepted" /var/log/auth.log | tail -10
# Tentativi di accesso con utenti inesistenti
grep "Invalid user" /var/log/auth.log | grep -oP 'user \K\w+' | sort | uniq -c | sort -rn | head -10
Fail2ban
Percorso: /var/log/fail2ban.log
Se hai Fail2ban attivo per la protezione del server, il suo log mostra gli IP bannati e unbannati per ogni jail configurata.
# IP attualmente bannati per ogni jail
fail2ban-client status sshd
fail2ban-client status nginx-http-auth
# Statistiche ban delle ultime 24 ore
grep "Ban" /var/log/fail2ban.log | awk -v d="$(date -d '24 hours ago' '+%Y-%m-%d')" '$0 >= d' | wc -l
# Top IP bannati
grep "Ban" /var/log/fail2ban.log | grep -oP '\d+\.\d+\.\d+\.\d+' | sort | uniq -c | sort -rn | head -10
UFW firewall
Percorso: /var/log/ufw.log
Se hai UFW abilitato come firewall, il log registra i pacchetti bloccati. Un picco anomalo di blocchi può indicare un attacco in corso o una misconfiguration:
# Porte più bersagliate
grep "UFW BLOCK" /var/log/ufw.log | grep -oP 'DPT=\K\d+' | sort | uniq -c | sort -rn | head -10
# IP più bloccati
grep "UFW BLOCK" /var/log/ufw.log | grep -oP 'SRC=\K\d+\.\d+\.\d+\.\d+' | sort | uniq -c | sort -rn | head -10
Certificati TLS: Certbot e Let’s Encrypt
Percorso: /var/log/letsencrypt/letsencrypt.log
I certificati Let’s Encrypt hanno una validità di 90 giorni e vengono rinnovati automaticamente da Certbot tramite un timer systemd o un cron job. Un rinnovo fallito silenziosamente porta alla scadenza del certificato e a errori SSL che rendono il sito inaccessibile.
# Verifica stato rinnovo automatico
systemctl status certbot.timer
# Verifica scadenza di tutti i certificati
certbot certificates
# Simula rinnovo (dry run) per verificare che tutto funzioni
certbot renew --dry-run
# Controlla il log per errori recenti
tail -100 /var/log/letsencrypt/letsencrypt.log | grep -i "error\|fail\|unable"
Gli errori più comuni nel rinnovo riguardano la validazione HTTP-01: Certbot deve poter creare un file in .well-known/acme-challenge/ e il web server deve servirlo correttamente. Configurazioni Nginx restrittive o regole di redirect mal scritte possono bloccare il processo. Se gestisci certificati per più domini sulla stessa macchina, consulta la guida al setup del server LEMP per la configurazione corretta.
Automazione: script di health check
Analizzare i log manualmente è utile per il debug, ma il monitoraggio deve essere automatizzato. Uno script bash eseguito via cron può verificare lo stato di tutti i servizi critici e inviare un alert quando qualcosa non funziona:
#!/bin/bash
# health-check.sh — Verifica stato servizi e log critici
# Eseguire via cron: 0 */6 * * * /root/scripts/health-check.sh
ALERT_EMAIL="[email protected]"
HOSTNAME=$(hostname)
ISSUES=""
# 1. Verifica servizi attivi
for svc in nginx php8.3-fpm mysql; do
if ! systemctl is-active --quiet "$svc"; then
ISSUES+="CRITICAL: $svc non attivo\n"
fi
done
# 2. Verifica spazio disco (alert sopra 85%)
DISK_USAGE=$(df / --output=pcent | tail -1 | tr -dc '0-9')
if [ "$DISK_USAGE" -gt 85 ]; then
ISSUES+="WARNING: Disco al ${DISK_USAGE}%\n"
fi
# 3. Errori fatali PHP nelle ultime 6 ore
FATAL_COUNT=$(grep -c "Fatal error" /var/www/sito/wp-content/debug.log 2>/dev/null | \
awk -v d="$(date -d '6 hours ago' '+%d-%b-%Y %H')" '{print}')
if [ "${FATAL_COUNT:-0}" -gt 0 ]; then
ISSUES+="WARNING: $FATAL_COUNT errori fatali PHP nelle ultime 6h\n"
fi
# 4. Verifica certificato SSL (alert sotto 14 giorni)
CERT_EXPIRY=$(echo | openssl s_client -connect localhost:443 -servername $(hostname -f) 2>/dev/null | \
openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -n "$CERT_EXPIRY" ]; then
DAYS_LEFT=$(( ($(date -d "$CERT_EXPIRY" +%s) - $(date +%s)) / 86400 ))
if [ "$DAYS_LEFT" -lt 14 ]; then
ISSUES+="WARNING: Certificato SSL scade tra $DAYS_LEFT giorni\n"
fi
fi
# 5. Verifica max_children raggiunto (PHP-FPM)
if grep -q "max_children" /var/log/php8.3-fpm.log 2>/dev/null; then
MC_COUNT=$(grep -c "max_children" /var/log/php8.3-fpm.log)
ISSUES+="WARNING: pm.max_children raggiunto $MC_COUNT volte\n"
fi
# Invia alert se ci sono problemi
if [ -n "$ISSUES" ]; then
echo -e "Health check $HOSTNAME — $(date)\n\n$ISSUES" | \
mail -s "[$HOSTNAME] Health Check Alert" "$ALERT_EMAIL"
fi
Questo script è un punto di partenza. In un ambiente di produzione con più server, conviene adottare un sistema di monitoraggio strutturato piuttosto che script personalizzati.
Stack di monitoraggio centralizzato
Per chi gestisce più server o vuole superare il monitoraggio manuale, esistono stack dedicati all’aggregazione e visualizzazione dei log. Le opzioni principali:
Promtail + Loki + Grafana
Stack leggero sviluppato da Grafana Labs. Promtail è l’agent che raccoglie i log e li invia a Loki, un datastore ottimizzato per log che indicizza solo i metadati (label) e non il contenuto completo, rendendolo molto più efficiente in termini di storage rispetto a Elasticsearch. Grafana fornisce l’interfaccia di query e visualizzazione con il linguaggio LogQL.
È la soluzione che consiglio per server singoli o piccoli cluster: basso consumo di risorse, facile da installare e mantenere, integrazione nativa con le dashboard Grafana che probabilmente usi già per le metriche di sistema.
ELK Stack (Elasticsearch, Logstash, Kibana)
Lo standard de facto per log aggregation in ambienti enterprise. Potente ma pesante: Elasticsearch richiede significative risorse di RAM e CPU. Giustificato solo per volumi elevati di log (decine di GB al giorno) o quando servono funzionalità avanzate di full-text search e analytics sui log.
Alternative lightweight
Per chi vuole un monitoraggio base senza la complessità di uno stack completo:
- Logwatch — Genera report giornalieri via email con un riepilogo delle attività di sistema. Zero configurazione, installabile con
apt install logwatch. - GoAccess — Analisi real-time dei log del web server con report HTML interattivi. Ideale per monitorare access log Nginx e Apache.
- Monit — Monitora processi, filesystem e risorse di sistema con restart automatico dei servizi che crashano.
Indipendentemente dallo strumento scelto, l’obiettivo è lo stesso: passare da un approccio reattivo (il sito è giù, guardo i log) a uno proattivo (il log mi avvisa prima che il sito vada giù). La differenza tra un server che funziona e un server che funziona bene sta nella capacità di individuare e risolvere i problemi prima che diventino visibili agli utenti.
Articoli correlati
Autore
Mi chiamo Giovanni Sacheli e dal 2009 aiuto le aziende a farsi trovare online. Sono specializzato in SEO tecnica e PPC, competenze che applico quotidianamente nella mia agenzia, Searcus Swiss Sagl. Mi piace sviluppare strumenti a supporto del mio lavoro, ho creato SEOdata.app e cluster.army e co-scritto il libro SEO Audit Avanzato. Curo maniacalmente questo blog per colleghi e appassionati, dove mi "appunto" quello che imparo. Sono un NERD anni '80, motociclista e orgoglioso papà di due bambini.
Link:
Giovanni Sacheli
SEO Audit Avanzato
Searcus Swiss Sagl
SEOdata.app
cluster.army