HTTP Responses and Status Codes
Table of Contents
- The Shape of a Response
- Status Codes: Five Families
- 2xx: Success
- 3xx: Redirects
- 4xx: The Client Got It Wrong
- 5xx: The Server Got It Wrong
- Response Headers and Content-Type
- Reading a Real Response
- Try It Yourself
1. The Shape of a Response
The server reads your request, decides what to send back, and replies. A response has almost the same shape as a request:
HTTP/1.1 200 OK ← status line
Content-Type: text/html; charset=utf-8 ← headers
Content-Length: 1543
Cache-Control: max-age=600
← blank line
<!DOCTYPE html> ← body
<html>
<head><title>Dara Sok</title> …
The only difference is the first line. Instead of a method and a path, it has a status line:
HTTP/1.1 200 OK
│ │ └── reason phrase: a human-readable label
│ └─────── status code: the number that matters
└─────────────── HTTP version
The status code is a three-digit number that tells the client how things went. Programs only look at the number; the words after it are just for humans (HTTP/2 doesn't even send them).
2. Status Codes: Five Families
The first digit tells you the family. Learn these five and you can make sense of any code, even one you've never seen:
| Family | Meaning | In one phrase |
|---|---|---|
| 1xx | Informational | "Hold on…" |
| 2xx | Success | "Here you go." |
| 3xx | Redirection | "It's over there." |
| 4xx | Client error | "You asked for something wrong." |
| 5xx | Server error | "I broke." |
You'll almost never see 1xx codes directly. The other four families are everywhere. The most useful distinction is between 4xx and 5xx: it tells you whose fault an error is, and therefore where to look. A 4xx means look at the request; a 5xx means look at the server's logs.
3. 2xx: Success
| Code | Name | When you'll see it |
|---|---|---|
| 200 | OK | The normal answer. Here's what you asked for |
| 201 | Created | A POST created something new — a user, an order |
| 204 | No Content | It worked, and there's nothing to send back — common for DELETE |
| 206 | Partial Content | Here's the part of the file you asked for — used when streaming video |
200 is by far the most common code on the web. When you build APIs
in the NestJS Crash Course, you'll return 201 from POST endpoints
and 200 from most others — NestJS does this for you by default.
4. 3xx: Redirects
A redirect says: "What you want isn't here — go to this other URL
instead." The new address is in a Location header, and the browser
follows it automatically — you just see the address bar change.
HTTP/1.1 301 Moved Permanently
Location: https://dara-sok.github.io/my-site/
| Code | Name | Meaning |
|---|---|---|
| 301 | Moved Permanently | It's moved for good. Browsers and search engines remember the new address |
| 302 | Found | It's somewhere else for now. Ask here again next time |
| 304 | Not Modified | The copy you already have is still fresh — use it (chapter 9) |
| 307 / 308 | Temporary / Permanent Redirect | Like 302 / 301, but the browser must keep the same method — a POST stays a POST |
You've triggered redirects without noticing:
- Typing
http://on a site that only does HTTPS →301tohttps://. - Visiting
/my-sitewithout the trailing slash on GitHub Pages →301to/my-site/, because it's a folder. - Opening a page that needs you to log in →
302to the login page, which redirects you back afterwards. The Keycloak tutorial is built on exactly this pattern.
304 is the odd one out: it's not really a redirect but a "you
already have it", and it's the key to the caching chapter.
5. 4xx: The Client Got It Wrong
| Code | Name | Meaning |
|---|---|---|
| 400 | Bad Request | The request doesn't make sense — invalid JSON, a missing field |
| 401 | Unauthorized | Who are you? You're not logged in, or your token is missing or expired |
| 403 | Forbidden | I know who you are, and you're not allowed |
| 404 | Not Found | Nothing at this path |
| 405 | Method Not Allowed | This path exists, but not for this method — e.g. DELETE on a read-only page |
| 409 | Conflict | That clashes with what's already there — e.g. the email is taken |
| 422 | Unprocessable Content | I understood it, but the data isn't valid |
| 429 | Too Many Requests | Slow down — you've hit a rate limit |
The pair that confuses everyone is 401 vs. 403. Think of a building with a security guard:
- 401: "I don't know who you are. Show me your ID." → Log in and try again.
- 403: "I've seen your ID. You don't work on this floor." → Logging in again won't help; you need different permissions.
(The name "Unauthorized" for 401 is an old mistake — it really means unauthenticated. The Keycloak tutorial explains the difference between authentication and authorization.)
404 is the one everyone knows. It just means the server has nothing
at that path: a typo, a deleted page, or — on GitHub Pages — a
relative link pointing at the wrong folder, like the /about.html
example from chapter 2.
429 matters as soon as you call other people's APIs. Many services
limit how many requests you can make per minute. A 429 response often
includes a Retry-After header saying how many seconds to wait.
6. 5xx: The Server Got It Wrong
| Code | Name | Meaning |
|---|---|---|
| 500 | Internal Server Error | Something crashed — usually an unhandled exception in the code |
| 502 | Bad Gateway | A server in front got a broken reply from the server behind it |
| 503 | Service Unavailable | Overloaded or down for maintenance — try later |
| 504 | Gateway Timeout | A server in front waited too long for the server behind it |
To understand 502 and 504, you need one more idea: on real websites, your request often doesn't go straight to the app. It first hits a proxy or load balancer — a server whose only job is to pass requests along to the app servers behind it:
Browser ──▶ Proxy (nginx, a CDN, a load balancer) ──▶ Your app
└── 502 if the app's reply is broken
└── 504 if the app takes too long
└── 503 if no app is available
So a 502 often means "your app crashed or isn't running", even though
the error page comes from the proxy. When you deploy something in the
Docker or Cloud+ tutorials and see a 502, check whether your container
is actually up.
A rule worth remembering when you build APIs: a 500 is always a bug. If the user sent bad data, that's a 400 or 422 — tell them what's wrong. A 500 should only happen when something unexpected broke on your side.
7. Response Headers and Content-Type
Responses have headers too. Some you'll meet in later chapters
(Set-Cookie in chapter 8, Cache-Control and ETag in chapter 9).
The most important one right now is Content-Type: it tells the
client what kind of data the body is. The browser doesn't look at
the file extension in the URL; it trusts this header.
| Content-Type | What it is |
|---|---|
text/html; charset=utf-8 | A web page (UTF-8 encoded — Khmer works) |
text/css | A stylesheet |
text/javascript | A script |
application/json | Data for an app or API |
image/png, image/jpeg, image/webp, image/svg+xml | Images |
application/pdf | A PDF |
application/octet-stream | Raw bytes — the browser will usually download it |
These labels are called MIME types (or media types). If a server sends the wrong one, strange things happen: an HTML page shown as raw code, a stylesheet that's ignored, or a page that downloads instead of opening.
That charset=utf-8 matters too: it tells the browser how to turn the
bytes back into characters. Without it, Khmer text can turn into
garbage like ក.
8. Reading a Real Response
Ask your GitHub Pages site for its headers:
curl -I https://dara-sok.github.io/my-site/
You'll get something like this (trimmed):
HTTP/2 200
server: GitHub.com
content-type: text/html; charset=utf-8
last-modified: Mon, 14 Sep 2026 08:12:45 GMT
etag: "6aa7ac7d-60b"
cache-control: max-age=600
date: Thu, 01 Oct 2026 03:20:11 GMT
x-cache: HIT
content-length: 1547
You can already read most of it:
HTTP/2 200— success, over HTTP/2.server— which software answered. Not every site sends this.content-type— an HTML page in UTF-8.last-modified— when the file last changed: your last merge tomain.content-length— the size of the body in bytes.etag,cache-control,x-cache— caching. They get a whole chapter of their own.
Headers starting with x- are custom, made up by whoever runs the
server. You'll see lots of them; search the name if you're curious.
9. Try It Yourself
-
Collect status codes. Run each of these and note the status line.
-Ishows only the headers:curl -I https://dara-sok.github.io/my-site/ curl -I https://dara-sok.github.io/my-site/no-such-page curl -I http://dara-sok.github.io/my-site/ curl -I https://dara-sok.github.io/my-siteThe last two are redirects. What does the
locationheader say in each? -
Follow redirects.
curldoesn't follow redirects unless you ask. Runcurl -IL http://dara-sok.github.io/my-site(-Lmeans "follow"). How many responses do you get before the200? -
Make any code you like. httpbin returns whatever status you ask for:
curl -I https://httpbin.org/status/404 curl -I https://httpbin.org/status/503 -
Find them in the browser. Open DevTools → Network on a busy site and add the Status column if it isn't there. Can you find a
304, a301, or a204? Then visit a page that doesn't exist on any site and find its404.
The browser has its response — usually a page of HTML. In the next chapter, we'll see how it turns that text into what you see on the screen.
Buy Me a Coffee
Found this tutorial helpful? Consider buying me a coffee to support more free content!