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
- Print the expanded value inside the same environment (caddy adapt --pretty, or log the env) and correct it to network/host:port form
- Use Caddyfile-style env defaults {$VAR:...} so a missing var cannot produce an empty address
- 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
- Run `caddy validate` with the exact environment (env_file/unit) used at runtime
- Use {$VAR:default} defaults so expansion can never yield an empty or scheme-prefixed string
- Log resolved listen addresses at startup before Caddy starts
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
- server %s, listener %d: %v
- replacing listen address: %v
- listening on %s: %v
- network '%s' cannot handle HTTP/1 or HTTP/2 connections
- evaluated placeholder %s%s%s is empty
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/5d64cbf3a53c2339.
Report an issue: GitHub.