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

  1. Remove the @servername suffix from the TO address and keep only the forward-level tls_servername.
  2. Or remove the tls_servername option and use per-address @suffixes for each upstream.
  3. 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

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

Related errors


AI-assisted analysis of coredns/coredns@558c9757a9 (2026-09-06). Data as JSON: /api/errors/6d59f7836e82dd06. Report an issue: GitHub.