juanfont/headscale · error · errServerURLSame

server_url cannot use the same domain as base_domain in a wa

Error message

server_url cannot use the same domain as base_domain in a way that could make the DERP and headscale server unreachable

What it means

errServerURLSame is an unexported config validation error in hscontrol/types/config.go:40 returned by isSafeServerURL (config.go:1339) when the server_url hostname equals base_domain exactly. Because Tailscale/MagicDNS claims the whole base_domain for the tailnet, a control plane hosted on that exact host would become unreachable from inside the tailnet.

Source

Thrown at hscontrol/types/config.go:40

	"tailscale.com/tailcfg"
	"tailscale.com/types/dnstype"
	"tailscale.com/util/set"
)

const (
	PKCEMethodPlain string = "plain"
	PKCEMethodS256  string = "S256"

	defaultNodeStoreBatchSize = 100
)

var (
	errOidcMutuallyExclusive     = errors.New("oidc_client_secret and oidc_client_secret_path are mutually exclusive")
	errOIDCIssuerInvalid         = errors.New("oidc.issuer must be a valid http(s) URL")
	errOIDCClientIDRequired      = errors.New("oidc.client_id is required when oidc.issuer is set")
	errOIDCClientSecretRequired  = errors.New("oidc.client_secret or oidc.client_secret_path is required when oidc.issuer is set")
	errServerURLSuffix           = errors.New("server_url cannot be part of base_domain in a way that could make the DERP and headscale server unreachable")
	errServerURLSame             = errors.New("server_url cannot use the same domain as base_domain in a way that could make the DERP and headscale server unreachable")
	errInvalidPKCEMethod         = errors.New("pkce.method must be either 'plain' or 'S256'")
	errTrustedProxyZeroRange     = errors.New("0.0.0.0/0 and ::/0 are not allowed")
	ErrNoPrefixConfigured        = errors.New("no IPv4 or IPv6 prefix configured, minimum one prefix is required")
	ErrInvalidAllocationStrategy = errors.New("invalid prefix allocation strategy")
)

type IPAllocationStrategy string

const (
	IPAllocationStrategySequential IPAllocationStrategy = "sequential"
	IPAllocationStrategyRandom     IPAllocationStrategy = "random"
)

type PolicyMode string

const (
	PolicyModeDB   = "database"
	PolicyModeFile = "file"

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Use a distinct hostname for server_url (e.g. https://hs.example.net) not equal to base_domain
  2. Or pick a different base_domain for MagicDNS (a subdomain dedicated to tailnet hosts)
  3. Ensure the DERP infrastructure also lives outside base_domain
  4. Restart and verify control-plane reachability from a joined node

Example fix

# before
server_url: https://example.com
dns:
  base_domain: example.com

# after
server_url: https://example.com
dns:
  base_domain: ts.example.com
Defensive patterns

Strategy: validation

Validate before calling

host := urlHostname(cfg.ServerURL)
if host == cfg.BaseDomain {
    return errors.New("server_url must not equal base_domain")
}

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: server_url https://example.com with dns.base_domain example.com; simpler single-domain deployments that point both settings at the same host; covered in config_test.go:532/595.

Common situations: Using the naked apex domain for both the headscale server and MagicDNS; misreading docs and assuming base_domain should match the server host.

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/fe336ed505ee395f. Report an issue: GitHub.