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
- Pre-create the directory: mkdir -p /var/lib/headscale && chown headscale:headscale /var/lib/headscale.
- Correct db.path in config so every parent directory is a writable directory.
- 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
- Package installations should ship a systemd RuntimeDirectory/StateDirectory directive.
- In Kubernetes, mount a PersistentVolume at the DB directory, not over a file.
- Lint config with `headscale configtest` (if available) before first run.
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
- path cannot be empty
- creating directory failed with permission error
- building sqlite connection URL: %w
- database type not supported
- foreign key constraints violated
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/b114aeb1e1d4ef01.
Report an issue: GitHub.