Se la home di WordPress si carica ma articoli, pagine e categorie restituiscono 404, o la richiesta non arriva mai a index.php (in Nginx manca il try_files corretto, in Apache manca il .htaccess, mod_rewrite è disattivato oppure AllowOverride è None), oppure WordPress non ha alcuna regola di riscrittura per quell’URL. Per prima cosa scoprite chi ha generato la pagina 404: se non ha la grafica del tema, va corretto il server web; se è la pagina 404 del vostro tema, eseguite wp rewrite flush (oppure salvate Impostazioni → Permalink) e cercate un plugin che rompe le regole. Abbiamo riprodotto sei cause su un banco di prova; qui sotto trovate i codici di risposta reali e le correzioni che abbiamo verificato.
Come abbiamo testato
- Ambiente: banco di prova RichHost, container isolato con 2 vCPU / 8 GB, Ubuntu 24.04, nginx 1.24, PHP 8.3-FPM, MariaDB 10.11, WordPress 6.7.2, tema Twenty Twenty-Five, permalink /%postname%/. Per i test con Apache abbiamo installato temporaneamente Apache 2.4.58 su una porta separata. Data: ottobre 2026.
- Abbiamo rotto una copia separata del sito con un proprio database (nginx sulla porta 8090, Apache sulla 8091), così il banco principale è rimasto intatto. La copia, il suo database e Apache sono stati poi rimossi.
- Ogni stato è stato verificato con curl: codice di risposta, numero di occorrenze di wp-content nell’HTML (segno che la pagina è stata generata da WordPress) e intestazione X-Cache. L’output è abbreviato.
- Sono risultati del banco di prova, non misurazioni dei server di produzione di RichHost.
Primo passo: chi ha restituito il 404, il server o WordPress
Prendete un qualsiasi URL di articolo che restituisce 404 ed eseguite tre comandi:
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/post-name/
curl -s https://example.com/post-name/ | grep -c wp-content
curl -sI "https://example.com/?p=123" | grep -iE "^HTTP|^location"
Sul banco, la pagina 404 di nginx o di Apache conteneva 0 occorrenze di wp-content, mentre la pagina 404 di Twenty Twenty-Five ne conteneva 4. Zero significa che la richiesta non è mai arrivata a PHP e bisogna correggere la configurazione del server web. Un numero maggiore di zero significa che WordPress ha ricevuto la richiesta ma non ha trovato una regola corrispondente.
Il controllo con l’indirizzo semplice /?p=ID è utile, ma non si comporta come dicono molte guide: non restituisce 200, reindirizza (301) all’URL leggibile. È successo in tutti gli scenari che abbiamo provato: /?p=304 restituiva 301 verso /post-300/, che poi restituiva 404. Il reindirizzamento stesso dimostra che PHP e il database funzionano e che a essere rotta è la riscrittura degli URL.

Causa 1. Nginx senza il try_files corretto
Nginx ignora il .htaccess, quindi tutto dipende dal blocco location /. Abbiamo fatto girare la copia del sito con tre versioni di questo blocco:
| URL | try_files $uri $uri/ /index.php?$args; |
try_files $uri $uri/ =404; |
senza location / |
|---|---|---|---|
| / | 200 | 200 | 200 |
| /post-300/ | 200 | 404 | 404 |
| /sample-page/ | 200 | 404 | 404 |
| /category/uncategorized/ | 200 | 404 | 404 |
| /wp-json/ | 200 | 404 | 404 |
| /?p=304 | 301 | 301 | 301 |
La home funziona sempre perché per “/” nginx trova index.php tramite la direttiva index. La riga try_files $uri $uri/ =404; arriva dal sito predefinito di Ubuntu, ed è per questo che finisce spesso nelle configurazioni di WordPress. Con questa riga error.log restava vuoto, quindi il log non vi porterà alla causa. Senza alcun blocco location /, invece, il log dà un indizio:
[error] ... "/var/www/r10wp/sample-page/index.php" is not found (2: No such file or directory), request: "GET /sample-page/ HTTP/1.1"
Anche /wp-json/ smetteva di funzionare, quindi l’editor a blocchi non riesce a salvare gli articoli. Intanto /?rest_route=/ restituiva 200: la REST API funziona, fallisce solo la riscrittura. Il blocco con cui sul banco ogni URL ha restituito 200:
server {
listen 80;
server_name example.com;
root /var/www/example.com/public_html;
index index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
}
sudo nginx -T | grep -n try_files
sudo nginx -t && sudo systemctl reload nginx
Sostituite la versione di PHP nel percorso del socket con la vostra. Altri blocchi Nginx, compresi i reindirizzamenti verso https e verso l’host senza www, sono raccolti nei nostri esempi pronti di redirect 301.
Causa 2. Apache: .htaccess mancante, mod_rewrite disattivato o AllowOverride None
Per questa parte abbiamo installato Apache 2.4.58 con PHP-FPM su una porta separata per la stessa copia del sito. Quattro guasti diversi hanno prodotto quasi lo stesso risultato:
| Stato | Home | Articolo | Log degli errori di Apache |
|---|---|---|---|
| Nessun .htaccess | 200 | 404 | vuoto |
| .htaccess con il blocco IfModule, mod_rewrite disattivato | 200 | 404 | vuoto |
| .htaccess senza IfModule, mod_rewrite disattivato | 500 | 500 | Invalid command ‘RewriteEngine’ |
| mod_rewrite attivo, ma AllowOverride None | 200 | 404 | vuoto |
| mod_rewrite attivo, AllowOverride All | 200 | 200 | nessun errore |
Quindi una semplice pagina “Not Found” di Apache con il log vuoto è normale per tre cause su quattro. Tre controlli permettono di distinguerle:
ls -la .htaccess # is the file in the site root
apache2ctl -M | grep rewrite # expect rewrite_module (shared)
# AllowOverride test: add any random word as the first line of .htaccess
Abbiamo verificato anche il test della parola a caso. Con AllowOverride None il sito ha continuato a restituire 404, perché il file viene ignorato. Con AllowOverride All ha restituito subito 500, e nel log è comparso “Invalid command” con quella parola. Dopo il test togliete la riga.
Il blocco standard per un singolo sito, generato da WordPress stesso sul banco:
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
Su un VPS dove gestite Apache da soli, attivate il modulo e consentite il .htaccess per la directory del sito (dopo questo, sul banco ogni URL ha restituito 200):
sudo a2enmod rewrite
sudo systemctl restart apache2
# in the virtual host config
<Directory /var/www/example.com/public_html>
AllowOverride All
Require all granted
</Directory>
Una trappola di WP-CLI: wp rewrite flush --hard non ha creato il .htaccess e ha mostrato invece un avviso:
$ wp rewrite flush --hard --path=/var/www/r10wp
Warning: Regenerating a .htaccess file requires special configuration. See usage docs.
Success: Rewrite rules flushed.
Un file wp-cli.yml con apache_modules risolve il problema, ma WP-CLI cerca questo file nella directory corrente, non in quella indicata con --path. Ha funzionato solo così:
cd /var/www/example.com/public_html
printf 'apache_modules:\n - mod_rewrite\n' > wp-cli.yml
wp rewrite flush --hard
Il file è stato creato con proprietario root, perché il comando girava come root. Sull’hosting eseguite WP-CLI come utente del sito oppure cambiate il proprietario; i permessi del .htaccess devono essere 644. Altre impostazioni del server web sono trattate nella nostra guida alla configurazione del server.
Causa 3. Struttura dei permalink cambiata, regole non rigenerate
WordPress salva le regole di riscrittura nel database (l’opzione rewrite_rules) e le rigenera solo con un “flush”: quando si salva la pagina Permalink, quando lo fanno alcuni plugin o quando eseguite wp rewrite flush. Se la struttura viene cambiata direttamente nel database o con wp option update (capita dopo importazioni del database e script di migrazione), i link del sito sono già nuovi mentre le regole sono ancora vecchie:
$ wp option update permalink_structure '/%year%/%monthnum%/%postname%/'
Success: Updated 'permalink_structure' option.
/2026/10/post-300/ 404 (theme 404 page, wp-content: 4)
/post-300/ 301 → /2026/10/post-300/
/sample-page/ 200
$ wp rewrite list --match=/2026/10/post-300/ --fields=match,query
(.?.+?)(?:/([0-9]+))?/?$ index.php?pagename=$matches[1]&page=$matches[2]
I vecchi URL reindirizzano ai nuovi e i nuovi restituiscono 404, quindi il visitatore trova un errore su ogni link del menu. wp rewrite list --match mostra quale regola è scattata per prima: qui l’URL è stato preso dalla regola delle pagine, perché nel database non ci sono regole per la nuova struttura. La correzione:
$ wp rewrite flush
Success: Rewrite rules flushed.
$ wp rewrite list --format=count
96
/2026/10/post-300/ 200
I nuovi tipi di contenuto si comportano allo stesso modo. Abbiamo registrato un tipo di contenuto book in un mu-plugin: /book/test-book/ restituiva la pagina 404 del tema, anche se /?book=test-book si apriva con 200. Dopo wp rewrite flush il numero di regole è passato da 94 a 111 e l’articolo si è aperto. Per questo un 404 solo sui prodotti WooCommerce o su un altro tipo di contenuto dipende quasi sempre da regole non rigenerate. La stessa causa è frequente dopo aver trasferito WordPress su un altro hosting importando il database a mano.
Causa 4. home e siteurl sbagliati, o installazione in una sottodirectory
Il sito è passato da /blog/ alla radice, il database ha mantenuto il vecchio URL
Abbiamo impostato home e siteurl sull’URL con /blog mentre i file stavano nella radice e Nginx era configurato correttamente. Gli articoli si aprivano comunque con 200 sia su /post-300/ sia su /blog/post-300/: WordPress interpreta entrambi. Ma tutti i link nell’HTML puntavano a /blog/, gli stili da /blog/wp-content/ restituivano 404 e /wp-admin/ reindirizzava a /blog/wp-login.php, che restituiva anch’esso 404. Quindi qui il sintomo è diverso: un sito senza stili e senza accesso all’amministrazione.
wp option get home
wp option get siteurl
wp option update home https://example.com
wp option update siteurl https://example.com
Dopo i due comandi di aggiornamento, sul banco gli stili hanno restituito 200 e /wp-admin/ ha portato alla pagina di accesso corretta. Se non riuscite a entrare nell’amministrazione e non avete SSH, impostate gli stessi valori con le costanti WP_HOME e WP_SITEURL in wp-config.php. I vecchi URL nel contenuto degli articoli si sostituiscono con wp search-replace (eseguitelo prima con --dry-run); in questo test non l’abbiamo eseguito.
WordPress in una sottodirectory, Nginx ripiega sull’index.php della radice
I file di WordPress erano in /blog/ e la configurazione aveva solo location / con ripiego su /index.php. Risultato: /blog/ restituiva 200, /blog/post-300/ e /blog/sample-page/ restituivano la pagina 404 di nginx, stili e wp-login.php restituivano 200. È lo stesso “funziona solo la home”, ma per una sottodirectory: nginx cerca /index.php nella radice, dove non c’è. La correzione dopo la quale ogni URL ha restituito 200:
location /blog/ {
try_files $uri $uri/ /blog/index.php?$args;
}
Su Apache, un’installazione in sottodirectory cambia due righe del .htaccess: RewriteBase /blog/ e RewriteRule . /blog/index.php [L]. Questa variante non l’abbiamo provata sul banco.
Causa 5. Un plugin o un tema con una regola di riscrittura in conflitto
I plugin che “rimuovono la base dagli URL” (perché landing page o prodotti si aprano come /nome/) aggiungono una propria regola in cima all’elenco. Lo abbiamo riprodotto con un mu-plugin che registra un tipo di contenuto personalizzato e una regola:
add_rewrite_rule('^([^/]+)/?$', 'index.php?landing=$matches[1]', 'top');
- Subito dopo l’installazione non è cambiato nulla: le regole di un plugin arrivano nel database solo dopo un flush. Per questo il guasto spesso compare più tardi, quando qualcuno salva i permalink o aggiorna un altro plugin.
- Dopo
wp rewrite flushla home e /category/uncategorized/ restituivano 200, mentre /post-300/ e /sample-page/ restituivano la pagina 404 del tema. - Dopo aver rimosso il plugin senza flush è andata peggio: /post-300/ restituiva 200 ma con il contenuto della home (titolo del sito, body class “home blog”). Un motore di ricerca vedrebbe decine di duplicati della home al posto degli articoli.
- Un altro
wp rewrite flushha rimesso tutto a posto: 200 e l’articolo giusto.
Lo stesso comando trova il colpevole:
$ wp rewrite list --match=/post-300/ --fields=match,query,source
^([^/]+)/?$ index.php?landing=$matches[1] other
(.?.+?)(?:/([0-9]+))?/?$ index.php?pagename=$matches[1]&page=$matches[2] page
La sorgente “other” indica una regola aggiunta dal codice di un plugin o di un tema, non dal core. Poi disattivate i plugin sospetti uno alla volta (meglio su una copia del sito) ed eseguite wp rewrite flush dopo ogni passo. Un dettaglio curioso: la stessa regola troppo ampia che punta a pagine normali (pagename) non ha rotto nulla. WordPress la salta quando nessuna pagina ha quel percorso e passa alla regola successiva. A rompere il sito sono le regole che puntano a query var personalizzate.
Causa 6. La cache continua a servire un 404 salvato dopo la correzione
Abbiamo attivato fastcgi_cache con fastcgi_cache_valid 404 10m;. Prima osservazione: WordPress invia il suo 404 con Cache-Control: no-cache e una data Expires del 1984, quindi per impostazione predefinita nginx non lo mette in cache (X-Cache: MISS a ogni richiesta). Il problema è comparso quando abbiamo aggiunto fastcgi_ignore_headers Cache-Control Expires Set-Cookie;, una riga spesso consigliata per la cache di WordPress:
broken /post-300/ 404 X-Cache: MISS
/post-300/ 404 X-Cache: HIT
rules fixed /post-300/ 404 X-Cache: HIT
/post-300/?utm=1 200 X-Cache: MISS
cache entry deleted /post-300/ 200 X-Cache: MISS
/post-300/ 200 X-Cache: HIT
Il segnale rivelatore: l’URL con un parametro qualsiasi si apre, mentre l’URL senza parametri restituisce 404 con X-Cache: HIT. Potete cancellare una singola voce senza il modulo purge, perché il nome del file è l’md5 della chiave di cache. Sul banco quel file conteneva “KEY: httpGET127.0.0.1/post-300/” e “Status: 404 Not Found”; una volta cancellato, l’articolo ha restituito 200.
# key = $scheme$request_method$host$request_uri, levels=1:2
KEY="httpsGETexample.com/post-name/"
H=$(echo -n "$KEY" | md5sum | cut -d' ' -f1)
sudo rm -f /var/cache/nginx/wp/${H: -1}/${H: -3:2}/$H
# or the whole cache
sudo find /var/cache/nginx/wp -type f -delete
Prendete il percorso della cache dalla vostra direttiva fastcgi_cache_path. Svuotate separatamente la cache del plugin e la CDN. Perché gli errori non restino appiccicati, mettete in cache i 404 per poco tempo, per esempio fastcgi_cache_valid 404 1m;. Come la cache lato server influisce sulla velocità lo spieghiamo nell’articolo su cache e RAM nell’hosting.
Tabella decisionale: sintomo, causa, controllo, correzione
| Sintomo | Causa | Controllo | Correzione |
|---|---|---|---|
| 404 senza la grafica del tema (pagina nginx), log vuoto | try_files ... =404 in location / |
nginx -T | grep try_files |
try_files $uri $uri/ /index.php?$args; |
| Pagina 404 di nginx, il log dice “index.php is not found” | Nessun blocco location / | Log degli errori di nginx | Aggiungere location / con try_files |
| Pagina “Not Found” di Apache, log vuoto | Nessun .htaccess, mod_rewrite disattivato o AllowOverride None | ls -la .htaccess, apache2ctl -M, test della parola a caso |
Ripristinare il .htaccess, a2enmod rewrite, AllowOverride All |
| 500 su tutte le pagine, home compresa | .htaccess senza IfModule con mod_rewrite disattivato | “Invalid command ‘RewriteEngine'” nel log | a2enmod rewrite |
| 404 del tema su tutti gli articoli, i vecchi URL restituiscono 301 | Struttura dei permalink cambiata senza flush | wp rewrite list --match=/url/ |
wp rewrite flush |
| 404 del tema su un solo tipo di contenuto | Nuovo tipo di contenuto, regole non rigenerate | /?type=slug si apre | wp rewrite flush |
| 404 del tema sugli URL a un solo segmento, le categorie funzionano | Plugin con una regola troppo ampia | La prima regola in wp rewrite list --match ha sorgente other |
Disattivare il plugin, wp rewrite flush |
| Qualsiasi URL a un solo segmento mostra la home con 200 | Plugin rimosso, la sua regola è rimasta nel database | Body class “home” sull’URL di un articolo | wp rewrite flush |
| Il 404 resta dopo la correzione, l’URL con parametro restituisce 200 | La cache ha salvato il 404 | curl -sI url | grep -i x-cache → HIT |
Cancellare la voce o tutta la cache |
| Sito senza stili, l’accesso all’amministrazione restituisce 404 | home e siteurl sbagliati | wp option get home |
wp option update home e siteurl |
| /blog/ funziona, /blog/post/ restituisce il 404 di nginx | Installazione in sottodirectory, ripiego su /index.php della radice | Configurazione di location | location /blog/ con /blog/index.php?$args |
Domande frequenti
Perché dopo il passaggio a un nuovo hosting funziona solo la home?
Il più delle volte è andato perso il file nascosto .htaccess (i client FTP spesso nascondono e saltano i file che iniziano con un punto) oppure il nuovo server usa Nginx senza il try_files necessario. Sul banco entrambi i casi davano lo stesso quadro: 200 sulla home e 404 sugli articoli.
Come capisco se il mio sito è servito da Nginx o da Apache?
Eseguite curl -sI https://example.com/ | grep -i server. Sul banco l’intestazione era “Server: nginx/1.24.0 (Ubuntu)”. Alcuni provider la nascondono; in quel caso controllate il tipo di server web nel pannello di controllo. Se nginx fa da proxy davanti ad Apache, le regole di WordPress le esegue Apache tramite il .htaccess.
Reinstallare WordPress serve?
No. In nessuno dei sei scenari la causa erano i file del core: le regole stanno nel database, e passare le richieste a index.php è compito del server web.
Qualche giorno di 404 danneggia il posizionamento su Google?
Un breve disservizio di solito passa senza conseguenze, ma se il crawler vede 404 per diversi giorni, le pagine iniziano a uscire dall’indice. Dopo la correzione, controllate alcuni URL con lo strumento Controllo URL di Search Console e inviate di nuovo la sitemap.
Se preferite non configurare da soli Nginx, Apache e la cache, date un’occhiata ai piani di hosting RichHost: dischi NVMe, pannello di controllo proprietario con assistente IA e SSL Let’s Encrypt gratuito. In una guida a parte spieghiamo anche come scegliere l’hosting per WordPress.



