Skip to content

feat: serve env WordPress sites over HTTPS (TLS phase 2, fresh-env path) - #77

Open
louisreingold wants to merge 4 commits into
masterfrom
env-https
Open

feat: serve env WordPress sites over HTTPS (TLS phase 2, fresh-env path)#77
louisreingold wants to merge 4 commits into
masterfrom
env-https

Conversation

@louisreingold

Copy link
Copy Markdown
Member

What

Serves env WordPress sites over HTTPS (issue #73, phase 2 — the fresh-env path). The control plane got TLS in #72; this extends it to the per-env sites using the key fact that a Let's Encrypt IP certificate is valid for any port — Caddy terminates TLS for the whole env port range with the cert it already holds. No domain needed.

How

Scaffolder

  • --bind=IP publishes ports (WP + app ports) on one host interface only, via a new WP_BIND prefix in .env interpolated into docker-compose.yml ("${WP_BIND:-}${WP_PORT}:80"). Empty = unchanged local-dev behavior; local scaffolds are byte-identical to before.
  • --public-scheme=http|https lands in .env as PUBLIC_SCHEME and reaches setup scripts as SANDBOX_PUBLIC_SCHEME.
  • wp-config forwarded-proto shim: X-Forwarded-Proto: https sets $_SERVER['HTTPS']='on', so the existing per-request WP_HOME/WP_SITEURL scheme detection (and is_ssl()) yields https behind the proxy. In-network http://wordpress access is untouched (header never set there).
  • The port-free check now tests the interface the stack will actually bind — with --bind=127.0.0.1, Caddy's listener on the public address of the same port is not a conflict (this bit an E2E run before the fix).
  • update carries WP_BIND/PUBLIC_SCHEME through from the project's .env.

Server

  • DEVBOX_PUBLIC_SCHEME=https → new envs scaffold with --bind=127.0.0.1 --public-scheme=https.
  • The scheme is recorded per env at allocation (record.scheme). Envs created before a switch keep their working http URLs and public port binds until migrated; a warm env claimed after the switch reports the scheme it was built with; a duplicate inherits its source's scheme (the copy is the source's dir verbatim). wpUrl, admin-login loginUrl, and all UI links follow the record.
  • clone.js handles the bind-aware app-port line shape.
  • Built-in FutureLayer preset URLs use SANDBOX_PUBLIC_SCHEME.

Deploy

  • deploy/gen-env-sites.sh emits ONE Caddy site block listing every range port as an address, proxied port-for-port via {http.request.local.port}. Two load-bearing details, both learned the hard way: explicit bind <public-ip> (Caddy's default 0.0.0.0 would collide with docker's loopback publishes and the allocator's probe), and no tls directive (a second automation policy for the same IP is rejected — the env sites share the control-plane block's certificate).

Verified on the live box

Deployed with WP_PORT_RANGE=9100-9299 (existing envs sit on 9000–9058; the range starts above them so Caddy never collides with their public binds):

  • Fresh env on :9100 — https:// 200 with a verified LE cert (ssl_verify_result: 0, no -k), 30/30 rendered URLs https (no mixed content), docker-proxy on 127.0.0.1:9100 + caddy on 174.138.43.144:9100, plain http to the port → 400 (nothing served).
  • Admin login end-to-end: mint → https loginUrl (64-char one-time token) → redeem lands authenticated on https://…/wp-admin/index.php, session cookie valid for subsequent https requests. No redirect loops.
  • In-container http://wordpress → 200 (no canonical bounce); DB siteurl untouched.
  • Duplicate of an https env: clone on :9101, https with loopback bind, all URLs rewritten, source restarted healthy.
  • Pre-existing envs still report http:// URLs and work unchanged.
  • No warm pools configured (all warm: 0), so no rebuilds were needed.

Not in this change

🤖 Generated with Claude Code

louisreingold and others added 4 commits August 29, 2026 03:37
The control plane got TLS in PR #72; this extends it to the per-env sites
(issue #73). Key enabler: a Let's Encrypt IP certificate is valid for any
port, so Caddy terminates TLS for the whole env port range with the cert
it already holds — no domain needed.

Scaffolder:
- --bind=IP publishes ports (WP + app ports) on one host interface only,
  via a new WP_BIND prefix in .env interpolated into docker-compose.yml
  ("127.0.0.1:" when a proxy owns the public side; empty = unchanged
  local-dev behavior).
- --public-scheme=http|https lands in .env as PUBLIC_SCHEME and reaches
  setup scripts as SANDBOX_PUBLIC_SCHEME (run-setup-script.sh).
- wp-config forwarded-proto shim: X-Forwarded-Proto: https sets
  $_SERVER['HTTPS']='on' so the existing per-request WP_HOME/WP_SITEURL
  scheme detection (and is_ssl()) yields https behind the proxy.
- `update` carries WP_BIND/PUBLIC_SCHEME through from the project's .env.

Server:
- DEVBOX_PUBLIC_SCHEME=https makes new envs scaffold with
  --bind=127.0.0.1 --public-scheme=https.
- The scheme is recorded PER ENV at allocation (record.scheme): envs
  created before a switch keep working http URLs (and their public port
  binds) until migrated; a warm env claimed after the switch reports the
  scheme it was built with; a duplicate inherits its source's scheme
  (the copy is the source's dir verbatim).
- wpUrl / admin-login loginUrl / UI links all follow the record's scheme
  (ops.js, status.js, ui/app.js). clone.js handles the new bind-aware
  app-port line shape.
- Built-in FutureLayer preset URLs use SANDBOX_PUBLIC_SCHEME.

Deploy:
- deploy/gen-env-sites.sh generates ONE Caddy site block listing every
  range port as an address, proxying port-for-port via the
  {http.request.local.port} placeholder. The explicit `bind <public-ip>`
  is load-bearing: it keeps loopback free for docker's publishes and the
  allocator's port probe (Caddy would otherwise bind 0.0.0.0).
- Caddyfile.example + READMEs document the switch; warm pools must be
  rebuilt after it (they bake the scaffold at build time).

Migration of pre-existing envs is deliberately NOT in this change — they
continue exactly as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant