juanfont/headscale · error · types.ErrPolicyUpdateIsDisabled

update is disabled for modes other than 'database'

Error message

update is disabled for modes other than 'database'

What it means

ErrPolicyUpdateIsDisabled (hscontrol/types/policy.go:11) is returned by the policy-update API paths (hscontrol/api/v1/policy.go:115 and hscontrol/api/v2/acl.go:101) when the policy mode is not 'database'. Headscale can source its policy from a file on disk or from the database; only 'database' mode is writable through the API, because file mode must be edited on disk and reloaded.

Source

Thrown at hscontrol/types/policy.go:11

package types

import (
	"errors"

	"gorm.io/gorm"
)

var (
	ErrPolicyNotFound         = errors.New("acl policy not found")
	ErrPolicyUpdateIsDisabled = errors.New("update is disabled for modes other than 'database'")
)

// Policy represents a policy in the database.
type Policy struct {
	gorm.Model

	// Data contains the policy in HuJSON format.
	Data string
}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Set policy.mode: database in config.yaml (and remove policy.path if fully migrating) so API/CLI policy updates work
  2. Or keep file mode and edit the HuJSON policy file on the server, then reload — no API write is possible
  3. Check the current mode first via the config or the policy GET endpoint before attempting an update

Example fix

# before (file mode, API updates fail)
policy:
  mode: file
  path: /etc/headscale/acl.hujson

# after (database mode, API updates allowed)
policy:
  mode: database
Defensive patterns

Strategy: validation

Validate before calling

// check policy mode before issuing an API/CLI policy update
if cfg.Policy.Mode != types.PolicyModeDatabase {
	return errors.New("policy updates via API require policy.mode: database in config; file mode must be edited on disk")
}
// safe to PUT /api/v1/policy here

Try / catch

resp, err := client.SetPolicy(ctx, policy)
if err != nil {
	if strings.Contains(err.Error(), types.ErrPolicyUpdateIsDisabled.Error()) {
		// switch to database mode or edit policy.path on disk instead
	}
}

Prevention

When it happens

Trigger: PUT /api/v1/policy or the ACL set endpoint while policy.mode is 'file' (or unset/unrecognized); also CLI commands that call the same API. The handler compares the configured policy mode and responds with an error whose message is exactly 'update is disabled for modes other than database'.

Common situations: Running a config with policy.path set (file mode) but pointing tooling at the API to update ACLs; upgrading from a version where the policy was always file-based; forgetting to add policy.mode: database after switching the policy store to the DB.

Related errors


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