HTTP status codes: complete reference and most common confusion
A complete reference list of HTTP status codes — plus the actual hard choices: 301 vs 308, 401 vs 403, 422 vs 400, 502 vs 503, including how Next.js handles them under the hood.

Memorizing a list of status codes is easy; picking the right one is the hard part. The difference between "couldn't find this record" and "you don't have permission to see this record" in an API, whether a POST turns into a GET during a redirect, whether Google drops an address from its index on an error page — it all comes down to choosing the right three-digit number.
This post has two parts: first, a complete list (use it as a reference), then the codes most commonly picked wrong in practice and how they show up in Next.js.
Five classes
| Class | Meaning | In Short |
|---|---|---|
| 1xx | Informational | Request received, processing continues |
| 2xx | Successful | Request successfully processed |
| 3xx | Redirection | Further action needed to complete |
| 4xx | Client Error | Error is on the client's end |
| 5xx | Server Error | Error is on the server's end |
The distinction looks simple, but there are edge cases: the client sent a valid request, the server is working correctly, but a business rule forbids it — is that 4xx or 5xx? (Answer: 4xx. Details below.)
1xx — Informational
| Code | Name | Meaning |
|---|---|---|
| 100 | Continue | Client can continue sending the request body |
| 101 | Switching Protocols | Server is switching to protocol in Upgrade header (WebSocket handshake) |
| 102 | Processing | Request received, no result yet (WebDAV) |
| 103 | Early Hints | Resource preload hint via Link header before final response is ready |
In practice, 103 Early Hints is the only modern 1xx that's actually useful: while the server prepares the main response, it can tell the browser "start fetching this CSS and font ahead of time."
2xx — Successful
| Code | Name | Meaning |
|---|---|---|
| 200 | OK | Request succeeded; meaning varies by HTTP method |
| 201 | Created | New resource created (usually POST/PUT) |
| 202 | Accepted | Received but not processed yet — for async jobs |
| 203 | Non-Authoritative Information | Metadata does not come from origin server (rare; prefer 200) |
| 204 | No Content | Successful, no body — headers can be meaningful |
| 205 | Reset Content | Client should reset document view |
| 206 | Partial Content | Partial response to Range request (video streaming, resuming downloads) |
| 207 | Multi-Status | Separate status for multiple resources (WebDAV) |
| 208 | Already Reported | Avoids re-enumerating members (WebDAV) |
| 226 | IM Used | Response returned via delta encoding |
Tip: Use 201 and 204
Most APIs return 200 for everything. But when a creation endpoint returns 201 Created + a Location header, and a deletion endpoint returns 204 No Content, the client immediately knows what happened without having to parse a body.
3xx — Redirection
| Code | Name | Preserves method? | Permanent? |
|---|---|---|---|
| 300 | Multiple Choices | — | — |
| 301 | Moved Permanently | No (browsers change POST to GET) | Permanent |
| 302 | Found | No (POST → GET) | Temporary |
| 303 | See Other | No — always changes to GET | Temporary |
| 304 | Not Modified | — (cached response) | — |
| 305 | Use Proxy | ⚠️ Deprecated | — |
| 306 | (unused) | ⚠️ Reserved | — |
| 307 | Temporary Redirect | Yes | Temporary |
| 308 | Permanent Redirect | Yes | Permanent |
This entire table boils down to one distinction: is the HTTP method preserved after redirecting?
- 301 / 302 are legacy codes. Browsers convert POST requests to GET on these — even if the spec didn't strictly say so, that's the de facto standard.
- 307 / 308 were introduced to clear up this ambiguity: whatever method was used stays unchanged. If you redirect a POST, it goes as a POST.
- 303 does the exact opposite on purpose: it forces the method to GET regardless of what came in.
Explanation: What is 303 for?
When a user submits a form, you want to redirect them to a result page. If you use 307, they will hit the result page with a POST request too; if they refresh the page, the browser asks "do you want to resubmit the form?" and you might get duplicate records. 303 prevents this trap by forcing the request to GET. This pattern is called POST/Redirect/GET and is critical in workflows like payment callbacks.
Warning: SEO impact
Use 301/308 for permanent moves; search engines will drop the old URL from their index and transfer link equity to the new one. If you use 302/307, the old URL stays indexed. Getting this choice wrong during site migrations is one of the most common causes of traffic loss.
4xx — Client Error
| Code | Name | Meaning |
|---|---|---|
| 400 | Bad Request | Request is malformed: invalid syntax, body cannot be parsed |
| 401 | Unauthorized | Actually means "unauthenticated" — login required |
| 402 | Payment Required | Payment required; no standard usage |
| 403 | Forbidden | Server knows who you are, but you don't have permission |
| 404 | Not Found | Resource missing — or you want to hide its existence |
| 405 | Method Not Allowed | Method is recognized but not supported on this resource |
| 406 | Not Acceptable | Content negotiation produced no acceptable format |
| 407 | Proxy Authentication Required | Like 401, but for a proxy server |
| 408 | Request Timeout | Idle connection closed by server |
| 409 | Conflict | Request conflicts with current state of resource |
| 410 | Gone | Permanently deleted, address no longer exists |
| 411 | Length Required | Content-Length header required |
| 412 | Precondition Failed | Precondition in conditional request failed |
| 413 | Content Too Large | Request body exceeds server limits |
| 414 | URI Too Long | Target URL is too long |
| 415 | Unsupported Media Type | Request payload format is unsupported |
| 416 | Range Not Satisfiable | Requested range cannot be fulfilled |
| 417 | Expectation Failed | Expectation in Expect header could not be met |
| 418 | I'm a teapot | Joke code (RFC 2324) — teapots can't brew coffee |
| 421 | Misdirected Request | Request sent to wrong server |
| 422 | Unprocessable Content | Syntax is correct, but semantic content is invalid |
| 423 | Locked | Resource is locked (WebDAV) |
| 424 | Failed Dependency | Previous request failed (WebDAV) |
| 425 | Too Early | Refuses to process request that might be replayed (TLS 1.3 early data) |
| 426 | Upgrade Required | Client must upgrade protocol |
| 428 | Precondition Required | Conditional request required — prevents "lost update" problem |
| 429 | Too Many Requests | Rate limit exceeded |
| 431 | Request Header Fields Too Large | Headers are too large |
| 451 | Unavailable For Legal Reasons | Access blocked for legal reasons |
401 or 403?
The most frequently confused pair. Their names are misleading, because 401 actually means "unauthenticated":
- 401 — I don't know who you are. Log in. (No session or invalid token.)
- 403 — I know who you are, but you can't access this resource. (A user with the
userrole trying to access/admin.)
Tip: A third option: 404
Sometimes even 403 reveals too much. Saying "this resource exists, but you can't see it" leaks information. Returning 404 to completely conceal a resource's existence — like GitHub does for private repositories — is a legitimate approach.
400 or 422?
- 400 — I couldn't parse the request. Corrupted JSON, missing required field, wrong data type.
- 422 — I parsed it and the format is valid, but the meaning is invalid. "End date cannot be before start date", "this email is already registered".
In other words: syntax error is 400, business rule violation is 422. In practice, many APIs return 400 for both, which is fine — but if you separate them, client applications can handle "fix the form" vs "fix the request format" without extra code.
409 and 410
- 409 Conflict — The request conflicts with the current state of the resource. Concurrent editing version conflict, trying to create a resource with a slug that already exists.
- 410 Gone — The resource existed, was permanently deleted, and won't come back. The difference from
404is certainty: search engines deindex a410much faster. If you're not sure, stick to404.
429 and Retry-After
If you implement rate limiting, returning 429 alone is only half the job. Send a Retry-After header too — so the client knows how long to wait instead of blindly retrying.
Example: Rate limiting in a Next.js Route Handler
export async function POST(req: Request) {
const allowed = await rateLimit(req);
if (!allowed) {
return new Response(
JSON.stringify({ error: "Çok fazla istek gönderdiniz." }),
{
status: 429,
headers: {
"Content-Type": "application/json",
"Retry-After": "60", // seconds
},
}
);
}
// ...
}451
Used when content is blocked for legal reasons. Named after Ray Bradbury's novel Fahrenheit 451 — the temperature at which paper burns.
5xx — Server Error
| Code | Name | Meaning |
|---|---|---|
| 500 | Internal Server Error | Generic error; server encountered an unexpected condition |
| 501 | Not Implemented | Server does not support the request method |
| 502 | Bad Gateway | Proxy received an invalid response from upstream server |
| 503 | Service Unavailable | Server temporarily unable to handle request (maintenance, overload) |
| 504 | Gateway Timeout | Upstream server failed to respond in time |
| 505 | HTTP Version Not Supported | HTTP version not supported |
| 506 | Variant Also Negotiates | Server configuration error (circular negotiation) |
| 507 | Insufficient Storage | Storage space exhausted (WebDAV) |
| 508 | Loop Detected | Infinite loop detected (WebDAV) |
| 510 | Not Extended | Required HTTP extension not supported |
| 511 | Network Authentication Required | Client needs to authenticate to gain network access (wifi portal) |
502, 503, 504 — which failure is which?
These three are the fastest way to diagnose server deployment issues:
- 502 Bad Gateway — Nginx is up, but the app behind it has crashed or isn't responding on that port. Check your app logs.
- 503 Service Unavailable — Server is up, but refuses to serve traffic right now: maintenance mode, system overload, no instance available in pool. Should be sent with
Retry-Afterand must not be cached. - 504 Gateway Timeout — Upstream app exists but is too slow; proxy gave up waiting. Look for slow queries or slow external API calls.
Caution: Don't return 200 for maintenance mode
Serving a "We're down for maintenance" page with status 200 tricks search engines into thinking that page is your actual content. Maintenance pages should return 503 — search engines won't penalize your ranking for short outages.
Status codes in Next.js
The framework chooses some status codes for you, and knowing what it picks under the hood helps.
Important: Redirect functions
redirect()→ 307 (temporary, preserves method)permanentRedirect()→ 308 (permanent, preserves method)redirect()inside Server Action form submission → 303 (so the browser follows up with GET; if JS is active, client-side navigation handles it anyway)NextResponse.redirect()→ defaults to 307
Next.js picks 307/308 over 302/301 because of the method preservation issue mentioned above: a browser receiving 302 converts POST to GET, turning a POST to /users into a GET to /people.
If you are making a permanent address change, use permanentRedirect() instead of redirect() — or set permanent: true inside your next.config.ts redirects() config. Otherwise, search engines won't deindex the old address.
Calling notFound() returns 404 and renders your not-found.tsx file.
Example: Correct status codes in a Route Handler
import { NextResponse } from "next/server";
export async function POST(req: Request) {
const body = await req.json().catch(() => null);
if (!body) {
return NextResponse.json({ error: "Geçersiz JSON" }, { status: 400 });
}
const parsed = schema.safeParse(body);
if (!parsed.success) {
// Valid format, invalid meaning
return NextResponse.json(
{ error: "Doğrulama hatası", issues: parsed.error.issues },
{ status: 422 }
);
}
const session = await auth();
if (!session) {
return NextResponse.json({ error: "Giriş gerekli" }, { status: 401 });
}
if (session.user.role !== "admin") {
return NextResponse.json({ error: "Yetkiniz yok" }, { status: 403 });
}
const created = await db.post.create({ data: parsed.data });
return NextResponse.json(created, {
status: 201,
headers: { Location: `/api/posts/${created.id}` },
});
}Quick decision guide
Summary: Which code should I return?
- Created a record → 201 +
Location - Deleted, nothing to return → 204
- URL permanently changed → 308 (
301also acceptable for SEO) - Redirecting to result page after form submit → 303
- Unauthenticated → 401
- Authenticated but unauthorized → 403
- Need to hide record existence → 404
- Corrupted JSON → 400
- Valid JSON, business rule violation → 422
- Concurrent edit conflict → 409
- Rate limited → 429 +
Retry-After - Down for maintenance → 503 +
Retry-After - Unexpected error → 500 (write the actual reason to logs, not to the user)
One final note: never expose stack traces to users in error responses. Status codes tell the client what action to take; granular details belong in server logs.
Note: Sources
The status code list is based on the MDN HTTP response status codes reference, and Next.js behaviors follow the redirect documentation.

