juanfont/headscale · error

loading database policy on startup: %w

Error message

loading database policy on startup: %w

What it means

Thrown when pool.Retry(hsic.reloadDatabasePolicy) exhausts its retries. In PolicyModeDB with an aclPolicy set, the policy must be loaded into the database via the container CLI before headscale starts (the container sleeps, then boots). reloadDatabasePolicy runs the load command repeatedly; persistent failure means headscale cannot ingest the policy file.

Source

Thrown at integration/hsic/hsic.go:636

		err = hsic.WriteFile(tlsKeyPath, hsic.tlsKey)
		if err != nil {
			return nil, fmt.Errorf("writing TLS key to container: %w", err)
		}
	}

	for _, f := range hsic.filesInContainer {
		err := hsic.WriteFile(f.path, f.contents)
		if err != nil {
			return nil, fmt.Errorf("writing %q: %w", f.path, err)
		}
	}

	// Load the database from policy file on repeat until it succeeds,
	// this is done as the container sleeps before starting headscale.
	if hsic.aclPolicy != nil && hsic.policyMode == types.PolicyModeDB {
		err := pool.Retry(hsic.reloadDatabasePolicy)
		if err != nil {
			return nil, fmt.Errorf("loading database policy on startup: %w", err)
		}
	}

	return hsic, nil
}

func (t *HeadscaleInContainer) ConnectToNetwork(network *dockertest.Network) error {
	return t.container.ConnectToNetwork(network)
}

func (t *HeadscaleInContainer) hasTLS() bool {
	return len(t.tlsCert) != 0 && len(t.tlsKey) != 0
}

// Shutdown stops and cleans up the Headscale container.
func (t *HeadscaleInContainer) Shutdown() (string, string, error) {
	stdoutPath, stderrPath, err := t.SaveLog("/tmp/control")
	if err != nil {

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Run the load command manually: docker exec <hs> headscale policy load /etc/headscale/acl.hujson ... to see the exact policy error
  2. Validate the policy HuJSON locally against hscontrol/policy/v2 semantics
  3. Check that any tags/users referenced by the policy exist or are declared in the policy itself
  4. If the mode was unintended, use file policy mode instead of PolicyModeDB
Defensive patterns

Strategy: validation

Validate before calling

// Validate policy once before container setup
tmp := t.TempDir() + "/acl.hujson"
os.WriteFile(tmp, serializedPolicy, 0o600)
if out, err := exec.Command(headscaleBin, "policy", "load", "--test", tmp).CombinedOutput(); err != nil {
    t.Fatalf("policy rejected: %v\n%s", err, out)
}

Try / catch

if err := pool.Retry(hsic.reloadDatabasePolicy); err != nil {
    // re-run the load command manually to capture the exact CLI error for the report
    t.Fatalf("policy load failed; policy likely invalid: %v", err)
}

Prevention

When it happens

Trigger: HeadscaleInContainer built with WithACLPolicy and database policy mode: `headscale policy load` (or equivalent) inside the container keeps failing — invalid HuJSON/ACL syntax, unknown autogroup/tag references, or the headscale binary erroring on the policy — until pool.Retry gives up.

Common situations: Policy file with syntax errors or referencing undefined tags/users; policy written to the container (error 675 succeeded) but semantically invalid; version change in policy semantics between headscale releases.

Related errors


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