juanfont/headscale · error

creating directory for sqlite: %w

Error message

creating directory for sqlite: %w

What it means

Before opening a sqlite database, headscale ensures the parent directory of cfg.Database.Sqlite.Path exists (util.EnsureDir). This error means the directory could not be created: permission denied on the parent, a path component is a file, or the filesystem is read-only.

Source

Thrown at hscontrol/db/db.go:1069

	return &db, err
}

func openDB(cfg types.DatabaseConfig) (*gorm.DB, error) {
	// TODO(kradalby): Integrate this with zerolog
	var dbLogger logger.Interface
	if cfg.Debug {
		dbLogger = util.NewDBLogWrapper(&log.Logger, cfg.Gorm.SlowThreshold, cfg.Gorm.SkipErrRecordNotFound, cfg.Gorm.ParameterizedQueries)
	} else {
		dbLogger = logger.Default.LogMode(logger.Silent)
	}

	switch cfg.Type {
	case types.DatabaseSqlite:
		dir := filepath.Dir(cfg.Sqlite.Path)

		err := util.EnsureDir(dir)
		if err != nil {
			return nil, fmt.Errorf("creating directory for sqlite: %w", err)
		}

		log.Info().
			Str("database", types.DatabaseSqlite).
			Str("path", cfg.Sqlite.Path).
			Msg("Opening database")

		// Build SQLite configuration with pragmas set at connection time
		sqliteConfig := sqliteconfig.Default(cfg.Sqlite.Path)
		if cfg.Sqlite.WriteAheadLog {
			sqliteConfig.JournalMode = sqliteconfig.JournalModeWAL
			sqliteConfig.WALAutocheckpoint = cfg.Sqlite.WALAutoCheckPoint
		}

		connectionURL, err := sqliteConfig.ToURL()
		if err != nil {
			return nil, fmt.Errorf("building sqlite connection URL: %w", err)
		}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Pre-create the directory: mkdir -p /var/lib/headscale && chown headscale:headscale /var/lib/headscale.
  2. Correct db.path in config so every parent directory is a writable directory.
  3. In containers, verify the volume is mounted at the directory, not over a file, and is mounted rw.

Example fix

// before (config.yaml)
db:
  type: sqlite
  sqlite:
    path: /var/lib/headscale/db.sqlite
# -> error if /var/lib/headscale is not creatable by the process user

// fix
sudo mkdir -p /var/lib/headscale
sudo chown headscale:headscale /var/lib/headscale
Defensive patterns

Strategy: validation

Validate before calling

// Deployment check: ensure the sqlite parent dir exists and is writable
// bash -c 'mkdir -p "$(dirname "$DB_PATH")" && [ -w "$(dirname "$DB_PATH")" ]'

Try / catch

// Configuration error — no retry. Fix the path/permissions and restart.
// In code embedding headscale, validate cfg.Sqlite.Path before calling
// db.NewDatabase().

Prevention

When it happens

Trigger: Configuring db.path like /var/lib/headscale/headscale.db when /var/lib/headscale does not exist and the process lacks rights to mkdir it; a path whose parent component is a regular file; container mounts read-only at the wrong place.

Common situations: First run after installation without creating the data directory; Docker/Kubernetes volumeMount pointing at a file instead of a directory; running as a non-root user without write access to the configured parent.

Related errors


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