caddyserver/caddy · critical

finalizing automatic HTTPS: %v

Error message

finalizing automatic HTTPS: %v

What it means

After all servers are listening, automaticHTTPSPhase2 begins actual certificate management (issuing/renewing via ACME/Internal CAs, solving challenges, loading managed certs). Any failure in that startup — a CA rejecting issuance, challenge solver failure, cache errors — is wrapped as 'finalizing automatic HTTPS'. The server sockets are already open, but Caddy aborts Start.

Source

Thrown at modules/caddyhttp/app.go:670

						// Can only serve h3 with TLS enabled
						app.logger.Warn("HTTP/3 skipped because it requires TLS",
							zap.String("network", listenAddr.Network),
							zap.String("addr", hostport))
					}
				}
			}
		}

		srv.logger.Info("server running",
			zap.String("name", srvName),
			zap.Strings("protocols", srv.Protocols))
	}

	// finish automatic HTTPS by finally beginning
	// certificate management
	err := app.automaticHTTPSPhase2()
	if err != nil {
		return fmt.Errorf("finalizing automatic HTTPS: %v", err)
	}

	return nil
}

// stdlibLogPrefixPanic is the prefix Go's net/http server uses when it writes a
// recovered handler panic (and its stack trace) to http.Server.ErrorLog.
// See the deferred recover in net/http.(*conn).serve.
const stdlibLogPrefixPanic = "http: panic serving"

// serverErrorLogger returns a *log.Logger suitable for http.Server.ErrorLog
// that forwards the standard library's server messages to Caddy's structured
// logger. Most of these messages are low-signal and logged at DEBUG, but
// recovered handler panics indicate a real server-side error, so they are
// logged at ERROR to stay visible at the default log level.
func serverErrorLogger(logger *zap.Logger) *log.Logger {
	return log.New(stdlibLogRouter{logger: logger}, "", 0)
}

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Read the wrapped error to identify the failing CA/domain/challenge
  2. For local/test use, force the internal CA: `tls internal` on that site so no external ACME is attempted
  3. Open/forward ports 80+443 or switch to a DNS-01 challenge with a provider plugin; wait out rate limits or use Let's Encrypt staging while testing

Example fix

# before
example.com {
  reverse_proxy localhost:8080
}
# after (local testing)
example.com {
  tls internal
  reverse_proxy localhost:8080
}
Defensive patterns

Strategy: fallback

Validate before calling

// pre-flight DNS check for ACME-managed domains
for _, d := range managedDomains {
    addrs, err := net.LookupHost(d)
    if err != nil || len(addrs) == 0 {
        return fmt.Errorf("domain %s does not resolve; issuance will fail", d)
    }
}

Try / catch

if err := caddy.Run(cfg); err != nil {
    if strings.Contains(err.Error(), "finalizing automatic HTTPS") {
        // fallback: rerun with tls internal / ACME staging, or surface the wrapped cause
    }
}

Prevention

When it happens

Trigger: ACME account/issuance errors (e.g. Let's Encrypt rate limit, unreachable DNS), failing HTTP-01/TLS-ALPN challenges because ports 80/443 are not externally reachable, an explicitly configured internal CA that fails to initialize, or on-demand cert config errors.

Common situations: First deploy behind a firewall blocking port 80 so HTTP-01 fails; DNS for the domain not yet pointing at the server; staging->production LE quota exhaustion; test environments without internet access.

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/e7a8274325ce855a. Report an issue: GitHub.