Obtain certificates, and wait for the one that matters

The public shape now works end to end: real ports, Caddy in front, and both
programs that need certificates getting them from the same CA -- Caddy for
the front ends over TLS-ALPN-01, the mail server for its own names over
HTTP-01, which Caddy forwards on port 80.

Proved in the lab against Pebble, with a DNS stub answering every name with
the machine's own address, so no public name or public CA is involved:
twenty checks, ending with IMAPS and submissions presenting a certificate
for the mail host that verifies against the CA, and the webmail sending
sign-in to the server as the first-party client the server registered.

Two things the test found, both of which would have shipped:

- The proxy fronted four of the server's five names. The server puts
  ua-auto-config in its own certificate too, so its challenge was never
  forwarded, one name failed, and the whole order failed with it -- leaving
  the mail ports on a self-signed certificate while everything else looked
  healthy. The list now matches what the server asks for.

- Nothing waited for the certificate. An order that fails is not retried on
  its own and a restart does not start a new one, so the install declared
  itself finished over a self-signed certificate. It now waits, asks again
  every 45 seconds, and reports the issuer -- or says plainly that the
  server will keep trying once the domain resolves here, which is the
  ordinary case on a first install.

--acme-directory and --acme-ca-root are what let a private CA be used: the
root is added to the server image's own bundle and given to Caddy, because
neither sees the other's trust store.
This commit is contained in:
2026-09-22 19:11:52 -07:00
parent 7141e565ea
commit 8725d8c11b
6 changed files with 263 additions and 14 deletions
+9 -2
View File
@@ -71,10 +71,17 @@ type Stack struct {
CABundle bool
}
// ServerNames is every name the server's certificate and the proxy need.
// ServerNames is every name the server's certificate and the proxy need:
// the mail host, and the four service names the server puts in its own
// certificate by default.
//
// The list has to match what the server asks for exactly. Leaving
// ua-auto-config out of the proxy meant its HTTP-01 challenge was not
// forwarded, the whole order failed on that one name, and the mail ports
// served a self-signed certificate while every other name validated.
func (s Stack) ServerNames() []string {
names := []string{s.MailHost}
for _, n := range []string{"autoconfig", "autodiscover", "mta-sts"} {
for _, n := range []string{"autoconfig", "autodiscover", "mta-sts", "ua-auto-config"} {
names = append(names, n+"."+s.Domain)
}
return names