Fix staging HTTPS: force tls internal, require a hostname not a bare IP
Two real, verified findings from actually testing the "let Caddy issue a self-signed cert automatically" approach against a literal IP: 1. TLS SNI is not sent for literal IP connections (out of spec — SNI's HostName type explicitly excludes IPs). Confirmed via openssl s_client: the handshake fails with a TLS-layer internal_error, even though Caddy logs "certificate obtained successfully" — Caddy has no way to select a cert without SNI. No Caddyfile config fixes this; the address has to be a hostname. 2. Caddy's automatic-HTTPS heuristic only treats bare IPs and "localhost" as obviously-private. Any other name — including a made-up LAN hostname like "bookstore-staging.test" — it assumes might be real and tries Let's Encrypt, which fails the same way staging.example.com did. Fixed by forcing `tls internal` explicitly in Caddyfile.staging, removing the guesswork entirely. Verified end-to-end with curl --resolve (proper SNI, no real DNS/hosts change needed to test): direct HTTPS 200, HTTP->HTTPS redirect chain 200. README now documents the /etc/hosts requirement and how to trust Caddy's root cert to skip the one-time browser warning. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,10 +46,30 @@ drops into the app container.
|
||||
```
|
||||
cp .env.example .env.staging
|
||||
# fill in SITE_DOMAIN, SITE_URL, DB_*
|
||||
# SITE_DOMAIN must be a HOSTNAME, not a bare IP — see below
|
||||
|
||||
make deploy ENV=staging
|
||||
```
|
||||
|
||||
**SITE_DOMAIN needs to be a hostname, not the VM's IP directly.** TLS SNI
|
||||
(how Caddy picks which certificate to present) isn't sent for literal IP
|
||||
connections per spec, so HTTPS to a bare IP fails at the handshake itself
|
||||
no matter what Caddy does. Pick any hostname (e.g. `bookstore.lan`), add it
|
||||
to `/etc/hosts` (or the Windows equivalent) on whatever machine you're
|
||||
browsing from, pointing at the VM's LAN IP, and use that hostname as both
|
||||
`SITE_DOMAIN` and in `SITE_URL`.
|
||||
|
||||
Caddyfile.staging forces `tls internal` — Caddy's own self-signed CA,
|
||||
issued locally with no external network calls — rather than letting Caddy
|
||||
guess whether the name looks "public" (its automatic heuristic only
|
||||
recognizes bare IPs and `localhost` as obviously-private; anything else,
|
||||
including a made-up LAN hostname, it assumes might be real and tries
|
||||
Let's Encrypt, which then fails). Your browser will show an untrusted-cert
|
||||
warning once; click through it, or trust Caddy's root cert to skip that:
|
||||
```
|
||||
docker compose -p bookstore-staging exec caddy cat /data/caddy/pki/authorities/local/root.crt
|
||||
```
|
||||
|
||||
Staging is `noindex`'d (`blog_public=0` plus the `X-Robots-Tag` header in
|
||||
Caddyfile.staging) so search engines won't index it — there's no basic-auth
|
||||
wall on top of that, since this box is LAN-only and not reachable from
|
||||
|
||||
Reference in New Issue
Block a user