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 TLS

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Find and stop the occupant: `ss -ltnp | grep <port>` (or `lsof -i :<port>`) then restart
  2. For ports <1024, run with CAP_NET_BIND_SERVICE (`setcap cap_net_bind_service=+ep caddy`) or use a high port with redirection
  3. 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

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


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