diff --git a/README.md b/README.md index 477e5c3..160b154 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docker/caddy/Caddyfile.staging b/docker/caddy/Caddyfile.staging index 132c060..1090d81 100644 --- a/docker/caddy/Caddyfile.staging +++ b/docker/caddy/Caddyfile.staging @@ -1,6 +1,28 @@ -http://{$SITE_DOMAIN} { +{$SITE_DOMAIN} { encode gzip + # `tls internal` forces Caddy's own self-signed CA unconditionally, + # instead of guessing from the domain name — its automatic-HTTPS + # heuristic only recognizes specific patterns (bare IPs, "localhost") + # as "obviously private"; anything else (including a private-network + # hostname like this one) it assumes might be a real public domain and + # tries Let's Encrypt, which then fails exactly like it did for + # staging.example.com. This directive skips that guesswork entirely. + # + # SITE_DOMAIN must be an actual HOSTNAME here, not a bare IP: TLS SNI + # (which Caddy needs to pick a certificate) isn't sent for literal IP + # connections per spec — confirmed this fails at the TLS handshake + # itself (curl: "TLS alert, internal error") even though Caddy reports + # the cert as obtained successfully. Point this hostname at the VM's + # IP via /etc/hosts (or your router/local DNS) on whatever machine + # you're browsing from. + tls internal + + # Browser will show an untrusted-cert warning once (self-signed, not + # from a public CA) — click through it, or trust Caddy's root cert + # (docker compose exec caddy cat /data/caddy/pki/authorities/local/root.crt) + # in your OS/browser once to skip that entirely. + # Launch gate: "No staging URLs are publicly indexed." Belt-and-suspenders # with wp_option blog_public=0, which deploy.sh sets on staging. No # basic-auth wall — this box is LAN-only, not reachable from outside.