coredns/coredns · error
both forward ('%s') and proxy level ('%s') TLS servernames a
Error message
both forward ('%s') and proxy level ('%s') TLS servernames are set for upstream proxy '%s' What it means
For a TLS upstream, the server name may be set either at the forward level (tls_servername option, f.tlsServerName) or per-address via a @servername zone suffix. Setting both is ambiguous, so parseStanza rejects the stanza when a TLS host carries a @serverName while f.tlsServerName is also set.
Source
Thrown at plugin/forward/setup.go:199
}
if len(toHosts) == 0 {
return f, fmt.Errorf("no valid upstream addresses found")
}
tlsServerNames := make([]string, len(toHosts))
perServerNameProxyCount := make(map[string]int)
transports := make([]string, len(toHosts))
allowedTrans := map[string]bool{"dns": true, "tls": true, "https": true}
for i, hostWithZone := range toHosts {
host, serverName := splitZone(hostWithZone)
trans, h := parse.Transport(host)
if !allowedTrans[trans] {
return f, fmt.Errorf("'%s' is not supported as a destination protocol in forward: %s", trans, host)
}
if trans == transport.TLS && serverName != "" {
if f.tlsServerName != "" {
return f, fmt.Errorf("both forward ('%s') and proxy level ('%s') TLS servernames are set for upstream proxy '%s'", f.tlsServerName, serverName, host)
}
tlsServerNames[i] = serverName
perServerNameProxyCount[serverName]++
}
p := proxy.NewProxy("forward", h, trans)
f.proxies = append(f.proxies, p)
transports[i] = trans
}
perServerNameTlsConfig := make(map[string]*tls.Config)
if f.tlsServerName != "" {
f.tlsConfig.ServerName = f.tlsServerName
} else {
for serverName, proxyCount := range perServerNameProxyCount {
tlsConfig := f.tlsConfig.Clone()
tlsConfig.ServerName = serverName
tlsConfig.ClientSessionCache = tls.NewLRUClientSessionCache(proxyCount)View on GitHub (pinned to 558c9757a9)
Solutions
- Remove the @servername suffix from the TO address and keep only the forward-level tls_servername.
- Or remove the tls_servername option and use per-address @suffixes for each upstream.
- If different upstreams need different names, use only per-address suffixes and drop the global option.
Example fix
// before
forward . tls://9.9.9.9@dns.quad9.net {
tls_servername dns.quad9.net
}
// after
forward . tls://9.9.9.9 {
tls_servername dns.quad9.net
} Defensive patterns
Strategy: validation
Validate before calling
if tlsServerNameSet && strings.Contains(upstream, "@") {
return fmt.Errorf("cannot set both tls_servername and @suffix for %s", upstream)
} Prevention
- Pick one TLS server-name mechanism: global tls_servername OR per-address @suffix.
- Audit migrated configs for leftover @suffixes.
When it happens
Trigger: A forward stanza with 'tls_servername example.com' AND a TO address like 'tls://9.9.9.9@dns.quad9.net' — the two TLS server names conflict for the same upstream.
Common situations: Migrating configs and keeping both the legacy per-address suffix and a newer global tls_servername; copy-pasting examples that each use a different mechanism; generated configs that always emit tls_servername while upstream strings retain @suffixes.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- no forwarder defined
- this plugin can only be used once per Server Block
- ACME certificate is not ready
- server name is not managed by this ACME configuration
- more than %d TOs configured: %d
AI-assisted analysis of coredns/coredns@558c9757a9 (2026-09-06).
Data as JSON: /api/errors/6d59f7836e82dd06.
Report an issue: GitHub.