Document two proxy traps found by deploying rather than assuming

The deployment came up healthy, served a valid certificate, and returned 200 —
and was completely unreachable. Two distinct causes, both invisible from
inside the host:

1. Every other site on this proxy binds to a private VNIC address. Caddy
   groups site blocks into servers BY listen address, so a block without
   `bind` landed in a separate server on :443. The specific listener wins for
   traffic arriving on that address, which is all public traffic after NAT, so
   requests hit the server that had never heard of these hostnames and fell
   through to an empty 200. Testing from the host with --resolve 127.0.0.1
   worked perfectly, which is exactly why this was worth chasing from a third
   machine instead of trusting a local check.

2. The CSP blocked the inline pre-paint theme script, so dark-mode users would
   have seen a white flash on every load. Fixed with the script's hash rather
   than 'unsafe-inline', which would have defeated the policy, and rather than
   an external file, which would have reintroduced the flash. Editing that
   script changes its hash and silently breaks it, so that is written down.

Verified from an independent host: health returns JSON, the app serves, an
unauthenticated API call is refused, the short alias redirects, security
headers are present, and the existing sites on the proxy are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 19:31:57 -07:00
parent 7bb8835974
commit 93818a2d2c
2 changed files with 24 additions and 1 deletions
+15
View File
@@ -46,6 +46,21 @@ will adopt its volumes, which is a memorable way to lose a database.
See `Caddyfile.example`. Serve the app and API from the **same** origin.
Two things that will otherwise cost you an hour:
- **If other sites on the host use `bind <address>`, yours must too.** Caddy
groups site blocks into servers by listen address. A block without `bind`
lands in a *separate* server on `:443`, and the more specific listener wins
for traffic arriving on that address — which is all public traffic after NAT.
The symptom is a valid certificate, a 200 response, an empty body, and none
of your headers. It looks like the app is broken; it is that the request
never reached it.
- **The CSP must carry the hash of the inline theme script** in `index.html`.
That script sets light or dark before first paint so dark-mode users do not
get a white flash. Editing it changes the hash and CSP will silently block
it — the browser console prints the hash it expects.
## 5. Verify
```bash