HTTP codes — Caddy's apartment building and diagnosis
Extracted from the master document on 2026-09-04. §N numbering preserved so cross-references don’t break.
26. HTTP explained with Caddy’s apartment building
Section titled “26. HTTP explained with Caddy’s apartment building”An absurdly useful analogy so you never again mix up 401 with 403, or 404 with 502.
26.1 The cast
Section titled “26.1 The cast”| Technical concept | In the apartment building |
|---|---|
| Caddy | The security guard at the entrance |
| Caddyfile | The guard’s list / rules |
| Client (browser, app) | Juan, the visitor |
| Vaultwarden / backend | Pedro, the resident |
| Nextcloud / another backend | María, another resident |
| Container / service | The house or apartment |
| Port | The door / number you use to contact the service |
| Docker DNS | The internal directory that says where each resident lives |
26.2 The codes
Section titled “26.2 The codes”200 OK — everything went fine.
“I know you, I know who you want to see, I know where they live… and Pedro opened the door.” The request arrived, the route exists, the backend answered correctly and the resource could be delivered.
400 Bad Request — Juan showed up talking nonsense.
“Evening, I’m here to… Pedro… apartment… banana… 74…” — The guard: “Buddy, say what you actually want, because I can’t do anything with this.” The server received the request, but it is malformed or contains data it cannot interpret as a valid request.
401 Unauthorized — no ID.
“I need some ID. Let’s see your driver’s license.” The resource may well exist, but the client hasn’t proved who it is yet. Valid authentication is missing.
403 Forbidden — we know exactly who you are, and that is precisely why you’re not getting in.
“I know who you are and there’s a complaint filed against you. You’re not coming in.” Another version: the guard calls Pedro and Pedro says “Yeah, I know who that is. Don’t let him in.” The client is identified, but is not authorized for that action or resource.
404 Not Found — no María lives here.
“I know who you are, but no María lives here.” / “I checked the register and I can’t find María’s place.” The server understood what was asked for, but did not find the requested route or resource.
500 Internal Server Error — the building’s door broke.
“Everything was going fine… and suddenly the automatic door collapses, starts smoking, and the guard just stands there staring: …damn.” The request may have been valid, but something unexpected failed inside the server or the application while it was trying to process it.
502 Bad Gateway — Pedro passed out.
“Yes, Pedro lives here. I know exactly where. Let me call him…” Pedro is out cold and doesn’t answer. “Juan, I know he lives there, but I can’t get a valid answer out of him.” The reverse proxy received the request and knows the backend, but could not get a valid response out of it. Real example: Caddy knows
vaultwarden:80, but Vaultwarden is down or unreachable.
26.3 The whole movie with Caddy
Section titled “26.3 The whole movie with Caddy”Juan doesn’t walk straight into Pedro’s apartment. He gets to Caddy first, the security guard, who checks his rules to know what to do with the name being asked for:
vault.balserosworld.net │ ▼ Caddy ← the domain block in the Caddyfile │ tells it WHAT TRAFFIC IT RECOGNIZES ▼reverse_proxy vaultwarden:80 ← tells it WHO to send it to and ON WHICH PORT │ ▼ Docker DNS ← resolves the name `vaultwarden` │ ▼ container's internal IP │ ▼ Vaultwarden26.4 404 vs 502 — the difference you can’t forget
Section titled “26.4 404 vs 502 — the difference you can’t forget” Does Caddy have a rule for this hostname/path? │ ┌──────────────────┴──────────────────┐ NO YES │ │ ▼ does the backend answer? "Pedro's not on my list" │ ── no HTTP route ── ┌─────────┴─────────┐ │ YES NO ▼ │ │ 404 / routing error ▼ ▼ (or whatever error the 200 / 4xx / 5xx 502 Bad Gateway backend emits by default) as per the app "Pedro passed out"| Situation | In the apartment building | Result |
|---|---|---|
| Pedro isn’t on the list / there is no route in Caddy | “Pedro? I’ve got no Pedro in the register here.” | Not a typical 502: there may be no HTTP route for that hostname |
| Pedro is on the list, but doesn’t answer | “Pedro lives here, but the bastard passed out.” | 502 Bad Gateway |
Caddy has no route for the hostname ≠Caddy has a reverse_proxy but the backend is unreachable ▲ └── this second case is the classic 50226.5 Final cheat sheet
Section titled “26.5 Final cheat sheet”| Code | The guard’s line |
|---|---|
| 200 | “I know you, I know who you’re here to see, and it all went fine.” |
| 400 | “What the hell are you even asking for? Speak properly.” |
| 401 | “ID first. Let’s see the driver’s license.” |
| 403 | “Ah, yes, I know exactly who you are… and that’s precisely why you’re not getting in.” |
| 404 | “No María lives here.” |
| 500 | “The door broke. Something happened here that wasn’t supposed to happen.” |
| 502 | “Pedro lives here, but the bastard passed out and won’t answer.” |
Rule of thumb:
4xx= the server has a problem with what the client is asking for or with its permissions/identity.5xx= something failed on the server side, or between the proxy and its backend.
27. From the HTTP code to the command — diagnosis table
Section titled “27. From the HTTP code to the command — diagnosis table”This is what turns the analogy into an operational tool:
| You see this | Suspect layer | What to check first |
|---|---|---|
| The name doesn’t resolve | 1 · DNS | dig vault.balserosworld.net / cache and TTL |
| Timeout, Caddy doesn’t even answer | 2 · network | are you on the LAN or on Tailscale? ping 192.168.2.8 |
| Certificate warning | Caddy/TLS | docker compose logs caddy | grep -i acme; is the CF token valid? |
| 404 on a hostname that should exist | 3 · Caddy | does the block exist in the Caddyfile? is it commented out? |
| 502 | 3→4 · backend | docker ps, docker logs <backend>, is it on the proxy network? |
502 only on cloud. |
4 · AIO exception | curl -I http://127.0.0.1:11000 on the host; is extra_hosts present? |
| 401 | 5 · app | login/token missing: it’s the application, not the infrastructure |
| 403 | 5 · app | authenticated but without permission: check role/ACL in the app |
| 500 | 5 · app | the application’s logs, not Caddy’s |
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"docker logs <container> --tail 50docker inspect -f '{{json .NetworkSettings.Networks}}' <container>curl -I http://127.0.0.1:11000