caddyserver/caddy · error

%s: parsing listen address '%s': %v

Error message

%s: parsing listen address '%s': %v

What it means

During Start (after provisioning and placeholder replacement), each listen address is parsed again with caddy.ParseNetworkAddress. If a placeholder-expanded address no longer parses (e.g. an env var supplied a value like "http://x" or omitted the port), Start fails with this error naming server and address.

Source

Thrown at modules/caddyhttp/app.go:534

			// this type of connection by checking if it's behind a TLS listener wrapper or if it implements tls.ConnectionState.
			srv.server.Protocols.SetUnencryptedHTTP2(true)
			// when h2c is enabled but h2 disabled, we already removed h2 from NextProtos
			// the handshake will never succeed with h2
			// http2.ConfigureServer will enable the server to handle both h2 and h2c
			h2server := new(http2.Server)
			//nolint:errcheck
			http2.ConfigureServer(srv.server, h2server)
		}

		// this TLS config is used by the std lib to choose the actual TLS config for connections
		// by looking through the connection policies to find the first one that matches
		tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx)
		srv.configureServer(srv.server)

		for lnIndex, lnAddr := range srv.Listen {
			listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
			if err != nil {
				return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err)
			}

			srv.addresses = append(srv.addresses, listenAddr)

			protocols := srv.Protocols
			if srv.ListenProtocols != nil && srv.ListenProtocols[lnIndex] != nil {
				protocols = srv.ListenProtocols[lnIndex]
			}

			protocolsUnique := map[string]struct{}{}
			for _, protocol := range protocols {
				protocolsUnique[protocol] = struct{}{}
			}
			_, h1ok := protocolsUnique["h1"]
			_, h2ok := protocolsUnique["h2"]
			_, h2cok := protocolsUnique["h2c"]
			_, h3ok := protocolsUnique["h3"]

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Print the expanded value inside the same environment (caddy adapt --pretty, or log the env) and correct it to network/host:port form
  2. Use Caddyfile-style env defaults {$VAR:...} so a missing var cannot produce an empty address
  3. Run `caddy validate` with the same env as the real service

Example fix

// before
"listen": ["{env.BIND}"] with BIND="https://0.0.0.0"
// after
"listen": ["{env.BIND}:443"] with BIND="0.0.0.0"
Defensive patterns

Strategy: validation

Validate before calling

// validate AFTER placeholder expansion, in the runtime environment
for _, a := range srvCfg.Listen {
    expanded, err := repl.ReplaceOrErr(a, true, true)
    if err != nil { return err }
    if _, err := caddy.ParseNetworkAddress(expanded); err != nil {
        return fmt.Errorf("expanded listen %q invalid: %w", expanded, err)
    }
}

Prevention

When it happens

Trigger: A listen placeholder resolving to a malformed value at runtime: {env.PORT}="8080" alone with no host separator is fine, but {env.ADDR}="https://0.0.0.0" or an empty value yields an unparseable address.

Common situations: Config passes validation with literal addresses but Start uses expanded ones; env vars changed between validate and run; container env injecting URLs instead of host:port.

Related errors


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