caddyserver/caddy · error

resolver address must have exactly one address; cannot call

Error message

resolver address must have exactly one address; cannot call %v

What it means

Thrown while building the ACME server's DNS resolver list: each configured resolver address, after ParseNetworkAddressWithDefaults(v, "udp", 53), must denote exactly one host:port — i.e. PortRangeSize() == 1. A port range (e.g. `:53-54`), a multi-address form, or a network address expanding to more than one port is rejected. Both tls app resolvers and any default resolvers funnel through this loop.

Source

Thrown at modules/caddypki/acmeserver/acmeserver.go:326

	// If no local resolvers are configured, check for global resolvers from TLS app
	resolversToUse := ash.Resolvers
	if len(resolversToUse) == 0 {
		tlsAppIface, err := ash.ctx.App("tls")
		if err == nil {
			tlsApp := tlsAppIface.(*caddytls.TLS)
			if len(tlsApp.Resolvers) > 0 {
				resolversToUse = tlsApp.Resolvers
			}
		}
	}

	for _, v := range resolversToUse {
		addr, err := caddy.ParseNetworkAddressWithDefaults(v, "udp", 53)
		if err != nil {
			return nil, err
		}
		if addr.PortRangeSize() != 1 {
			return nil, fmt.Errorf("resolver address must have exactly one address; cannot call %v", addr)
		}
		ash.resolvers = append(ash.resolvers, addr)
	}

	var resolver *net.Resolver
	if len(ash.resolvers) != 0 {
		dialer := &net.Dialer{
			Timeout: 2 * time.Second,
		}
		resolver = &net.Resolver{
			PreferGo: true,
			Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
				//nolint:gosec
				addr := ash.resolvers[weakrand.IntN(len(ash.resolvers))]
				return dialer.DialContext(ctx, addr.Network, addr.JoinHostPort(0))
			},
		}
	} else {

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Use a single host with one port per resolver entry: `resolvers 1.1.1.1 8.8.8.8` (port defaults to 53/udp)
  2. Remove any port-range syntax (`-`) and multi-host expansion from resolver values
  3. Add one resolver address per list item rather than compressing into one token
  4. Validate with `caddy validate --config` before deploy

Example fix

# before
{
  acme_server
}
tls {
  dns cloudflare {env.CF_TOKEN}
  resolvers 1.1.1.1:53-54
}

# after
tls {
  dns cloudflare {env.CF_TOKEN}
  resolvers 1.1.1.1 8.8.8.8
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate resolver addresses exactly as the server does:
for _, v := range resolvers {
    addr, err := caddy.ParseNetworkAddressWithDefaults(v, "udp", 53)
    if err != nil { return err }
    if addr.PortRangeSize() != 1 {
        return fmt.Errorf("resolver %q expands to %d ports", v, addr.PortRangeSize())
    }
}

Prevention

When it happens

Trigger: Configuring `resolvers` (in the tls app or a DNS-provider context) with a value like `1.1.1.1:53-55`, `dns://1.1.1.1,*`, or an address string that Caddy's network-address parser expands into a port range. The loop over resolversToUse computes PortRangeSize() and returns this error instead of the resolver list.

Common situations: Copy-pasting a resolver list from a DNS load-balancing config where ranges are legal; accidentally using Caddy's listener-style address syntax (which supports port ranges for bind addresses) in a DNS resolver field; trailing comma producing an odd parsed address.

Related errors


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