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
- Use a single host with one port per resolver entry: `resolvers 1.1.1.1 8.8.8.8` (port defaults to 53/udp)
- Remove any port-range syntax (`-`) and multi-host expansion from resolver values
- Add one resolver address per list item rather than compressing into one token
- 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
- One host per resolver entry, no port ranges
- Prefer bare IPs or hostnames without ports (defaults to udp/53)
- CI-validate the tls app config after resolver changes
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
- acme challenge %q is not supported
- certificate lifetime (%s) should be less than intermediate c
- configuring ACME DB: %v
- making folder for CA database: %v
- CA ID is required (use 'local' for the default CA)
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/d97cf4d3594d8a3d.
Report an issue: GitHub.