Skip to content

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.

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

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.

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
│
▼
Vaultwarden

26.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 502
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
Terminal window
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
docker logs <container> --tail 50
docker inspect -f '{{json .NetworkSettings.Networks}}' <container>
curl -I http://127.0.0.1:11000