Vai al contenuto principale
enricomorano.it/casi-studio/wordpress-43915-errori-500-al-giorno← ../
▸ Audit e-commerce

Errori 500 su WordPress: 43.915 in un giorno, azzerati

43.915 errori 500 in 24 ore su un WordPress che cedeva solo ogni tanto. Non erano i bot: un plugin di sicurezza ne murava un altro. Diagnosi dai log.

6 agosto 2026#wordpress#errore-500#translatepress#log#hosting

“Il sito ogni tanto da ERROR 500. Non riusciamo a capire il perché.” È con questa frase che è arrivata la richiesta: un sito WordPress trilingue (italiano, inglese, arabo) di un’agenzia di servizi luxury, su hosting condiviso dietro Cloudflare. Le campagne pubblicitarie erano già state spente, pensando fosse un problema di traffico. Nel giorno peggiore quel sito aveva restituito 43.915 errori 500. Nessuno lo sapeva, perché aprendolo nel browser sembrava a posto.

Il traffico non c’entrava. Il 90% delle richieste che il server stava servendo non veniva da nessun visitatore: era il sito che parlava da solo.

⚠️ “Ogni tanto” non era ogni tanto

Prima di formulare qualsiasi ipotesi ho puntato sul sito un monitor esterno con cadenza al minuto. Il primo errore 500 è stato catturato dopo 13 minuti, con data e ora. In 3 ore e 47 minuti:

228  controlli
 26  errori 500 (11,4%)
  8  timeout oltre 15 secondi
  1  errore 525 (l'origine non chiudeva nemmeno l'handshake TLS)

La latenza mediana era 979 ms, ma il 43% delle risposte stava sopra i 2 secondi, con picchi a 13,5 secondi. Lo stesso monitor, nello stesso periodo, puntato su un server sano: 228 controlli, zero errori.

Il primo risultato utile non è il numero: è la riformulazione del problema. Non era un guasto intermittente e imprendibile, era un affanno permanente che ogni tanto sfondava. Una cosa così si misura, e quello che si misura si può chiudere.

⚠️ Le due ipotesi ragionevoli, ed entrambe sbagliate

Dall’esterno tutto puntava su due colpevoli plausibili.

Il primo: la cache non lavorava mai. Ogni risposta HTML settava un cookie di sessione, e sia la cache dell’hosting sia quella di Cloudflare la scavalcavano di conseguenza (SKIP_CACHE_SET_COOKIE, cf-cache-status: DYNAMIC). Il 100% delle richieste di pagina arrivava a PHP e MySQL.

Il secondo: la superficie crawlabile era dieci volte quella dichiarata. Il cliente parlava di “oltre 100 pagine”, la sitemap ne dichiarava 1.029, di cui 615 archivi di tag e categorie moltiplicati per tre dalle lingue. Tutte dinamiche, tutte non cachate, tutte battute dai bot.

Due diagnosi sensate, che avrei potuto vendere senza che nessuno obiettasse. Erano entrambe sbagliate come causa principale, e l’ho scoperto solo aprendo i log.

I bot risulteranno essere lo 0,5% del traffico. Se mi fossi fermato alla ricognizione esterna, avrei fatturato un intervento anti-bot su un problema che non esisteva.

🛠️ La riga che si ripeteva 300 volte

Nell’error log scaricato dal server, 598 righe coprivano 50 secondi. Un solo tipo di errore, ripetuto 300 volte:

[apache][authz_core:error] AH01630: client denied by server configuration:
.../wp-content/plugins/translatepress-multilingual/includes/trp-ajax.php

Con un picco di 48 richieste negate in un solo secondo. Gli IP coinvolti erano 15, tutti italiani residenziali: visitatori veri, non bot. Il referer dominante era la landing inglese su cui atterravano le campagne (256 richieste su 300).

AH01630 significa una cosa sola: una regola di configurazione del server sta negando l’accesso a quel file. Restava da trovare chi l’aveva scritta.

⚠️ La causa: un plugin di sicurezza che ne mura un altro

Entrato via SSH, il colpevole aveva nome, file e data. Il plugin di sicurezza dell’hosting aveva scritto in wp-content/.htaccess, il 12 gennaio, una regola di “Directory Hardening”:

# SGS Directory Hardening
<FilesMatch "\.(?i:php)$">
  <IfModule mod_authz_core.c>
    Require all denied
  </IfModule>
</FilesMatch>
# SGS Directory Hardening END

Nega l’esecuzione di ogni file .php sotto wp-content, sottocartelle comprese. Presa da sola è una misura sensata: è così che si blocca una shell caricata via upload. Solo che ha murato anche trp-ajax.php, che è l’endpoint leggero con cui TranslatePress traduce gli elementi dinamici aprendo una connessione diretta al database, senza caricare WordPress.

Murato quello, TranslatePress fa quello che deve: ripiega su admin-ajax.php, che carica WordPress per intero a ogni singola chiamata. E siccome il file bloccato è anche quello che salva le traduzioni, niente viene mai memorizzato. Ogni visitatore rifà la stessa raffica, all’infinito.

La catena completa, in una riga: hardening nega wp-content/**.php -> trp-ajax.php risponde 403 -> TranslatePress ricade su admin-ajax.php -> ogni chiamata carica WordPress intero -> il tetto di esecuzioni dell’hosting salta -> 500 per tutti, anche per chi sta su una pagina che non c’entra niente.

🛠️ Il numero che chiude il caso

Access log, 24 ore, 519.566 richieste:

EndpointRichiesteQuota
trp-ajax.php (tutte negate, 403)236.32745,5%
admin-ajax.php (il fallback pesante)232.85944,8%
Somma del loop469.18690,3%

Nelle stesse 24 ore: 222.091 risposte buone, 233.631 errori 403, 33.146 errori 500 e 24.765 status 499, cioè visitatori che chiudono la pagina prima di ricevere una risposta. I bot dichiarati erano 2.659 su 519.566: lo 0,5%.

Il loop era cronico dalla regola di gennaio, sempre nella stessa proporzione. Quello che cambiava era il volume, ed è esattamente qui che sta la spiegazione del “ogni tanto”:

GiornoRichiesteErrori 500
27 luglio104.5410
28 luglio309.44118.767
29 luglio333.65343.915
30 luglio519.56633.146

Il margine del server era già mangiato al 90% dal lavoro inutile. Nelle giornate fiacche reggeva e il sito sembrava perfetto; nelle giornate buone sfondava. Il sito si rompeva esattamente quando serviva, cioè quando c’era gente sopra e le campagne portavano click.

✅ Il fix: un file, dodici righe, zero modifiche all’esistente

Nessuna riga di codice toccata, nessun plugin disattivato, nessun hardening rimosso. Un file nuovo dentro la cartella del plugin di traduzione:

<Files "trp-ajax.php">
  <IfModule mod_authz_core.c>
    Require all granted
  </IfModule>
</Files>

La scelta della posizione è la parte che conta: non in wp-content/.htaccess, perché quel file lo riscrive il plugin di sicurezza a ogni cambio di impostazione, e il fix sarebbe sparito al primo click nel pannello. Messo nella cartella dell’endpoint, l’hardening resta attivo su tutto il resto del sito: si apre solo la porta che serve.

Prima di aprire al mondo un file PHP mi sono posto la domanda ovvia: quel file è davvero del plugin, o ce l’ha messo qualcun altro? Verificato con il pacchetto ufficiale scaricato da wordpress.org: MD5 identico, 249 righe entrambi. Aggiunti i controlli di integrità del core e di tutti i plugin, la ricerca di PHP dentro uploads e dei pattern tipici da backdoor: nessuna alterazione. Il sito non era compromesso.

Verifica funzionale in due colpi: GET sull’endpoint da 403 a 200, e POST con il formato reale del frontend che risponde 200 in 0,69 secondi con le traduzioni vere, senza caricare WordPress.

✅ Il risultato misurato

La prova più netta è la tabella oraria dell’access log del giorno del fix, applicato alle 13:37 UTC:

Ora UTCRichiesteadmin-ajaxtrp-ajaxErrori 500
0858.73926.40427.1517.225
0957.47625.03825.3329.157
1259.19626.72727.8151.395
13 (fix)49.69917.48828.456683
1430.3882.71324.8820
1522.5331.81917.9750
169.5235827.8490

Due cose che questa tabella dimostra da sola. La prima: gli errori 500 non calano, si fermano di netto nell’ora del fix, e restano a zero per tutte le ore successive. La seconda: il rapporto fra i due endpoint si rovescia. Prima erano appaiati, dopo trp-ajax regge da solo il suo volume mentre admin-ajax crolla del 90%. Non è calato il traffico: è calato il lavoro inutile.

Il monitor esterno, stessa sonda e stessa URL prima e dopo, dice la stessa cosa dal lato del visitatore:

Home, stessa URLCheckErroriMedianap90
Prima del fix27736 (13%)1.001 ms7.592 ms
Dopo il fix1290558 ms828 ms

Mediana quasi dimezzata e p90 crollata da 7,6 secondi a 0,8: sparisce proprio la coda lunga che faceva sembrare il sito “a volte” rotto.

Il primo giorno interamente successivo al fix ha chiuso con 221.097 richieste e zero errori 500, con trp-ajax che risponde 200 e nessun 403 residuo. Il file di log giornaliero, che era passato da 1,3-1,9 MB di inizio mese a 7,2 MB nel giorno peggiore, è tornato a 3,1 MB.

🛠️ Il debito che resta, e perché va sorvegliato

Questo fix ha una scadenza nota: al primo aggiornamento di TranslatePress la cartella del plugin viene ricreata e il file di sblocco sparisce. Il loop riparte identico, e con lui i 500.

Per questo insieme al fix ho lasciato acceso un canarino: un controllo al minuto puntato direttamente sull’endpoint, che non guarda il contenuto ma solo lo status. Finché risponde 200 il fix è in sede; se torna 403, il problema è ripartito e si sa prima che lo scoprano i clienti. Nelle prime 24 ore: 931 controlli, zero errori, mediana 73 millisecondi, che è anche la conferma che l’endpoint non passa più da WordPress intero. Da lì è nata la parte di continuità del lavoro, con controlli periodici invece di una singola riparazione.

Un problema che si ripresenta a ogni aggiornamento non è risolto: è rimandato. La differenza fra chi lo sa e chi lo scopre dai clienti persi è un controllo automatico che costa poco.

Cosa porta via chi legge

Tre cose, in ordine di utilità.

“Ogni tanto va giù” non è mai un mistero, è un log che nessuno ha aperto. In questo caso la causa era scritta per esteso nell’error log, 300 volte in 50 secondi, da mesi. Bastava guardare.

Le ipotesi ragionevoli sono le più pericolose. Cache bucata e bot sulle URL dinamiche erano diagnosi vendibili e tecnicamente vere: erano problemi reali, ma di secondo ordine. Senza i log avrei speso il budget del cliente sul contorno lasciando intatta la causa.

Il rischio vive fra i plugin, non dentro. Nessuno dei due software qui aveva un bug. Il primo blocca l’esecuzione di PHP dove non dovrebbe servire, il secondo mette un endpoint PHP esattamente lì. Ognuno fa il suo mestiere, e insieme mandano giù il sito. È il motivo per cui su un WordPress con dieci plugin la documentazione dei plugin non basta.

Il tuo sito va in 500 “ogni tanto” e nessuno sa dire perché

Se il sito si apre normalmente ma di tanto in tanto restituisce errori, quasi sempre la causa è già scritta nei log del server e nessuno li ha letti. Lavoro soprattutto su e-commerce PrestaShop, ma la diagnosi dai log è la stessa su WordPress: si misura prima, si escludono i sospetti con una prova ciascuno, si tocca solo quello che serve. È il metodo che uso nell’audit tecnico di un sito e-commerce. Raccontamelo da uno dei canali qui sotto, meglio se con gli access log degli ultimi giorni: ti dico se è diagnosticabile.

❓ Domande frequenti

Perché un sito WordPress da errore 500 solo ogni tanto?
Quasi sempre perché c'è un consumo di risorse cronico che mangia il margine del server, e il crollo arriva solo quando il traffico normale ci si somma sopra. In questo caso il 90% delle richieste giornaliere era un loop fra due plugin: nelle giornate fiacche il server reggeva e il sito sembrava perfetto, in quelle buone restituiva decine di migliaia di errori. L'intermittenza non era del guasto, era del volume.
Un plugin di sicurezza può rompere il sito?
Sì, e senza avere alcun bug. Il Directory Hardening blocca l'esecuzione di ogni file PHP sotto wp-content per impedire le shell caricate via upload: è una regola corretta. Diventa un problema quando un altro plugin, in questo caso quello delle traduzioni, mette lì un endpoint legittimo che gli serve a ogni pagina. La soluzione non è disattivare la protezione, ma sbloccare il singolo file lasciando l'hardening su tutto il resto.
Servono per forza gli accessi al server per capire un errore 500?
Dall'esterno si arriva a misurare il problema e a formulare ipotesi: un monitor al minuto dice quanto spesso accade e quanto è lento il sito, e gli header dicono se la cache lavora. Ma la causa era una riga dell'error log del server, non visibile da fuori. Senza log si tira a indovinare, e in questo caso le due ipotesi esterne più plausibili erano entrambe sbagliate.
Enrico Morano
Sviluppatore PrestaShop freelance da quasi 10 anni - debug, performance, moduli custom

Hai un progetto in testa?

Raccontami cosa ti serve o cosa non funziona. Rispondo entro 24 ore, senza preventivi a scatola chiusa.

Descrivi il tuo progetto →il form guidato, 2 minuti
Scrivimi una mailemail
Scrivimi su WhatsAppdi solito rispondo in giornata