juanfont/headscale · error

opening database: %w

Error message

opening database: %w

What it means

Thrown by bypassDatabase() when db.NewHeadscaleDatabase() cannot open the configured database. Bypass mode skips the running server and attaches directly to SQLite/PostgreSQL; failure means the connection string, driver, permissions, or schema state prevented a usable handle.

Source

Thrown at cmd/headscale/cli/policy.go:34

)

const (
	bypassFlag = "bypass-server-and-access-database-directly" //nolint:gosec // not a credential
)

var errAborted = errors.New("command aborted by user")

// bypassDatabase opens the database directly, bypassing the running server.
// The caller must close the returned handle.
func bypassDatabase() (*db.HSDatabase, error) {
	cfg, err := types.LoadServerConfig()
	if err != nil {
		return nil, fmt.Errorf("loading config: %w", err)
	}

	d, err := db.NewHeadscaleDatabase(cfg)
	if err != nil {
		return nil, fmt.Errorf("opening database: %w", err)
	}

	return d, nil
}

// openBypassDB confirms the destructive bypass action and opens the database
// directly. The caller is responsible for closing the returned handle.
func openBypassDB(cmd *cobra.Command) (*db.HSDatabase, error) {
	if !confirmAction(cmd, "DO NOT run this command if an instance of headscale is running, are you sure headscale is not running?") {
		return nil, errAborted
	}

	return bypassDatabase()
}

func init() {
	rootCmd.AddCommand(policyCmd)

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Ensure headscale is NOT running (the command warns about this for a reason): `systemctl stop headscale` first.
  2. Check the database.path / postgres settings in the config the CLI just loaded.
  3. Verify file permissions on the SQLite file for the user running the CLI.
  4. For PostgreSQL, test connectivity with psql using the same DSN.

Example fix

# before: bypass while server is running (SQLite locked)
headscale policy set --file p.hujson --bypass-server-and-access-database-directly

# after
sudo systemctl stop headscale
headscale policy set --file p.hujson --bypass-server-and-access-database-directly
sudo systemctl start headscale
Defensive patterns

Strategy: validation

Validate before calling

// refuse to bypass while the server holds the database (SQLite case)
func ensureHeadscaleStopped(dbPath string) error {
    if err := exec.Command("fuser", dbPath).Run(); err == nil {
        return fmt.Errorf("another process holds %s — stop headscale first", dbPath)
    }
    return nil
}

Try / catch

d, err := bypassDatabase()
if err != nil {
    if strings.Contains(err.Error(), "opening database") {
        // likely lock/permissions: stop service, fix perms, retry once
    }
    return err
}

Prevention

When it happens

Trigger: `policy get/set/check --bypass...` with a SQLite file that does not exist or is not writable; PostgreSQL refused connection (wrong host/port/credentials); database file locked by a running headscale instance; migrations failing to apply on open.

Common situations: Running bypass against a database while headscale is running (SQLite lock); config's database section pointing at a stale path; Postgres TLS/auth mismatch; read-only filesystem (container, snap).

Related errors


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