tailscale/tailscale · error
unexpected response Content-Type %q
Error message
unexpected response Content-Type %q
What it means
The DoH client in tsdial (used to send DNS over PeerAPI to an exit node's ExitDNS DoH proxy, or to any configured DoH base URL) got an HTTP 200 whose Content-Type is not "application/dns-message" (net/tsdial/dohclient.go:84-86). The endpoint answered, but not with a DNS-over-HTTPS wire response — usually an HTML error/redirect page or a non-DoH service behind that URL.
Source
Thrown at net/tsdial/dohclient.go:85
if err != nil {
return 0, err
}
const dohType = "application/dns-message"
req.Header.Set("Content-Type", dohType)
hc := c.hc
if hc == nil {
hc = http.DefaultClient
}
hres, err := hc.Do(req)
if err != nil {
return 0, err
}
defer hres.Body.Close()
if hres.StatusCode != 200 {
return 0, errors.New(hres.Status)
}
if ct := hres.Header.Get("Content-Type"); ct != dohType {
return 0, fmt.Errorf("unexpected response Content-Type %q", ct)
}
_, err = io.Copy(&c.rbuf, hres.Body)
if err != nil {
return 0, err
}
if c.dnsCache != nil {
c.dnsCache.AddCacheEntry(packet, c.rbuf.Bytes())
}
return len(packet), nil
}
type todoAddr struct{}
func (todoAddr) Network() string { return "unused" }
func (todoAddr) String() string { return "unused-todoAddr" }
View on GitHub (pinned to 6e0912f979)
Solutions
- Verify the URL is a real DoH endpoint: curl -s -o /dev/null -w '%{content_type}' -H 'content-type: application/dns-message' --data-binary @q.bin <url>
- Upgrade both tailnet nodes to the same version so ExitDNS PeerAPI matches
- Inspect the response body — an HTML title usually reveals the captive portal or proxy
- Bypass intercepting proxies for the DoH host
- If you operate the server, fix it to return application/dns-message on the wire path
Example fix
// before: DoH client pointed at the site root baseURL := "https://dns.example.com" // after: RFC 8483 wire endpoint baseURL := "https://dns.example.com/dns-query"
Defensive patterns
Strategy: try-catch
Validate before calling
// preflight: confirm the endpoint speaks RFC 8483 before wiring it as DoH
req, _ := http.NewRequest(http.MethodPost, baseURL, bytes.NewReader(dnsQueryMsg))
req.Header.Set("Content-Type", "application/dns-message")
if res, err := hc.Do(req); err == nil {
if ct := res.Header.Get("Content-Type"); ct != "application/dns-message" {
// not a DoH endpoint; fail fast with a clear configuration error
}
} Try / catch
if err != nil && strings.Contains(err.Error(), "unexpected response Content-Type") {
// endpoint is wrong or something is intercepting: do not retry the same URL;
// check the URL path, then the network path (captive portal / proxy)
} Prevention
- Point DoH clients at known wire endpoints (path /dns-query), never site roots
- Keep exit node and client on the same tailscale version
- Inspect response bodies when DoH behaves oddly — HTML means interception or wrong URL
When it happens
Trigger: baseURL points at a path that returns text/html (site root instead of /dns-query); a captive portal or filtering proxy answering 200 with HTML; version skew where the exit node's PeerAPI does not implement the DoH endpoint; middleboxes rewriting Content-Type.
Common situations: Exit node and client on different Tailscale versions during upgrades; captive portals; pointing the DoH client at a management URL; corporate proxies rewriting responses.
Related errors
- failed refresh apk repository indexes: %w, output: %s
- failed refresh pkg repository indexes: %w, output: %s
- invalid port in address %q
- sending request: %w
- DNS lookup returned no results for %q
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/a6deceeb493e9e46.
Report an issue: GitHub.