HTTP Responses and Status Codes

Table of Contents

  1. The Shape of a Response
  2. Status Codes: Five Families
  3. 2xx: Success
  4. 3xx: Redirects
  5. 4xx: The Client Got It Wrong
  6. 5xx: The Server Got It Wrong
  7. Response Headers and Content-Type
  8. Reading a Real Response
  9. 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:

FamilyMeaningIn one phrase
1xxInformational"Hold on…"
2xxSuccess"Here you go."
3xxRedirection"It's over there."
4xxClient error"You asked for something wrong."
5xxServer 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

CodeNameWhen you'll see it
200OKThe normal answer. Here's what you asked for
201CreatedA POST created something new — a user, an order
204No ContentIt worked, and there's nothing to send back — common for DELETE
206Partial ContentHere'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/
CodeNameMeaning
301Moved PermanentlyIt's moved for good. Browsers and search engines remember the new address
302FoundIt's somewhere else for now. Ask here again next time
304Not ModifiedThe copy you already have is still fresh — use it (chapter 9)
307 / 308Temporary / Permanent RedirectLike 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 → 301 to https://.
  • Visiting /my-site without the trailing slash on GitHub Pages → 301 to /my-site/, because it's a folder.
  • Opening a page that needs you to log in → 302 to 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

CodeNameMeaning
400Bad RequestThe request doesn't make sense — invalid JSON, a missing field
401UnauthorizedWho are you? You're not logged in, or your token is missing or expired
403ForbiddenI know who you are, and you're not allowed
404Not FoundNothing at this path
405Method Not AllowedThis path exists, but not for this method — e.g. DELETE on a read-only page
409ConflictThat clashes with what's already there — e.g. the email is taken
422Unprocessable ContentI understood it, but the data isn't valid
429Too Many RequestsSlow 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

CodeNameMeaning
500Internal Server ErrorSomething crashed — usually an unhandled exception in the code
502Bad GatewayA server in front got a broken reply from the server behind it
503Service UnavailableOverloaded or down for maintenance — try later
504Gateway TimeoutA 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-TypeWhat it is
text/html; charset=utf-8A web page (UTF-8 encoded — Khmer works)
text/cssA stylesheet
text/javascriptA script
application/jsonData for an app or API
image/png, image/jpeg, image/webp, image/svg+xmlImages
application/pdfA PDF
application/octet-streamRaw 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 to main.
  • 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

  1. Collect status codes. Run each of these and note the status line. -I shows 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-site
    

    The last two are redirects. What does the location header say in each?

  2. Follow redirects. curl doesn't follow redirects unless you ask. Run curl -IL http://dara-sok.github.io/my-site (-L means "follow"). How many responses do you get before the 200?

  3. 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
    
  4. 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, a 301, or a 204? Then visit a page that doesn't exist on any site and find its 404.

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!