Codici HTTP: cheat sheet completo (1xx-5xx) con esempi
Apri i DevTools e la scheda Network è mezza rossa. Il tuo endpoint risponde 502 in produzione, 200 in locale, e su Slack un collega ti chiede: «ma qui ci vuole un 401 o un 403?». I codici di stato HTTP sembrano semplici, tre cifre e cinque categorie, ma scegliere quello sbagliato fa trapelare informazioni, danneggia la SEO e rende infernali i turni di reperibilità.
Questa guida è un cheat sheet dei codici HTTP per chi sviluppa davvero. Trovi tre cose: (1) una tabella di riferimento rapido con tutti i codici che incontri sul campo, (2) matrici decisionali per le coppie che si confondono più spesso (301 vs 302, 401 vs 403, 404 vs 410, 502 vs 504) e (3) una sezione sugli strumenti che mostra come ispezionare i codici di stato da curl, fetch e Python requests. Ogni codice qui sotto si basa sulla RFC 9110, lo standard attuale della semantica HTTP, e sul registro IANA dei codici di stato HTTP.
Riferimento rapido: tutti i codici HTTP a colpo d’occhio
Ecco i codici che incontri in produzione, raggruppati per classe. Aggiungi questa tabella ai preferiti; il resto dell’articolo spiega quelli più insidiosi.
| Codice | Nome | Quando lo vedrai |
|---|---|---|
| 100 | Continue | Invio di un POST con corpo grande con Expect: 100-continue |
| 101 | Switching Protocols | Handshake WebSocket, upgrade HTTP/2 |
| 103 | Early Hints | Il server invia header Link prima della risposta vera e propria |
| 200 | OK | Successo predefinito per GET, PUT, PATCH |
| 201 | Created | POST che crea una risorsa (restituisce Location) |
| 202 | Accepted | Job asincrono in coda, lavoro ancora non concluso |
| 204 | No Content | DELETE riuscito, PUT senza corpo da restituire |
| 206 | Partial Content | Range request, seek di video, download riprendibile |
| 301 | Moved Permanently | URL vecchio dismesso, i motori di ricerca trasferiscono il link equity |
| 302 | Found | Redirect temporaneo, l’URL originale resta canonico |
| 303 | See Other | Pattern Post/Redirect/Get dopo un POST di form |
| 304 | Not Modified | GET condizionale con ETag o If-Modified-Since corrispondente |
| 307 | Temporary Redirect | Come 302, ma metodo e corpo vengono preservati |
| 308 | Permanent Redirect | Come 301, ma metodo e corpo vengono preservati |
| 400 | Bad Request | JSON malformato, campo obbligatorio mancante, schema non valido |
| 401 | Unauthorized | Credenziali assenti o token scaduto |
| 403 | Forbidden | Autenticato ma non autorizzato |
| 404 | Not Found | La risorsa non esiste (o la stai nascondendo) |
| 405 | Method Not Allowed | POST su un endpoint solo GET (deve includere Allow) |
| 408 | Request Timeout | Il client ha impiegato troppo per inviare la richiesta |
| 409 | Conflict | Fallimento di optimistic lock, chiave duplicata |
| 410 | Gone | Risorsa cancellata definitivamente, non tornerà |
| 415 | Unsupported Media Type | Content-Type errato, ad esempio XML su API JSON |
| 422 | Unprocessable Content | Sintassi valida, semantica no (errore di validazione) |
| 425 | Too Early | Rischio di replay sui dati early-data di TLS 1.3 |
| 428 | Precondition Required | Il server richiede If-Match per evitare aggiornamenti persi |
| 429 | Too Many Requests | Rate limit superato (deve includere Retry-After) |
| 451 | Unavailable for Legal Reasons | DMCA, rimozione GDPR, geo-blocco |
| 500 | Internal Server Error | Eccezione non gestita nel tuo codice |
| 501 | Not Implemented | Metodo o funzionalità non supportata (raro in REST) |
| 502 | Bad Gateway | L’upstream ha restituito una risposta non valida |
| 503 | Service Unavailable | Modalità manutenzione o sovraccarico |
| 504 | Gateway Timeout | L’upstream non ha risposto in tempo |
| 507 | Insufficient Storage | A WebDAV è finito lo spazio su disco |
| 508 | Loop Detected | Redirect infinito o ricorsione in WebDAV |
| 511 | Network Authentication Required | Captive portal del WiFi di un hotel o aeroporto |
Il resto dell’articolo analizza ogni classe con matrici decisionali, anti-pattern e le conseguenze SEO degli errori.
Come funzionano i codici HTTP (anatomia delle 3 cifre)
Perché tre cifre?
I codici HTTP sono tre cifre decimali perché HTTP/0.9 aveva bisogno di un segnale a larghezza fissa, abbastanza piccolo da essere interpretato rapidamente da un parser e abbastanza ampio da lasciare spazio a nuovi codici. Tre cifre danno 900 valori possibili (100–999), molto più del necessario: il registro IANA ne usa oggi solo una sessantina.
La prima cifra è la classe. La seconda e la terza identificano il codice specifico dentro quella classe. Un client che non riconosce 418 deve gestirlo come un generico 4xx. La RFC 9110 §15 lo dice esplicitamente: i client devono trattare i codici non riconosciuti come l’x00 della loro classe.
Le cinque categorie a colpo d’occhio
| Classe | Significato | Corpo richiesto? | Cacheable di default? |
|---|---|---|---|
1xx | Informativo, interim, segue altro | No | No |
2xx | Successo, richiesta compresa e accettata | Spesso | Dipende dal metodo |
3xx | Redirect, serve un’azione ulteriore | Opzionale | 301, 308 sì; 302, 307 no |
4xx | Errore client, colpa tua, sistema la richiesta | Sì (con spiegazione) | Di solito no |
5xx | Errore server, colpa nostra, un retry può aiutare | Sì (con spiegazione) | No |
La colonna «cacheable di default» è importante. CDN e browser cachano 301 e 308 in modo aggressivo e per sempre: scegliere il codice di redirect sbagliato in produzione è difficile da annullare, perché gli utenti hanno il redirect cachato in locale. Torneremo su questo nella sezione SEO.
Se vuoi approfondire la struttura degli URL (su cui operano i codici di redirect), Codifica e decodifica URL ti guida tra percent-encoding, query string e la pipeline a livello di byte che determina cosa renda valido un URL.
1xx, informativi (quando li vedrai davvero)
La maggior parte degli sviluppatori passa anni senza vedere direttamente un 1xx. Sono risposte interim: il server sta dicendo al client «sono ancora qui, vai avanti». I DevTools del browser di solito li nascondono e la maggior parte delle librerie HTTP li accorpa nella risposta finale.
Per ciascun codice qui sotto, il riferimento MDN sui codici di stato HTTP è il più chiaro se vuoi una seconda opinione su una definizione.
100 Continue
Il client invia Expect: 100-continue negli header e attende prima di trasmettere un corpo di richiesta grande. Il server risponde 100 Continue se è disposto ad accettare il corpo, oppure un 4xx se la richiesta verrà comunque rifiutata. Così si risparmia banda sugli upload pesanti: non ha senso inviare 200 MB se il server li rifiuterà per un header mancante.
curl -v -H "Expect: 100-continue" \
-H "Content-Type: application/octet-stream" \
--data-binary @big-file.bin \
https://api.example.com/upload
Se non vedi < HTTP/1.1 100 Continue nell’output verboso, probabilmente il client ha rimosso l’header oppure il server non lo supporta.
101 Switching Protocols
L’handshake che trasforma una connessione HTTP in una connessione WebSocket o HTTP/2. Il client invia Upgrade: websocket, il server risponde 101 Switching Protocols e da quel momento la connessione parla un protocollo diverso. Lo vedi nella scheda Network di qualsiasi app di chat, dashboard live o strumento di collaborazione.
103 Early Hints
Un codice relativamente recente (RFC 8297, 2017) che permette al server di inviare header Link per i preload hint prima che la risposta principale sia pronta. Il browser inizia a recuperare CSS e JS mentre il server sta ancora generando la pagina. Nel 2026 Cloudflare, Fastly e Vercel supportano 103 in produzione: è l’alternativa attuale allo HTTP/2 server push (che è stato deprecato in Chrome).
HTTP/1.1 103 Early Hints
Link: </styles.css>; rel=preload; as=style
Link: </app.js>; rel=preload; as=script
HTTP/1.1 200 OK
Content-Type: text/html
...
Anti-pattern check. Se il tuo client non vede mai i codici 1xx quando te li aspetti, di solito il colpevole è un reverse proxy. Le versioni più vecchie di nginx eliminano Expect: 100-continue e 103 Early Hints. Controlla la configurazione del proxy prima di dare per scontato che il server sia rotto.
2xx, successo (oltre il semplice 200)
Restituire 200 OK per tutto è il code-smell più comune nelle API REST. La famiglia 2xx porta con sé informazioni semantiche che rendono i client più intelligenti e le cache più efficienti.
200 OK
Il default. Una GET restituisce la risorsa, una PUT restituisce la risorsa aggiornata (oppure 204), una PATCH restituisce la risorsa modificata. Se non hai motivi per usare un codice più specifico, usa 200.
201 Created
Una POST che crea una nuova risorsa dovrebbe restituire 201 insieme a un header Location che punta alla nuova risorsa. È così che i client RESTful scoprono l’URL canonico della cosa che hanno appena creato.
HTTP/1.1 201 Created
Location: /api/users/42
Content-Type: application/json
{"id": 42, "name": "Ada Lovelace"}
202 Accepted
Il server ha accettato la richiesta ma non ha finito l’elaborazione. Usalo per il lavoro asincrono: il client dovrebbe fare polling, sottoscrivere un webhook o controllare un endpoint di stato. Abbinalo a un job ID nel corpo.
204 No Content
Successo, nessun corpo. Tipico per DELETE (la risorsa non c’è più, cosa restituiresti?) e per le operazioni PUT in cui il client conosce già il nuovo stato. I browser non cambiano la pagina corrente se l’invio di un form risponde con 204: utile per azioni fire-and-forget nelle single-page app.
206 Partial Content
Restituito per le range request: il client ha chiesto i byte da 1000 a 2000 con un header Range: bytes=1000-2000 e il server ha risposto con quella sola fetta. Streaming video, download riprendibili e sincronizzazione di file via HTTP si basano su 206.
Decisione: 200 vs 201 vs 204 per le POST
| Scenario | Codice | Corpo |
|---|---|---|
| POST crea una nuova risorsa | 201 Created | Nuova risorsa (o solo ID) + Location |
| POST avvia lavoro asincrono, risultato non pronto | 202 Accepted | Job ID, URL di polling |
POST è un’azione senza risorsa (es. /login) | 200 OK | Risultato dell’azione (token, stato) |
| POST riuscita ma risposta vuota | 204 No Content | (nessuno) |
Se sei indeciso tra 200 e 201, chiediti: «il server ha creato una risorsa che ora ha un proprio URL?». Se sì, 201. Se no, 200.
3xx, redirect (301 vs 302 vs 307 vs 308)
I redirect sono la classe usata peggio. Le differenze tra 301, 302, 307 e 308 si riducono a tre domande ortogonali: il trasferimento è permanente, il metodo viene preservato, la risposta è cacheable.
301 Moved Permanently
La risorsa si è spostata e non tornerà. I motori di ricerca trasferiscono il link equity al nuovo URL. Browser e CDN cachano 301 a tempo indefinito: se redirezioni /old su /new con un 301 e poi cambi idea, gli utenti con il redirect in cache continueranno ad andare su /new per sempre (o finché non svuotano la cache).
Storicamente i browser potevano riscrivere il metodo della richiesta su un 301 (POST → GET): è proprio per questo che HTTP/1.1 ha introdotto 308.
302 Found
Redirect temporaneo. L’URL originale resta canonico: i motori di ricerca dovrebbero continuare a indicizzare l’originale. Usalo per il routing degli A/B test, le pagine di manutenzione o i flussi «accedi per continuare».
Come per 301, storicamente i browser riscrivevano POST in GET su 302. Se devi redirezionare una POST mantenendola POST, usa invece 307.
303 See Other
Riscrive sempre il metodo in GET. Il pattern Post/Redirect/Get: il form fa POST su /submit, il server risponde 303 con Location: /thank-you, il browser fa una GET /thank-you. Ricaricando la pagina di ringraziamento il form non viene reinviato. È esattamente lo scopo per cui 303 è stato pensato.
304 Not Modified
La risposta condizionale. Il client invia If-None-Match: "abc123" (o If-Modified-Since), il server controlla se la risorsa è cambiata e, se non lo è, risponde 304 senza corpo. Il browser usa la sua copia in cache. È così che CDN e livelli di caching mantengono veloce il tuo sito.
307 Temporary Redirect
Come 302, ma il metodo non deve cambiare. POST resta POST, il corpo viene preservato. Usalo quando vuoi un redirect temporaneo su una richiesta non-GET.
308 Permanent Redirect
Come 301, ma il metodo non deve cambiare. La scelta più sicura per i redirect permanenti su API che accettano POST/PUT.
Matrice decisionale: quale codice di redirect?
| Permanente (cache per sempre) | Temporaneo (non cachare) | |
|---|---|---|
| Il metodo può diventare GET | 301 Moved Permanently | 302 Found |
| Il metodo deve restare lo stesso | 308 Permanent Redirect | 307 Temporary Redirect |
Caso speciale: se vuoi specificamente POST → GET (il pattern Post/Redirect/Get), usa 303 See Other.
Per le pagine HTML con la navigazione del browser, 301 e 302 di solito vanno bene perché GET resta GET. Per API e form, preferisci 308 e 307 per evitare riscritture inattese del metodo.
4xx, errori del client (scegliere quello giusto)
4xx significa che il client ha sbagliato qualcosa. Più ricco è il tuo vocabolario 4xx, più semplice è usare la tua API: i client possono ramificarsi sul codice invece di fare il parse di stringhe d’errore.
400 Bad Request
Errore di sintassi generico. JSON malformato, campo obbligatorio mancante a livello strutturale, richiesta che il server non riesce nemmeno a interpretare. Se la richiesta viene parsata ma fallisce la validazione di business, preferisci 422.
401 Unauthorized vs 403 Forbidden
La coppia più confusa di tutto HTTP. La distinzione è semplice una volta vista:
401 Unauthorized: la richiesta non porta un’autenticazione valida. Il server non sa chi sei. Reinviare le credenziali (o rinfrescare il token) potrebbe risolvere. La risposta deve includere un headerWWW-Authenticatecome da RFC 9110 §15.5.2.403 Forbidden: il server sa chi sei e si rifiuta comunque. Reinviare la richiesta non aiuta. Servono credenziali diverse o permessi diversi.
| Cosa vedi | Cosa significa |
|---|---|
401 con WWW-Authenticate: Bearer | Token assente, scaduto o non valido |
403 dopo un login riuscito | Sei autenticato, ma questo utente non può accedere a questa risorsa |
401 dopo un login riuscito | Bug, probabilmente volevi 403 |
Anti-pattern: 403-as-404. Alcuni siti restituiscono 403 quando un utente non autenticato richiede /admin/dashboard. Così facendo si rivela l’esistenza di /admin/dashboard. GitHub aggira il problema restituendo 404 per i repository privati di cui non sei membro: dal tuo punto di vista la risorsa «non esiste». È una scelta deliberata di occultamento delle informazioni, non un bug.
404 Not Found vs 410 Gone
Entrambi dicono «questa risorsa non c’è». La differenza sta nella permanenza e nella SEO.
404 Not Found: potrebbe esistere, potrebbe no, potrebbe tornare. I motori di ricerca continueranno a controllare.410 Gone: c’era, è stata rimossa volontariamente, non tornerà. I motori la tolgono dall’indice molto più in fretta.
Se cancelli una pagina prodotto e vuoi farla sparire ora dall’indice di Google, 410 è la scelta giusta. Se un URL è solo temporaneamente rotto, 404 va bene.
405 Method Not Allowed
L’URL esiste ma non accetta questo metodo. La risposta deve includere un header Allow con la lista dei metodi supportati.
HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
{"error": "POST is not allowed on this endpoint"}
Dimenticare l’header Allow è la violazione di contratto numero 1 nelle API REST scritte a mano.
408 Request Timeout
Il client ha iniziato a inviare una richiesta e poi è andato in silenzio. Il server ha rinunciato. Diverso da 504 Gateway Timeout, che riguarda l’upstream: 408 significa «tu, il client, hai impiegato troppo».
409 Conflict
La richiesta è in conflitto con lo stato corrente. Uso più comune: optimistic locking. Il client invia If-Match: "etag-v3" e l’ETag corrente del server è "etag-v4", quindi l’aggiornamento viene rifiutato con 409.
410 Gone
Vedi sopra: cancellazione permanente. Utile per rimuovere dagli indici di ricerca i record cancellati in soft-delete.
415 Unsupported Media Type
Il client ha inviato un corpo che il server non capisce. Una POST di XML su un’API solo JSON ottiene 415. La risposta dovrebbe suggerire i tipi accettabili.
422 Unprocessable Content
La richiesta viene parsata correttamente, ma fallisce la validazione semantica. Nel 2022 la RFC 9110 ha finalmente promosso questo codice da WebDAV alla specifica core. Usa 422 per gli errori di validazione:
{
"error": "validation_failed",
"details": [
{"field": "email", "message": "must be a valid email"},
{"field": "age", "message": "must be at least 13"}
]
}
Se la tua API è indecisa tra 400 e 422, la regola pratica è: 400 per «non riesco nemmeno a interpretarlo», 422 per «l’ho interpretato e non ha senso».
425 Too Early
Inviato quando il server non vuole rischiare di processare una richiesta che potrebbe essere un replay di early-data TLS 1.3. Rilevante soprattutto per CDN e reverse proxy.
428 Precondition Required
Il server pretende che tu invii If-Match o If-Unmodified-Since per evitare il problema dell’aggiornamento perso. Usato nelle API di editing collaborativo.
429 Too Many Requests
Rate limit superato. La risposta deve includere Retry-After (in secondi o come data HTTP) in modo che i client educati possano fare back-off.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json
{"error": "rate_limited", "limit": 100, "window": "1m"}
451 Unavailable for Legal Reasons
Il numero è una citazione di Bradbury. Il caso d’uso non è fantascientifico: takedown DMCA, rimozioni per il diritto all’oblio del GDPR e geo-blocchi a livello nazionale giustificano tutti 451. La risposta dovrebbe includere un header Link che punti all’autorità legale che impone il blocco, secondo la RFC 7725.
418 I’m a Teapot (l’easter egg)
Sì, esiste davvero. La RFC 2324 (pesce d’aprile 1998) e l’IETF lo hanno mantenuto a registro perché troppi prodotti lo avevano implementato per scherzo. Non spedire 418 in un’API reale: la maggior parte di reverse proxy e load balancer lo gestisce male.
Matrice decisionale: quale 4xx?
| Situazione | Codice |
|---|---|
| Corpo malformato o non parsabile | 400 |
| Nessuna autenticazione / token scaduto | 401 |
| Autenticato ma non autorizzato | 403 |
| L’URL non esiste (o lo stai nascondendo) | 404 |
| L’URL esisteva, è stato rimosso volontariamente | 410 |
| Metodo HTTP errato | 405 (con Allow) |
Content-Type errato | 415 |
| Conflitto di optimistic lock | 409 |
| Errore di validazione (parse ok, validazione no) | 422 |
| Rate limit superato | 429 (con Retry-After) |
| Bloccato per ragioni legali | 451 |
5xx, errori del server (cosa è davvero rotto)
5xx è «colpa nostra». Chi è di reperibilità si interessa soprattutto di quale 5xx lo ha svegliato alle 3 di notte, perché il codice ti dice quale livello indagare per primo.
500 Internal Server Error
Il jolly. Quasi sempre significa che un’eccezione non gestita è risalita fino all’handler di default del framework. Non ti dice nulla sulla causa, ed è per questo che qui un logging strutturato conta più del codice di stato.
501 Not Implemented
Il server non supporta proprio quel metodo. Diverso da 405 (questo metodo non è ammesso su questo URL): 501 dice «questo server non sa nemmeno cosa significhi PROPFIND». Raro nelle API REST.
502 Bad Gateway
Un reverse proxy o load balancer ha ricevuto una risposta non valida dall’upstream. L’upstream ha risposto, ma con immondizia: protocollo errato, header malformati, connessione persa a metà risposta. Se vedi 502 dalla tua CDN, l’origine probabilmente sta crashando o restituisce corpi troncati.
503 Service Unavailable
Il server non sta volutamente servendo richieste in questo momento. Usalo per finestre di manutenzione o risposte di sovraccarico controllato. Dovrebbe includere Retry-After.
504 Gateway Timeout
Il reverse proxy ha aspettato l’upstream e l’upstream non ha mai risposto in tempo. L’upstream è lento o bloccato; diverso da 502, dove l’upstream ha risposto con immondizia.
502 vs 504: la diagnosi di reperibilità
| Cosa vedi | Prima cosa da controllare |
|---|---|
502 Bad Gateway | L’upstream risponde con dati non validi: controlla i log dell’origine per crash, risposte malformate, mismatch di protocollo |
504 Gateway Timeout | L’upstream è bloccato: controlla CPU dell’origine, query DB, chiamate API a valle e il proxy_read_timeout del proxy |
Confusione comune: una query al database che impiega 60 secondi viene fuori come 504 se il proxy va in timeout a 30 secondi, ma come 500 se l’app server va in timeout a 90 secondi e solleva un’eccezione. Stessa causa principale, codice diverso, riga di log diversa: addestra le tue dashboard a mostrarli entrambi.
507 Insufficient Storage
Specifico di WebDAV. Disco pieno sul server. Se lo vedi da un’API non-WebDAV, qualcuno ne sta forzando il significato.
508 Loop Detected
Ricorsione infinita nelle operazioni PROPFIND di WebDAV. Molto raro.
511 Network Authentication Required
Codici da captive portal: il WiFi di un hotel o aeroporto invia 511 per dire al browser «prima devi accedere al portale». La risposta include un Location che punta alla pagina del portale.
Matrice di troubleshooting: quale livello controllare per primo
| Codice | App | Proxy | DB | Rete |
|---|---|---|---|---|
500 | Sì | — | Forse (errore DB non gestito) | — |
502 | — | Sì (upstream malformato) | — | Forse (TCP reset) |
503 | Sì (flag di manutenzione) | Sì (rate-limit reject) | — | — |
504 | Sì (handler lento) | Sì (config di timeout) | Sì (query lenta) | Sì (DNS, perdita pacchetti) |
Anti-pattern comuni dei codici HTTP
Questi cinque errori spiegano la maggior parte del codice scadente che mi capita di rivedere.
1. Avvolgere gli errori in un 200 OK
HTTP/1.1 200 OK
{"success": false, "error": "user_not_found"}
Tutti gli strumenti di monitoraggio, le CDN e le cache pensano ora che la richiesta sia andata a buon fine. La logica di retry fallisce. I load balancer status-code-aware instradano traffico difettoso verso backend «sani». Questo pattern arriva da JSON-RPC ed è stato ereditato da GraphQL: GraphQL lo fa perché i successi parziali richiedono una segnalazione di errore per campo, ed è legittimo. REST non ha scuse: usa 4xx per gli errori del client, 5xx per quelli del server e metti il dettaglio strutturato nel corpo.
2. Mescolare 401 e 403
Se i tuoi 401 e 403 non sono coerenti, un attaccante può sondare la tua API per scoprire quali risorse esistono. Scegli una policy: o restituisci 404 per «non puoi vederlo» (l’approccio di GitHub per i repo privati) oppure restituisci 403 in modo coerente. L’incoerenza fa trapelare informazioni.
3. Nascondere 403 dietro 404
A volte è corretto, spesso è un bug. GitHub che restituisce 404 per i repo privati è deliberato: l’esistenza stessa del repo è un’informazione sensibile. Ma se la tua API restituisce 404 per «questo account utente è sospeso», gli utenti legittimi non riescono a capire se hanno digitato male l’username o se sono stati sospesi. Documenta la tua policy esplicitamente e applicala con coerenza.
4. Usare 500 come catch di default
I framework lo rendono facile, e il problema è proprio quello. Ogni eccezione non gestita diventa 500 e il tuo sistema di alerting non distingue tra «il database è giù» e «l’utente ha passato un UUID malformato». Cattura gli errori di validazione e solleva 400 o 422. Cattura NotFound dal tuo ORM e solleva 404. Riserva 500 ai fallimenti davvero inattesi, e quando lo sollevi, logga un request ID per correlare.
5. Catene di redirect lunghe
Ogni hop costa un round trip. Se /old → /intermediate → /canonical, sono due lookup DNS aggiuntivi e due handshake TCP aggiuntivi (caso peggiore). Google declassa esplicitamente la priorità di crawl per le catene più lunghe di 3 hop, e i browser limitano le catene di redirect a circa 20 per prevenire i loop. Comprimi le catene alla sorgente: la config della CDN o la mappa di redirect dell’applicazione.
Codici HTTP e SEO
I motori di ricerca trattano i codici di stato come segnali autorevoli su quale URL mantenere, eliminare o trasferire. Sbagli e le tue posizioni si muovono.
301 vs 302 (link equity)
301 Moved Permanently trasferisce il PageRank: Google considera il nuovo URL come destinazione canonica di tutti i segnali che puntavano al vecchio URL. 302 Found non trasferisce il link equity (o lo fa lentamente, a seconda delle euristiche di Google). Se hai rinominato un URL per sempre, usa 301. Se redirezioni un ospite verso /login, usa 302.
404 vs 410 vs Soft 404
Google distingue tre stati di «mancante»:
404 Not Found: Google ricontrolla periodicamente e tiene l’URL nell’indice ancora per un po’.410 Gone: Google elimina l’URL più velocemente, spesso entro un singolo ciclo di crawl.- Soft 404: il termine di Google per una pagina che restituisce
200 OKma mostra un messaggio di «non trovato». Google lo individua dai pattern del contenuto e lo tratta comunque come404, ma intanto hai sprecato una richiesta di crawl e magari diluito i tuoi contenuti reali.
Se stai pulendo un indice obsoleto, restituisci veri 410 per gli URL rimossi in modo permanente.
5xx e crawl budget
Il crawler di Google riduce il proprio ritmo quando un sito restituisce 5xx persistenti. Il report Crawl Stats di Search Console lo mostra: un picco prolungato di errori 5xx può tagliare il tuo crawl budget per giorni, il che significa che le pagine nuove impiegheranno più tempo a essere indicizzate. Tratta il tasso di 5xx come una metrica SEO, non solo come una metrica di affidabilità.
Un 200 OK che in realtà è rotto
Restituire 200 OK con una pagina di errore (l’anti-pattern del soft-404) è il peggior caso possibile per la SEO. Google indicizza il messaggio di errore, lo posiziona per niente e capisce solo dopo molto tempo che la pagina è rotta. Restituisci sempre il codice di stato corretto dal server, anche se la tua single-page app mostra una UI di errore curata.
Come ispezionare i codici HTTP (strumenti)
Non puoi sistemare ciò che non vedi. Ogni sviluppatore in attività dovrebbe padroneggiare almeno tre di questi strumenti.
Pannello Network dei DevTools del browser
Chrome, Firefox e Safari mostrano una colonna Status nella scheda Network. Fai clic destro sull’intestazione della colonna per aggiungere Status Text se non è visibile. Trucchi utili:
- Preserve log: mantiene le voci tra una navigazione e l’altra in modo da vedere l’intera catena di redirect.
- Filtra per status: digita
status-code:5xx(Chrome) per vedere solo gli errori del server. - Replay XHR: clic destro su una richiesta → Replay XHR per rieseguirla senza ricaricare la pagina.
Per i redirect, espandi la richiesta per vedere ogni hop e il codice di stato a ciascun passaggio.
curl (la risposta universale)
curl mostra tutto. Tre pattern che risolvono il 90% del debugging:
# Solo il codice di stato
curl -o /dev/null -s -w "%{http_code}\n" https://api.example.com/users/1
# Solo gli header (richiesta HEAD, segui i redirect)
curl -I -L https://example.com
# Verboso completo con header di richiesta e di risposta
curl -v https://api.example.com/users/1
Quando costruisci URL di test con caratteri speciali nelle query string, usa --data-urlencode per lasciare a curl la gestione dell’encoding, oppure incolla l’URL nel nostro Codificatore e Decodificatore URL per verificare quali byte finiranno davvero sulla rete.
# curl codifica per te il valore della query
curl -G "https://api.example.com/search" \
--data-urlencode "q=hello world & friends"
# Sends: GET /search?q=hello%20world%20%26%20friends
JavaScript fetch
La proprietà Response.status contiene il codice intero. Response.ok è true per ogni 2xx.
const res = await fetch('https://api.example.com/users/1');
console.log(res.status); // 200
console.log(res.statusText); // "OK"
console.log(res.ok); // true
if (!res.ok) {
if (res.status === 401) {
// refresh token and retry
} else if (res.status === 429) {
const retryAfter = Number(res.headers.get('Retry-After')) || 1;
await new Promise(r => setTimeout(r, retryAfter * 1000));
} else if (res.status >= 500) {
throw new Error(`Server error: ${res.status}`);
}
}
In axios la stessa logica vive negli interceptor:
import axios from 'axios';
axios.interceptors.response.use(
response => response,
error => {
const status = error.response?.status;
if (status === 401) {
// redirect to login
}
return Promise.reject(error);
}
);
Python requests
import requests
r = requests.get('https://api.example.com/users/1')
print(r.status_code) # 200
print(r.reason) # 'OK'
# Raises requests.exceptions.HTTPError for 4xx/5xx
r.raise_for_status()
# Manual handling
if r.status_code == 429:
retry_after = int(r.headers.get('Retry-After', '1'))
time.sleep(retry_after)
elif 500 <= r.status_code < 600:
raise RuntimeError(f'Server error: {r.status_code}')
raise_for_status() è l’idioma Python per «fallisci a voce alta su 4xx/5xx». Usalo negli script in cui vuoi che gli errori sollevino eccezioni invece di ramificare su status_code.
Postman e Bruno
Entrambi permettono di fare assert sui codici di stato dentro uno script di test:
// Postman/Bruno test script
pm.test("Status is 201", () => {
pm.response.to.have.status(201);
});
pm.test("Has Location header", () => {
pm.expect(pm.response.headers.get('Location')).to.match(/^\/users\/\d+$/);
});
Eseguili in CI contro lo staging per intercettare le violazioni di contratto prima della produzione.
FAQ
Qual è la differenza tra 401 e 403?
401 Unauthorized significa che il server non sa chi sei: le tue credenziali sono mancanti, scadute o non valide. 403 Forbidden significa che il server sa chi sei e si rifiuta comunque. Se inviare credenziali diverse potrebbe risolvere il problema, usa 401. Altrimenti usa 403.
Quando usare 301 invece di 302?
Usa 301 quando lo spostamento è permanente: il vecchio URL non tornerà mai più e vuoi che i motori di ricerca trasferiscano il link equity al nuovo URL. Usa 302 per redirect temporanei in cui l’URL originale è ancora canonico (flussi di login, A/B testing, pagine di manutenzione). Per le API, preferisci 308 e 307 perché preservano il metodo della richiesta.
Cosa significa un errore 502 Bad Gateway?
502 significa che un reverse proxy o load balancer ha ricevuto una risposta non valida dal server upstream. L’upstream ha risposto, ma con immondizia: protocollo errato, header malformati o connessione interrotta. È diverso da 504 Gateway Timeout, dove l’upstream non ha risposto affatto. Primo posto da controllare: i log del server di origine, in cerca di crash o risposte troncate.
Cos’è un «soft 404»?
Un «soft 404» è una pagina che restituisce 200 OK ma in realtà mostra un messaggio «non trovato». Google li individua in modo euristico e li tratta comunque come 404. Sprecano crawl budget e possono diluire i tuoi contenuti reali. Restituisci sempre veri codici di stato 404 o 410 dal server, anche se la tua single-page app mostra una UI di errore curata.
Quando usare 422 invece di 400?
Usa 400 Bad Request quando il server non riesce nemmeno a interpretare la richiesta: JSON malformato, campi strutturali mancanti, errori di sintassi. Usa 422 Unprocessable Content quando la richiesta viene parsata correttamente ma non supera la validazione di business: formato di email non valido, valore fuori range, campi semanticamente incoerenti. La regola breve: 400 per la sintassi, 422 per la semantica.
Come si risponde a un 429 Too Many Requests?
Leggi l’header Retry-After (un numero di secondi o una data HTTP) e fai back-off per almeno quel tempo prima di riprovare. Se Retry-After manca, usa exponential backoff con jitter partendo da circa 1 secondo. Non riprovare mai subito: è così che ti fai bannare.
I codici informativi 1xx sono ancora usati nel 2026?
Sì, ma la maggior parte è invisibile al codice applicativo. 100 Continue e 101 Switching Protocols sono funzionalità di base di HTTP/1.1. 103 Early Hints viene usato sempre più da Cloudflare, Fastly e Vercel per inviare i preload hint prima della risposta principale, e migliora visibilmente il Largest Contentful Paint. La maggior parte delle librerie HTTP accorpa i 1xx nella risposta finale, quindi di solito li vedi solo nei DevTools o in curl -v.
Il 418 «I’m a teapot» è davvero un codice di stato?
Sì, sorprendentemente. La RFC 2324 era un pesce d’aprile del 1998, ma abbastanza prodotti l’avevano implementata da convincere l’IETF a tenerla a registro nella RFC 7168. Non spedire 418 in produzione: molti reverse proxy e load balancer non lo gestiscono correttamente, e fuori dallo scherzo non serve a nulla.