caddyserver/caddy · critical
listening on %s: %v
Error message
listening on %s: %v
What it means
The listener syscall failed: listenAddr.Listen could not bind the socket at the (possibly port-range-offset) address. The %s is the concrete address (listenAddr.At(portOffset)), %v the OS error such as 'address already in use' or 'permission denied'. This occurs at Start, after config is otherwise valid.
Source
Thrown at modules/caddyhttp/app.go:570
for portOffset := uint(0); portOffset < listenAddr.PortRangeSize(); portOffset++ {
hostport := listenAddr.JoinHostPort(portOffset)
// enable TLS if there is a policy and if this is not the HTTP port
useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort()
if h1ok || h2ok && useTLS || h2cok {
// create the listener for this socket
lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{
KeepAliveConfig: net.KeepAliveConfig{
Enable: srv.KeepAliveInterval >= 0,
Interval: time.Duration(srv.KeepAliveInterval),
Idle: time.Duration(srv.KeepAliveIdle),
Count: srv.KeepAliveCount,
},
})
if err != nil {
return fmt.Errorf("listening on %s: %v", listenAddr.At(portOffset), err)
}
ln, ok := lnAny.(net.Listener)
if !ok {
return fmt.Errorf("network '%s' cannot handle HTTP/1 or HTTP/2 connections", listenAddr.Network)
}
// wrap listener before TLS (up to the TLS placeholder wrapper)
var lnWrapperIdx int
for i, lnWrapper := range srv.listenerWrappers {
if _, ok := lnWrapper.(*tlsPlaceholderWrapper); ok {
lnWrapperIdx = i + 1 // mark the next wrapper's spot
break
}
ln = lnWrapper.WrapListener(ln)
}
if useTLS {
// create TLS listener - this enables and terminates TLSView on GitHub (pinned to 50e54ee279)
Solutions
- Find and stop the occupant: `ss -ltnp | grep <port>` (or `lsof -i :<port>`) then restart
- For ports <1024, run with CAP_NET_BIND_SERVICE (`setcap cap_net_bind_service=+ep caddy`) or use a high port with redirection
- Change the listen address/port in config if the conflict is with another intended service
Example fix
// before: caddy and nginx both on :443 // after: nginx proxies to caddy on 127.0.0.1:8080; caddy listens there
Defensive patterns
Strategy: validation
Validate before calling
// pre-flight: is the port free and bindable?
for _, a := range srvCfg.Listen {
na, _ := caddy.ParseNetworkAddress(a)
ln, err := net.Listen(na.Network, net.JoinHostPort(na.Host, strconv.Itoa(int(na.StartPort))))
if err != nil { return fmt.Errorf("cannot bind %s: %w", a, err) }
ln.Close()
} Try / catch
if err := caddy.Run(cfg); err != nil {
if strings.Contains(err.Error(), "listening on") {
// check occupancy: ss -ltnp / lsof, then retry once after freeing the port
}
} Prevention
- Ensure only one reverse proxy owns :80/:443; chain proxies on loopback
- Grant CAP_NET_BIND_SERVICE (setcap or systemd AmbientCapabilities) instead of running as root
- In containers, avoid duplicating host port bindings across services
When it happens
Trigger: Port already bound by another process or another Caddy server; binding to a privileged port (<1024) without capabilities; missing CAP_NET_BIND_SERVICE in containers; address family mismatch (IPv6 disabled); unix socket path already existing or in a non-writable directory.
Common situations: Two Caddy instances, or Caddy plus nginx/apache on :80/:443; docker port mappings conflicting; systemd socket activation already holding the port; SELinux denying bind.
Related errors
- network '%s' cannot handle HTTP/1 or HTTP/2 connections
- %s app module: start: %v
- starting caddy administration endpoint: %v
- %s: parsing listen address '%s': %v
- starting HTTP/3 QUIC listener: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/957bbf48fb304207.
Report an issue: GitHub.