HTTP-statuscodes spiekbriefje: 1xx-5xx uitgelegd
Je opent DevTools en het tabblad Netwerk staat half in het rood. Je endpoint geeft 502 in productie, 200 lokaal, en een collega vraagt net op Slack: “moet dit een 401 of een 403 zijn?”. HTTP-statuscodes lijken simpel (drie cijfers, vijf categorieën), maar de verkeerde keuze lekt informatie, breekt SEO en maakt on-call diensten ellendig.
Deze gids is een HTTP-statuscodes spiekbriefje voor werkende ontwikkelaars. Je krijgt drie dingen: (1) een snelle referentietabel met elke code die je in de praktijk tegenkomt, (2) beslismatrices voor de paren die mensen door elkaar halen (301 vs 302, 401 vs 403, 404 vs 410, 502 vs 504), en (3) een tooling-sectie die laat zien hoe je statuscodes inspecteert vanuit curl, fetch en Python requests. Elke code hieronder is gebaseerd op RFC 9110, de huidige standaard voor HTTP-semantiek, en het IANA HTTP Status Code Registry.
Snelle referentie: alle HTTP-statuscodes in één oogopslag
Hier zijn de codes die je in productie tegenkomt, gegroepeerd per klasse. Bookmark deze tabel; de rest van het artikel legt de lastigere uit.
| Code | Naam | Wanneer je hem ziet |
|---|---|---|
| 100 | Continue | Een grote POST-body verzenden met Expect: 100-continue |
| 101 | Switching Protocols | WebSocket-handshake, HTTP/2-upgrade |
| 103 | Early Hints | Server pusht Link-headers vóór het echte antwoord |
| 200 | OK | Standaard succes voor GET, PUT, PATCH |
| 201 | Created | POST die een resource aanmaakt (geeft Location terug) |
| 202 | Accepted | Asynchrone job in de wachtrij, werk nog niet klaar |
| 204 | No Content | DELETE-succes, PUT zonder body in het antwoord |
| 206 | Partial Content | Range-request, video-seek, hervatbare download |
| 301 | Moved Permanently | Oude URL gepensioneerd, zoekmachines dragen link equity over |
| 302 | Found | Tijdelijke redirect, originele URL blijft canoniek |
| 303 | See Other | Post/Redirect/Get-patroon na een form-POST |
| 304 | Not Modified | Conditionele GET met passende ETag of If-Modified-Since |
| 307 | Temporary Redirect | Net als 302, maar methode en body blijven behouden |
| 308 | Permanent Redirect | Net als 301, maar methode en body blijven behouden |
| 400 | Bad Request | Ongeldige JSON, ontbrekend verplicht veld, schema-fout |
| 401 | Unauthorized | Geen credentials of verlopen token |
| 403 | Forbidden | Geauthenticeerd maar niet toegestaan |
| 404 | Not Found | Resource bestaat niet (of je verbergt hem) |
| 405 | Method Not Allowed | POST naar een GET-only endpoint (moet Allow bevatten) |
| 408 | Request Timeout | Client deed te lang over het verzenden van het verzoek |
| 409 | Conflict | Optimistic-lock fout, dubbele sleutel |
| 410 | Gone | Resource permanent verwijderd, komt niet terug |
| 415 | Unsupported Media Type | Verkeerd Content-Type, bv. XML naar een JSON-API |
| 422 | Unprocessable Content | Syntaxis geldig, semantiek ongeldig (validatiefout) |
| 425 | Too Early | TLS 1.3 early-data replay-risico |
| 428 | Precondition Required | Server vereist If-Match om verloren updates te voorkomen |
| 429 | Too Many Requests | Rate limited (moet Retry-After bevatten) |
| 451 | Unavailable for Legal Reasons | DMCA, AVG-verwijdering, geo-blokkade |
| 500 | Internal Server Error | Onafgehandelde exception in je code |
| 501 | Not Implemented | Methode of feature niet ondersteund (zelden in REST) |
| 502 | Bad Gateway | Upstream gaf een ongeldig antwoord terug |
| 503 | Service Unavailable | Onderhoudsmodus of overbelasting |
| 504 | Gateway Timeout | Upstream antwoordde niet op tijd |
| 507 | Insufficient Storage | WebDAV is door zijn schijfruimte heen |
| 508 | Loop Detected | Oneindige redirect of recursie in WebDAV |
| 511 | Network Authentication Required | Captive portal op hotel- of luchthaven-WiFi |
De rest van dit artikel ontleedt elke klasse met beslismatrices, anti-patronen en de SEO-gevolgen wanneer je het verkeerd doet.
Hoe HTTP-statuscodes werken (anatomie van 3 cijfers)
Waarom drie cijfers?
HTTP-statuscodes zijn drie decimale cijfers omdat HTTP/0.9 een signaal van vaste breedte nodig had: klein genoeg zodat een parser er snel op kan vertakken, en groot genoeg om ruimte te laten voor nieuwe codes. Drie cijfers geven 900 mogelijke waarden (100-999), wat ruim voldoende is. Het IANA-register gebruikt er vandaag slechts ongeveer 60 van.
Het eerste cijfer is de klasse. Het tweede en derde zijn de specifieke code binnen die klasse. Een client die 418 niet herkent, moet terugvallen op de afhandeling als een generieke 4xx. RFC 9110 §15 maakt dit expliciet: clients moeten onbekende codes behandelen als de x00 van hun klasse.
De vijf categorieën in één oogopslag
| Klasse | Betekenis | Body verplicht? | Standaard cachebaar? |
|---|---|---|---|
1xx | Informatief: tussentijds, er komt meer | Nee | Nee |
2xx | Succes: request begrepen en geaccepteerd | Vaak | Hangt af van de methode |
3xx | Redirect: verdere actie nodig | Optioneel | 301, 308 ja; 302, 307 nee |
4xx | Clientfout: jouw fout, fix het verzoek | Ja (uitleggen) | Doorgaans nee |
5xx | Serverfout: onze fout, opnieuw proberen kan helpen | Ja (uitleggen) | Nee |
De kolom “standaard cachebaar” is belangrijk. CDN’s en browsers cachen 301 en 308 agressief en voorgoed. De verkeerde redirect-code kiezen in productie is moeilijk terug te draaien omdat gebruikers de redirect lokaal in de cache hebben staan. We komen hier terug op in de SEO-sectie.
Wil je dieper in de URL-structuur kijken (waar de redirect-codes op werken), dan behandelt de URL-encoderen en -decoderen percent-encoding, query strings en de byte-pijplijn die bepaalt wat een URL überhaupt geldig maakt.
1xx: informatief (wanneer je ze echt ziet)
De meeste ontwikkelaars zien jarenlang geen 1xx rechtstreeks. Het zijn tussentijdse antwoorden waarmee de server de client vertelt “ik ben er nog, ga door.” Browser-DevTools verbergt ze meestal, en de meeste HTTP-libraries vouwen ze op in het uiteindelijke antwoord.
Voor elke code hieronder is MDN’s HTTP response status reference de meest toegankelijke kruisreferentie als je een tweede paar ogen op een definitie wilt.
100 Continue
De client stuurt Expect: 100-continue in zijn headers en wacht voordat hij een grote request-body verstuurt. De server antwoordt met 100 Continue als hij de body wil accepteren, of met een 4xx als hij het verzoek toch gaat afwijzen. Dat scheelt bandbreedte bij grote uploads. Er is geen reden om 200 MB te versturen als de server het toch afwijst vanwege een ontbrekende header.
curl -v -H "Expect: 100-continue" \
-H "Content-Type: application/octet-stream" \
--data-binary @big-file.bin \
https://api.example.com/upload
Zie je < HTTP/1.1 100 Continue niet in de verbose output, dan heeft je client de header waarschijnlijk eraf gestript of de server ondersteunt het niet.
101 Switching Protocols
De handshake die een HTTP-verbinding omzet naar een WebSocket- of HTTP/2-verbinding. De client stuurt Upgrade: websocket, de server antwoordt met 101 Switching Protocols, en vanaf dat moment spreekt de verbinding een ander protocol. Je ziet dit in het Netwerk-tabblad van elke chat-app, live dashboard of samenwerkingstool.
103 Early Hints
Een relatief nieuwe code (RFC 8297, 2017) waarmee de server Link-headers voor preload-hints kan sturen voordat het hoofdantwoord klaar is. De browser begint CSS en JS op te halen terwijl de server nog aan het renderen is. In 2026 ondersteunen Cloudflare, Fastly en Vercel allemaal 103 in productie. Het is het moderne alternatief voor HTTP/2 server push, die in Chrome is uitgefaseerd.
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-patroon-check. Ziet je client nooit 1xx-codes wanneer je ze verwacht, dan ligt het probleem meestal bij een reverse proxy. Oudere nginx-versies strippen Expect: 100-continue en 103 Early Hints. Controleer je proxyconfig voordat je aanneemt dat de server stuk is.
2xx: succes (verder dan alleen 200)
200 OK voor alles teruggeven is de meest voorkomende code-smell in REST-API’s. De 2xx-familie draagt semantische informatie die clients slimmer en caches efficiënter maakt.
200 OK
De standaard. Een GET geeft de resource terug, een PUT geeft de bijgewerkte resource terug (of 204), een PATCH geeft de gepatchte resource terug. Heb je geen reden om een specifiekere code te gebruiken, gebruik dan 200.
201 Created
Een POST die een nieuwe resource aanmaakt moet 201 plus een Location-header teruggeven die naar de nieuwe resource wijst. Zo ontdekken RESTful clients de canonieke URL van het ding dat ze net hebben gemaakt.
HTTP/1.1 201 Created
Location: /api/users/42
Content-Type: application/json
{"id": 42, "name": "Ada Lovelace"}
202 Accepted
De server heeft het verzoek geaccepteerd, maar is nog niet klaar met verwerken. Gebruik dit voor asynchroon werk; de client moet pollen, op een webhook abonneren of een status-endpoint controleren. Combineer het met een job-ID in de body.
204 No Content
Succes, geen body. Veel gezien bij DELETE (de resource is weg, wat zou je teruggeven?) en bij PUT-operaties waar de client de nieuwe staat al kent. Browsers veranderen de huidige pagina niet als een formulierverzending 204 teruggeeft, handig voor fire-and-forget acties in single-page apps.
206 Partial Content
Wordt teruggegeven voor range-requests: de client vroeg om bytes 1000-2000 met een Range: bytes=1000-2000-header, en de server reageerde met precies dat stuk. Video-streaming, hervatbare downloads en HTTP-gebaseerde file-syncing bouwen allemaal op 206.
Beslissing: 200 vs 201 vs 204 voor POST
| Scenario | Code | Body |
|---|---|---|
| POST maakt een nieuwe resource | 201 Created | Nieuwe resource (of alleen ID) + Location |
| POST start asynchroon werk, resultaat nog niet klaar | 202 Accepted | Job-ID, poll-URL |
POST is een actie zonder resource (bv. /login) | 200 OK | Actieresultaat (token, status) |
| POST slaagt maar antwoord is leeg | 204 No Content | (geen) |
Kun je niet kiezen tussen 200 en 201, vraag jezelf dan af: “heeft de server een resource gemaakt die nu zijn eigen URL heeft?” Zo ja, 201. Zo nee, 200.
3xx: redirects (301 vs 302 vs 307 vs 308)
Redirects zijn de meest misbruikte klasse. Het verschil tussen 301, 302, 307 en 308 komt neer op drie onafhankelijke vragen: is de verhuizing permanent, blijft de methode behouden, en is het antwoord cachebaar.
301 Moved Permanently
De resource is verhuisd en komt niet terug. Zoekmachines dragen link equity over naar de nieuwe URL. Browsers en CDN’s cachen 301 voor onbepaalde tijd. Redirect je /old naar /new met een 301 en verander je later van gedachten, dan blijven gebruikers met gecachete redirects voor altijd naar /new gaan (of totdat ze hun cache leegmaken).
Historisch gezien kunnen browsers de request-methode bij een 301 herschrijven (POST naar GET). HTTP/1.1 introduceerde daarom 308 om dat te repareren.
302 Found
Tijdelijke redirect. De originele URL is nog steeds canoniek; zoekmachines moeten het origineel blijven indexeren. Gebruik dit voor A/B-test-routing, onderhoudspagina’s of “log in om door te gaan”-flows.
Net als 301 herschreven browsers historisch POST naar GET bij 302. Moet je een POST redirecten en POST houden, gebruik dan 307 in plaats daarvan.
303 See Other
Herschrijft altijd de methode naar GET. Het Post/Redirect/Get-patroon: formulier post naar /submit, server geeft 303 terug met Location: /thank-you, browser doet een GET /thank-you. De thank-you-pagina vernieuwen verstuurt het formulier niet opnieuw. Daar is 303 voor ontworpen.
304 Not Modified
Het conditionele antwoord. Client stuurt If-None-Match: "abc123" (of If-Modified-Since), server controleert of de resource is veranderd, en zo niet geeft 304 terug zonder body. De browser gebruikt zijn gecachete kopie. Zo houdt elk CDN en elke caching-laag je site snel.
307 Temporary Redirect
Net als 302, maar de methode mag niet veranderen. POST blijft POST, body blijft behouden. Gebruik dit als je een tijdelijke redirect wilt op een niet-GET-verzoek.
308 Permanent Redirect
Net als 301, maar de methode mag niet veranderen. De moderne, veiligere keuze voor permanente redirects op API’s die POST/PUT accepteren.
Beslismatrix: welke redirect-code?
| Permanent (cache voor altijd) | Tijdelijk (niet cachen) | |
|---|---|---|
| Methode mag veranderen naar GET | 301 Moved Permanently | 302 Found |
| Methode moet hetzelfde blijven | 308 Permanent Redirect | 307 Temporary Redirect |
Bijzonder geval: wil je specifiek POST → GET (het Post/Redirect/Get-patroon), gebruik dan 303 See Other.
Voor HTML-pagina’s met browsernavigatie zijn 301 en 302 meestal prima omdat GET nu eenmaal GET is. Voor API’s en formulieren hebben 308 en 307 de voorkeur om verrassende methode-herschrijvingen te voorkomen.
4xx: clientfouten (de juiste kiezen)
4xx betekent dat de client iets fout heeft gedaan. Hoe rijker je 4xx-vocabulaire, hoe makkelijker je API te gebruiken is. Clients kunnen op de code vertakken in plaats van foutstrings te parsen.
400 Bad Request
Generieke syntaxisfout. Ongeldige JSON, ontbrekend verplicht veld op structureel niveau, een verzoek dat de server niet eens kan parsen. Parseert het verzoek wel maar faalt het op business-validatie, kies dan 422.
401 Unauthorized vs 403 Forbidden
Het meest verwarde paar in HTTP. De splitsing is simpel zodra je het ziet:
401 Unauthorized: het verzoek mist geldige authenticatie. De server weet niet wie je bent. De credentials opnieuw versturen (of de token verversen) kan het oplossen. Het antwoord moet eenWWW-Authenticate-header bevatten volgens RFC 9110 §15.5.2.403 Forbidden: de server weet wie je bent en weigert toch. Het verzoek opnieuw versturen helpt niet. Je hebt andere credentials of andere permissies nodig.
| Je ziet | Wat klopt |
|---|---|
401 met WWW-Authenticate: Bearer | Geen token, verlopen token of ongeldige token |
403 na een succesvolle login | Ingelogd, maar deze gebruiker mag deze resource niet benaderen |
401 na een succesvolle login | Bug; je wilt waarschijnlijk 403 |
Anti-patroon: 403-als-404. Sommige sites geven 403 terug als een niet-geauthenticeerde gebruiker /admin/dashboard opvraagt. Dat lekt het bestaan van /admin/dashboard. GitHub lost dit op door 404 terug te geven voor private repositories waar je geen lid van bent: de resource “bestaat niet” vanuit jouw perspectief. Dat is een bewuste keuze om informatie te verbergen, geen bug.
404 Not Found vs 410 Gone
Beide zeggen “deze resource is hier niet.” Het verschil zit in permanentie en SEO.
404 Not Found: bestaat misschien, misschien niet, kan terugkomen. Zoekmachines blijven controleren.410 Gone: was hier, opzettelijk verwijderd, komt niet terug. Zoekmachines verwijderen het veel sneller uit de index.
Verwijder je een productpagina en wil je hem nu uit de Google-index, dan is 410 de juiste keuze. Is een URL slechts tijdelijk kapot, dan is 404 prima.
405 Method Not Allowed
De URL bestaat maar accepteert deze methode niet. Het antwoord moet een Allow-header bevatten met de ondersteunde methodes.
HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
{"error": "POST is not allowed on this endpoint"}
De Allow-header vergeten is contractbreuk #1 in zelfgebouwde REST-API’s.
408 Request Timeout
De client begon een verzoek te sturen en werd toen stil. De server gaf het op. Dit verschilt van 504 Gateway Timeout, wat over de upstream gaat. 408 betekent “jij, de client, deed te lang.”
409 Conflict
Het verzoek conflicteert met de huidige staat. Meest voorkomend gebruik: optimistic locking. De client stuurt If-Match: "etag-v3" en de huidige ETag van de server is "etag-v4", dus de update wordt afgewezen met 409.
410 Gone
Zie hierboven: permanente verwijdering. Handig om soft-deleted records uit zoekindexen te halen.
415 Unsupported Media Type
De client heeft een body gestuurd die de server niet begrijpt. XML POSTen naar een JSON-only API levert 415 op. Het antwoord moet een hint geven over acceptabele types.
422 Unprocessable Content
Het verzoek parseert prima, maar faalt op semantische validatie. RFC 9110 heeft dit in 2022 eindelijk gepromoveerd van WebDAV naar de kernspecificatie. Gebruik 422 voor validatiefouten:
{
"error": "validation_failed",
"details": [
{"field": "email", "message": "must be a valid email"},
{"field": "age", "message": "must be at least 13"}
]
}
Kan je API niet kiezen tussen 400 en 422, dan is de vuistregel: 400 voor “ik kan dit niet eens parsen”, 422 voor “ik heb het geparsed en het slaat nergens op.”
425 Too Early
Verstuurd wanneer de server niet het risico wil lopen een verzoek te verwerken dat een TLS 1.3 early-data replay zou kunnen zijn. Vooral relevant voor CDN’s en reverse proxies.
428 Precondition Required
De server eist dat je If-Match of If-Unmodified-Since stuurt om het lost-update-probleem te vermijden. Gebruikt in API’s voor collaboratief bewerken.
429 Too Many Requests
Rate limited. Het antwoord moet Retry-After bevatten (in seconden, of als HTTP-datum) zodat goed gedragende clients kunnen terugschakelen.
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
Het nummer is een verwijzing naar Bradbury. De use-case is niet fictief: DMCA-takedowns, AVG-recht-op-vergetelheid-verwijderingen en geo-blokkades op landniveau rechtvaardigen allemaal 451. Het antwoord moet een Link-header bevatten die naar de juridische instantie wijst die de blokkade oplegt, volgens RFC 7725.
418 I’m a Teapot (de easter egg)
Ja, hij is echt. RFC 2324 (1 april 1998) en de IETF hielden hem in de boeken omdat te veel producten hem als grap implementeerden. Lever 418 niet op in een echte API; de meeste reverse proxies en load balancers gaan er niet goed mee om.
Beslismatrix: welke 4xx?
| Situatie | Code |
|---|---|
| Body is ongeldig of niet te parsen | 400 |
| Geen authenticatie / verlopen token | 401 |
| Geauthenticeerd maar niet toegestaan | 403 |
| URL bestaat niet (of je verbergt hem) | 404 |
| URL bestond, opzettelijk verwijderd | 410 |
| Verkeerde HTTP-methode | 405 (met Allow) |
Verkeerde Content-Type | 415 |
| Optimistic-lock conflict | 409 |
| Validatiefout (parseert, valideert niet) | 422 |
| Rate limited | 429 (met Retry-After) |
| Geblokkeerd om juridische redenen | 451 |
5xx: serverfouten (wat is er echt stuk)
5xx is “onze schuld.” On-call engineers geven het meest om welke 5xx ze om 3 uur ‘s nachts wakker maakte, omdat de code je vertelt welke laag je als eerste moet onderzoeken.
500 Internal Server Error
De vergaarbak. Bijna altijd een onafgehandelde exception die naar de standaard handler van het framework is opgeborreld. Hij vertelt je niets over de oorzaak; daarom is gestructureerde logging hier belangrijker dan de statuscode.
501 Not Implemented
De server ondersteunt de methode helemaal niet. Anders dan 405 (deze methode is niet toegestaan voor deze URL): 501 zegt “deze server heeft geen idee wat PROPFIND zelfs maar betekent.” Zelden in REST-API’s.
502 Bad Gateway
Een reverse proxy of load balancer ontving een ongeldig antwoord van de upstream. De upstream antwoordde wel, maar met rommel: verkeerd protocol, ongeldige headers, halverwege afgebroken verbinding. Zie je 502 van je CDN, dan crasht je origin waarschijnlijk of geeft truncated bodies terug.
503 Service Unavailable
De server bedient nu opzettelijk geen verzoeken. Gebruik dit voor onderhoudsvensters of nette antwoorden bij overbelasting. Hoort Retry-After te bevatten.
504 Gateway Timeout
De reverse proxy wachtte op de upstream en de upstream antwoordde niet op tijd. De upstream is traag of vastgelopen, anders dan 502, waar de upstream met rommel antwoordde.
502 vs 504: de on-call diagnose
| Je ziet | Eerste wat je controleert |
|---|---|
502 Bad Gateway | Upstream antwoordt met ongeldige data; controleer origin-logs op crashes, ongeldige antwoorden, protocol-mismatches |
504 Gateway Timeout | Upstream hangt; controleer origin-CPU, DB-queries, downstream API-calls en de proxy_read_timeout van de proxy |
Een veelvoorkomende verwarring: een database-query van 60 seconden komt naar boven als 504 als je proxy na 30 seconden time-out, maar als 500 als de app-server na 90 seconden time-out en een exception opwerpt. Zelfde root cause, andere code, andere logregel; zorg dat je dashboards beide tonen.
507 Insufficient Storage
WebDAV-specifiek. Schijf vol op de server. Zie je dit van een niet-WebDAV-API, dan rekt iemand de betekenis op.
508 Loop Detected
Oneindige recursie in WebDAV PROPFIND-operaties. Heel zeldzaam.
511 Network Authentication Required
Captive-portal-codes. De WiFi in een hotel of luchthaven stuurt 511 om je browser te vertellen “je moet eerst inloggen op het portaal.” Het antwoord bevat een Location naar de portaalpagina.
Troubleshooting-matrix: welke laag eerst controleren
| Code | App | Proxy | DB | Netwerk |
|---|---|---|---|---|
500 | Ja | — | Misschien (onafgevangen DB-fout) | — |
502 | — | Ja (upstream ongeldig) | — | Misschien (TCP-reset) |
503 | Ja (onderhoudsvlag) | Ja (rate-limit reject) | — | — |
504 | Ja (trage handler) | Ja (timeout-config) | Ja (trage query) | Ja (DNS, packet loss) |
Veelvoorkomende anti-patronen bij HTTP-statuscodes
Deze vijf fouten zijn samen goed voor het meeste slechte code dat ik review.
1. Fouten verpakken in 200 OK
HTTP/1.1 200 OK
{"success": false, "error": "user_not_found"}
Elke monitoring-tool, CDN en cache denkt nu dat het verzoek is geslaagd. Retry-logica faalt. Status-code-bewuste load balancers routeren slecht verkeer naar “gezonde” backends. Dit patroon kwam uit JSON-RPC en is overgenomen door GraphQL. GraphQL doet het omdat gedeeltelijke successen per-veld foutrapportage nodig hebben, wat begrijpelijk is. REST heeft geen excuus: gebruik 4xx voor clientfouten, 5xx voor serverfouten, en stop het gestructureerde detail in de body.
2. 401 en 403 door elkaar gebruiken
Zijn je 401 en 403 niet consistent, dan kunnen aanvallers je API aftasten om te ontdekken welke resources bestaan. Kies een beleid: of geef 404 terug voor “dit mag je niet zien” (de aanpak van GitHub voor private repos) of geef consequent 403 terug. Inconsistentie lekt informatie.
3. 403 verbergen achter 404
Soms juist, vaak een bug. GitHub die 404 teruggeeft voor private repos is bewust: het bestaan van de repo is zelf gevoelig. Maar als je API 404 teruggeeft voor “dit gebruikersaccount is geschorst”, kunnen legitieme gebruikers niet meer onderscheiden of ze de gebruikersnaam verkeerd hebben getypt of zijn geschorst. Documenteer je beleid expliciet en pas het consequent toe.
4. 500 als standaard catch gebruiken
Frameworks maken dit makkelijk, en daar zit het probleem. Elke onafgevangen exception wordt 500 en je alerting kan “database is down” niet onderscheiden van “gebruiker gaf een ongeldige UUID door.” Vang validatiefouten op en geef 400 of 422. Vang NotFound van je ORM op en geef 404. Reserveer 500 voor echt onverwachte storingen, en log een request-ID wanneer je hem opwerpt zodat je kunt correleren.
5. Lange redirect-ketens
Elke hop kost een round trip. Als /old naar /intermediate naar /canonical gaat, dan zijn dat twee extra DNS-lookups en twee extra TCP-handshakes (worst case). Google verlaagt specifiek de crawl-prioriteit voor ketens langer dan 3 hops, en browsers cappen redirect-ketens op rond de 20 om loops te voorkomen. Klap ketens in bij de bron: je CDN-config of de redirect-map van je applicatie.
HTTP-statuscodes en SEO
Zoekmachines behandelen statuscodes als gezaghebbende signalen over of ze een URL moeten houden, weghalen of overdragen. Doe ze fout en je rankings verschuiven.
301 vs 302 (link equity)
301 Moved Permanently draagt PageRank over: Google behandelt de nieuwe URL als de canonieke bestemming van alle signalen die naar de oude URL wijzen. 302 Found draagt geen link equity over (of draagt het langzaam over, afhankelijk van Googles heuristieken). Heb je een URL voorgoed hernoemd, gebruik dan 301. Redirect je een gast naar /login, gebruik dan 302.
404 vs 410 vs Soft 404
Google onderscheidt drie “ontbrekende” toestanden:
404 Not Found: Google controleert periodiek opnieuw en houdt de URL nog een tijdje in de index.410 Gone: Google verwijdert de URL sneller, vaak binnen één crawl-cyclus.- Soft 404: Googles term voor een pagina die
200 OKteruggeeft maar een “niet gevonden”-bericht toont. Google detecteert dit aan content-patronen en behandelt het toch als een404, maar je hebt een crawl-verzoek verspild en mogelijk je echte content verwaterd.
Ben je een verouderde index aan het opschonen, geef dan echte 410’s terug voor permanent verwijderde URL’s.
5xx en crawl-budget
Googles crawler verlaagt zijn snelheid wanneer een site aanhoudend 5xx teruggeeft. Het Crawl Stats-rapport in Search Console laat dit zien. Een aanhoudende piek aan 5xx-fouten kan je crawl-budget dagenlang verlagen, wat betekent dat nieuwe pagina’s er langer over doen om geïndexeerd te worden. Behandel 5xx-percentages als een SEO-metric, niet alleen als een betrouwbaarheidsmetric.
200 OK die eigenlijk stuk is
200 OK teruggeven met een foutpagina (het soft-404-anti-patroon) is het slechtst denkbare scenario voor SEO. Google indexeert het foutbericht, rankt het voor niets, en realiseert zich langzaam dat de pagina stuk is. Geef altijd de juiste statuscode terug vanaf de server, ook al rendert je single-page app een vriendelijke fout-UI.
Hoe je HTTP-statuscodes inspecteert (tooling)
Je kunt niet repareren wat je niet kunt zien. Elke werkende ontwikkelaar zou vloeiend moeten zijn in minstens drie hiervan.
Browser DevTools Netwerk-paneel
Chrome, Firefox en Safari tonen allemaal een Status-kolom in het Netwerk-tabblad. Klik met de rechtermuisknop op de kolomkop om Status Text toe te voegen als die niet zichtbaar is. Handige trucs:
- Preserve log: bewaar entries over navigaties heen zodat je de volledige redirect-keten ziet.
- Filter by status: typ
status-code:5xx(Chrome) om alleen serverfouten te zien. - Replay XHR: rechtermuisknop op een verzoek, dan Replay XHR om het opnieuw te draaien zonder de pagina te herladen.
Klap voor redirects het verzoek uit om elke hop en de statuscode bij elke stap te zien.
curl (het universele antwoord)
curl toont alles. Drie patronen die het meeste debuggen afdekken:
# Just the status code
curl -o /dev/null -s -w "%{http_code}\n" https://api.example.com/users/1
# Headers only (HEAD request, follow redirects)
curl -I -L https://example.com
# Full verbose with request and response headers
curl -v https://api.example.com/users/1
Wanneer je test-URL’s bouwt met speciale tekens in query strings, gebruik dan --data-urlencode zodat curl de encoding voor je afhandelt, of plak de URL in onze URL Decoder & Encoder om te verifiëren welke bytes er daadwerkelijk over de lijn gaan.
# curl encodes the query value for you
curl -G "https://api.example.com/search" \
--data-urlencode "q=hello world & friends"
# Sends: GET /search?q=hello%20world%20%26%20friends
JavaScript fetch
De Response.status-property bevat de integer-code. Response.ok is true voor elke 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 woont dezelfde logica in interceptors:
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() is het Python-idioom voor “maak luid bezwaar bij 4xx/5xx.” Gebruik het in scripts waar je liever exceptions wilt bij fouten dan vertakken op status_code.
Postman en Bruno
Beide laten je in een testscript asserten op statuscodes:
// 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+$/);
});
Draai deze in CI tegen staging om contractbreuken vóór productie te onderscheppen.
FAQ
Wat is het verschil tussen 401 en 403?
401 Unauthorized betekent dat de server niet weet wie je bent: je credentials ontbreken, zijn verlopen of zijn ongeldig. 403 Forbidden betekent dat de server weet wie je bent en toch weigert. Kunnen andere credentials het oplossen, gebruik dan 401. Zo niet, gebruik 403.
Wanneer gebruik ik 301 vs 302?
Gebruik 301 als de verhuizing permanent is: de oude URL komt nooit meer terug, en je wilt dat zoekmachines link equity overdragen naar de nieuwe URL. Gebruik 302 voor tijdelijke redirects waar de originele URL nog canoniek is (login-flows, A/B-testen, onderhoudspagina’s). Geef voor API’s de voorkeur aan 308 en 307 omdat ze de request-methode behouden.
Wat betekent een 502 Bad Gateway-fout?
502 betekent dat een reverse proxy of load balancer een ongeldig antwoord ontving van de upstream-server. De upstream antwoordde, maar met rommel: verkeerd protocol, ongeldige headers of een verbroken verbinding. Het verschilt van 504 Gateway Timeout, waarbij de upstream helemaal niet antwoordde. Eerste plek om te kijken: origin-server-logs op crashes of afgekapte antwoorden.
Wat is een “soft 404”?
Een “soft 404” is een pagina die 200 OK teruggeeft maar in werkelijkheid een “niet gevonden”-bericht toont. Google detecteert deze heuristisch en behandelt ze toch als 404’s. Ze verspillen crawl-budget en kunnen je echte content verwateren. Geef altijd echte 404- of 410-statuscodes terug vanaf de server, ook al rendert je single-page app een vriendelijke fout-UI.
Wanneer gebruik ik 422 in plaats van 400?
Gebruik 400 Bad Request als de server het verzoek niet eens kan parsen: ongeldige JSON, ontbrekende structurele velden, syntaxisfouten. Gebruik 422 Unprocessable Content als het verzoek prima parseert maar faalt op business-validatie: ongeldig e-mailformaat, waarde buiten bereik, semantisch inconsistente velden. De vuistregel: 400 voor syntaxis, 422 voor semantiek.
Hoe reageer ik op 429 Too Many Requests?
Lees de Retry-After-header (een aantal seconden of een HTTP-datum) en wacht minstens zo lang voordat je het opnieuw probeert. Ontbreekt Retry-After, gebruik dan exponential backoff met jitter, beginnend rond 1 seconde. Probeer nooit meteen opnieuw; zo word je geband.
Worden 1xx-informatieve codes in 2026 nog gebruikt?
Ja, maar de meeste zijn onzichtbaar voor applicatiecode. 100 Continue en 101 Switching Protocols zijn basale HTTP/1.1-features. 103 Early Hints wordt steeds vaker gebruikt door Cloudflare, Fastly en Vercel om preload-hints te pushen voordat het hoofdantwoord klaar is, en verbetert Largest Contentful Paint merkbaar. De meeste HTTP-libraries vouwen 1xx op in het uiteindelijke antwoord, dus je ziet ze meestal alleen in DevTools of curl -v.
Is 418 “I’m a teapot” een echte statuscode?
Ja, verrassend genoeg. RFC 2324 was een 1-april-grap uit 1998, maar genoeg producten implementeerden het, dus de IETF hield het in de boeken in RFC 7168. Lever 418 niet op in productie. Veel reverse proxies en load balancers gaan er niet correct mee om, en het dient geen echt doel buiten de grap.