Beginner 12 min read

๐Ÿšฆ HTTP Status Codes and Headers Explained

๐Ÿ“˜ What You Will Learn

  • What an HTTP request and response look like
  • The 5 classes of status codes
  • 10 important codes: 200, 201, 301, 302, 400, 401, 403, 404, 500, 502
  • 5 important headers: Content-Type, Authorization, Cookie, Cache-Control, User-Agent
  • How to read them with curl -I and the browser DevTools
  • How to fix the most common beginner errors

๐Ÿง  What Is HTTP?

HTTP (HyperText Transfer Protocol) is the language a browser and a web server use to talk. HTTPS is the same language, locked with encryption. HTTP lives in the Application layer.

Every web visit has two parts:

  • Request: what your browser asks for
  • Response: what the server answers

Simple meaning:

Browser  ---- request: "GET /page" ---->  Server
Browser  <--- response: 200 OK + headers + page ---  Server

A response has three parts:

HTTP/1.1 200 OK               <-- status line: version, code, short text
Content-Type: text/html       <-- headers: notes about the response
Cache-Control: max-age=3600
                              <-- blank line
<h1>Hello</h1>                <-- body: the actual page or data

A status code is a 3-digit number that tells you how the request went. A header is one line of information in the form Name: value. Headers travel with both requests and responses.

๐Ÿ“ฆ Easy Analogy

Think of ordering food at a restaurant.

You (browser)     =  The customer
Server            =  The restaurant
Request           =  Your order
Status code       =  The waiter's first words
Headers           =  Notes on the order slip and on the tray
Body              =  The food (the web page)

The waiter's first words tell you what happened:

  • "Here you go!" = success
  • "That dish moved to another counter." = redirect
  • "There is a problem with your order." = your mistake
  • "The kitchen has a problem." = the restaurant's mistake

๐Ÿ”ข The 5 Classes of Status Codes

The first digit tells you the class.

Class Meaning Restaurant version
1xx Informational "Hold on, working on it" (rarely seen)
2xx Success "Here you go!"
3xx Redirection "Go to another counter"
4xx Client error "Your order has a problem"
5xx Server error "Our kitchen has a problem"

๐Ÿ“‹ 10 Codes You Must Know

Code Name Meaning
200 OK It worked. Here is what you asked for
201 Created A new thing was made, such as a new user after sign-up
301 Moved Permanently The page has a new address for good. Follow the Location header
302 Found The page is somewhere else for now. Come back to the old address later
400 Bad Request The server could not understand your request, such as broken data
401 Unauthorized You are not logged in, or your login is missing or wrong
403 Forbidden The server knows who you are, but you are not allowed
404 Not Found Nothing exists at this address
500 Internal Server Error The server hit a bug or crashed
502 Bad Gateway A server in the middle got a bad reply from the server behind it

Easy ways to tell similar codes apart:

  • 301 vs 302: 301 = moved for good, 302 = moved for now. Browsers and search engines remember a 301.
  • 401 vs 403: 401 = "who are you?", 403 = "I know you, but no".
  • 500 vs 502: 500 = the server itself broke, 502 = a server in the chain got a bad reply.
  • 4xx vs 5xx: 4xx means check your request. 5xx means the problem is on the server, not you.

๐Ÿ“จ 5 Headers You Must Know

Header Sent in What it does Example
Content-Type Request and response Says what kind of data the body is text/html, application/json
Authorization Request Proves who you are Bearer demo-token-123
Cookie Request Sends back small notes the server gave you earlier session=abc123
Cache-Control Response Says how long the answer can be saved max-age=3600 (1 hour), no-store (do not save)
User-Agent Request Says which browser or tool is asking curl/8.5.0

A few more you will see below:

  • Location: the new address in a 301 or 302 response
  • Set-Cookie: how a server gives you a cookie
  • WWW-Authenticate: comes with a 401 and says how to log in

โš ๏ธ Authorization and Cookie values are secrets. Never share real ones in screenshots or messages.

๐Ÿ› ๏ธ Try It: See Real Status Codes and Headers

There are no real accounts to risk, so we will make a tiny test server on your own computer. It listens only on 127.0.0.1, which means only your own computer can reach it.

Step 1: Create the Test Server

Create a file named test_server.py with this content. You do not need to understand it. It just returns different status codes for different addresses.

import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer


class Handler(BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.1"

    def reply(self, code, body="", content_type="text/html; charset=utf-8", extra=None):
        data = body.encode()
        self.send_response(code)
        self.send_header("Content-Type", content_type)
        self.send_header("Content-Length", str(len(data)))
        for name, value in (extra or {}).items():
            self.send_header(name, value)
        self.end_headers()
        if self.command != "HEAD":
            self.wfile.write(data)

    def handle_any(self):
        path = self.path
        if path == "/":
            self.reply(200, "<h1>Hello from the test server</h1>",
                       extra={"Cache-Control": "max-age=3600"})
        elif path == "/old":
            self.reply(301, extra={"Location": "/new"})
        elif path == "/temp":
            self.reply(302, extra={"Location": "/new"})
        elif path == "/new":
            self.reply(200, "<h1>You reached the new page</h1>")
        elif path == "/private":
            if self.headers.get("Authorization") == "Bearer demo-token-123":
                self.reply(200, "<h1>Welcome, you are logged in</h1>")
            else:
                self.reply(401, "<h1>Please log in</h1>",
                           extra={"WWW-Authenticate": "Bearer"})
        elif path == "/admin":
            self.reply(403, "<h1>You are not allowed here</h1>")
        elif path == "/boom":
            self.reply(500, "<h1>Something broke on the server</h1>")
        elif path == "/gateway":
            self.reply(502, "<h1>Bad reply from the server behind me</h1>")
        elif path == "/login":
            self.reply(200, "<h1>Logged in</h1>",
                       extra={"Set-Cookie": "session=abc123; HttpOnly"})
        elif path == "/whoami":
            info = {"user_agent": self.headers.get("User-Agent"),
                    "cookie": self.headers.get("Cookie")}
            self.reply(200, json.dumps(info), "application/json")
        elif path == "/users" and self.command == "POST":
            length = int(self.headers.get("Content-Length", 0))
            raw = self.rfile.read(length)
            try:
                user = json.loads(raw)
            except ValueError:
                self.reply(400, json.dumps({"error": "Body is not valid JSON"}),
                           "application/json")
                return
            user["id"] = 1
            self.reply(201, json.dumps(user), "application/json",
                       extra={"Location": "/users/1"})
        else:
            self.reply(404, "<h1>Page not found</h1>")

    do_GET = do_HEAD = do_POST = handle_any


ThreadingHTTPServer(("127.0.0.1", 8000), Handler).serve_forever()

Step 2: Start It

Linux:

python3 test_server.py

Windows:

python test_server.py

Nothing prints. That is normal. Leave this window open and open a second terminal for the next steps. To stop the server later, press Ctrl + C in its window.

Step 3: Read the Headers with curl -I

curl is a tool that makes web requests from the terminal. The -I option asks for headers only.

On Windows PowerShell, use curl.exe instead of curl. PowerShell has its own command named curl.

curl -I http://127.0.0.1:8000/

Output (real, from my run):

HTTP/1.1 200 OK
Server: BaseHTTP/0.6 Python/3.13.17
Date: Thu, 29 Oct 2026 18:44:34 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 35
Cache-Control: max-age=3600
  • 200 OK is the status line
  • Content-Type says the body is a web page
  • Cache-Control: max-age=3600 says the answer can be saved for 1 hour

Step 4: See a Redirect (301)

curl -I http://127.0.0.1:8000/old

Output (real, from my run):

HTTP/1.1 301 Moved Permanently
Server: BaseHTTP/0.6 Python/3.13.17
Date: Thu, 29 Oct 2026 18:44:34 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Location: /new

The Location header says where to go. Add -L to make curl follow it:

curl -IL http://127.0.0.1:8000/old

Output (real, from my run, shortened):

HTTP/1.1 301 Moved Permanently
Location: /new

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

Two answers: first the 301, then the 200 from the new address. Try /temp too. It gives a 302.

Step 5: See a 404

curl -I http://127.0.0.1:8000/missing

Output (real, from my run, shortened):

HTTP/1.1 404 Not Found
Content-Type: text/html; charset=utf-8
Content-Length: 23

Step 6: See 401, Then Log In with Authorization

Without a login:

curl -I http://127.0.0.1:8000/private

Output (real, from my run, shortened):

HTTP/1.1 401 Unauthorized
Content-Type: text/html; charset=utf-8
WWW-Authenticate: Bearer

Now send the Authorization header. This token is made up for the test:

curl -I -H "Authorization: Bearer demo-token-123" http://127.0.0.1:8000/private

Output (real, from my run, shortened):

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

Same address. With the right header, 401 became 200.

Step 7: See 201 and 400 with POST

A POST request sends data to the server, for example to create a user. -i shows the headers and the body.

Linux:

curl -i -X POST -H "Content-Type: application/json" -d '{"name": "Asha"}' http://127.0.0.1:8000/users

Windows (Command Prompt):

curl -i -X POST -H "Content-Type: application/json" -d "{\"name\": \"Asha\"}" http://127.0.0.1:8000/users

Output (real, from my run, shortened):

HTTP/1.1 201 Created
Content-Type: application/json
Location: /users/1

{"name": "Asha", "id": 1}

Here Content-Type tells the server the data is JSON. Now send broken data:

curl -i -X POST -H "Content-Type: application/json" -d "not json" http://127.0.0.1:8000/users

Output (real, from my run, shortened):

HTTP/1.1 400 Bad Request
Content-Type: application/json

{"error": "Body is not valid JSON"}

-v (verbose) shows the headers you send (lines with >) and the headers you get back (lines with <).

curl -v -H "Cookie: session=abc123" http://127.0.0.1:8000/whoami

Output (real, from my run, trimmed):

> GET /whoami HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/8.5.0
> Accept: */*
> Cookie: session=abc123
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 56
<
{"user_agent": "curl/8.5.0", "cookie": "session=abc123"}

curl sent its own User-Agent: curl/8.5.0. The server read both headers and repeated them back.

Step 9: Check All the Codes at Once (Linux)

for p in / /old /temp /missing /private /admin /boom /gateway; do echo -n "$p -> "; curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000$p; done

Output (real, from my run):

/ -> 200
/old -> 301
/temp -> 302
/missing -> 404
/private -> 401
/admin -> 403
/boom -> 500
/gateway -> 502

Step 10: See It in the Browser with DevTools

DevTools are tools built into your browser (Chrome, Edge, Firefox) that show what happens behind a page.

  1. Open http://127.0.0.1:8000/old in your browser
  2. Press F12 to open DevTools (on a Mac: Cmd + Option + I)
  3. Click the Network tab
  4. Tick Preserve log (in Firefox it is called Persist Logs), so the redirect stays on screen
  5. Reload the page with F5

You will see two rows: old with status 301, then new with status 200.

  1. Click the old row, then open the Headers tab

You will see:

  • General: the request address, the method (GET) and the status code
  • Response Headers: what the server sent, such as Location: /new
  • Request Headers: what your browser sent, such as User-Agent and Accept

Now try cookies. Open http://127.0.0.1:8000/login (the server gives you a cookie with Set-Cookie). Then open http://127.0.0.1:8000/whoami. It shows your browser's User-Agent text and "cookie": "session=abc123". Your browser sent that cookie by itself.

โŒ Common Errors & Solutions

Error / Symptom Cause Fix
curl: (7) Failed to connect to 127.0.0.1 port 8000 The test server is not running Start it in another window with python3 test_server.py
OSError: [Errno 98] Address already in use (Linux) An old copy of the server is still running Stop it with Ctrl + C, or change 8000 in the script and in your URLs
Still 401 with the token A typo in the header It must be exactly Authorization: Bearer demo-token-123
404 on a page that should exist A typo in the address Check the spelling. Many servers care about capital letters
405 Method Not Allowed with curl -I on a real site That site does not allow header-only requests Use curl -i instead, which does a normal request
curl -I fails in PowerShell In PowerShell, curl is a nickname for a different command Use curl.exe -I
DevTools Network tab is empty You opened it after the page had loaded Keep it open and reload with F5
Redirect rows disappear in DevTools The log was cleared on the new page Tick Preserve log
502 or 500 on a real website The problem is on their server Wait and try again. Site owners should check their server

๐Ÿ’ก Pro Tip

When something fails, read the first digit of the status code first. 4xx means check your own request (the address, your login, your data). 5xx means it is not your fault. Then look at the headers for clues.

๐ŸŽฏ Final Summary

- HTTP = request + response. A response has a status line, headers and a body
- 1xx info, 2xx success, 3xx redirect, 4xx your mistake, 5xx server mistake
- 200 OK, 201 Created, 301 moved for good, 302 moved for now
- 400 bad request, 401 not logged in, 403 not allowed, 404 not found
- 500 server broke, 502 bad reply from the server behind
- Content-Type = kind of data, Authorization = proof of login
- Cookie = notes sent back, Cache-Control = how long to save, User-Agent = who is asking
- curl -I = headers only, curl -v = headers you send (>) and get (<)
- DevTools: F12 > Network > click a request > Headers
- Never share real Authorization or Cookie values

๐Ÿ“Œ Next Tutorial

GET vs POST : how browsers ask for data vs send data, and when to use each.

TechWithJuned ยท Learn โ†’ Execute โ†’ Build

โœ๏ธ Written by TechWithJuned Team

๐Ÿ’ฌ Questions & Discussion

Have a doubt? Ask below โ€” no login needed.

Loading comments...