juanfont/headscale · error · ErrDirectoryPermission

creating directory failed with permission error: %s

Error message

creating directory failed with permission error: %s

What it means

Returned by util.EnsureDir when os.MkdirAll fails with a permission error while creating a directory (wrapped with ErrDirectoryPermission and the path). Headscale creates runtime directories (e.g. certificate/state dirs) at startup and fails fast when the filesystem denies writes.

Source

Thrown at hscontrol/util/file.go:56

}

func GetFileMode(key string) fs.FileMode {
	modeStr := viper.GetString(key)

	mode, err := strconv.ParseUint(modeStr, Base8, BitSize64)
	if err != nil {
		return PermissionFallback
	}

	return fs.FileMode(mode) //nolint:gosec // file mode is bounded by ParseUint
}

func EnsureDir(dir string) error {
	if _, err := os.Stat(dir); os.IsNotExist(err) { //nolint:noinlineerr
		err := os.MkdirAll(dir, PermissionFallback)
		if err != nil {
			if errors.Is(err, os.ErrPermission) {
				return fmt.Errorf("%w: %s", ErrDirectoryPermission, dir)
			}

			return fmt.Errorf("creating directory %s: %w", dir, err)
		}
	}

	return nil
}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. chown the directory tree to the user running headscale: sudo chown -R headscale:headscale /var/lib/headscale
  2. Fix container volume permissions (match PUID/PGID or use a named volume)
  3. Verify the filesystem is writable and not covered by SELinux/AppArmor policy

Example fix

# before
sudo mkdir -p /var/lib/headscale && ls -ld /var/lib/headscale  # owned by root
# after
sudo mkdir -p /var/lib/headscale && sudo chown -R headscale:headscale /var/lib/headscale
Defensive patterns

Strategy: validation

Validate before calling

import "os"

func canCreateDir(dir string) error {
    parent := filepath.Dir(dir)
    if _, err := os.Stat(parent); err != nil {
        return fmt.Errorf("parent %s missing: %w", parent, err)
    }
    if err := unix.Access(parent, unix.W_OK); err != nil { // or attempt a temp file
        return fmt.Errorf("no write permission on %s", parent)
    }
    return nil
}

Try / catch

if err := util.EnsureDir(path); err != nil {
    if errors.Is(err, util.ErrDirectoryPermission) {
        // prompt to chown / fix volume, do not retry blindly
    }
    return err
}

Prevention

When it happens

Trigger: Calling EnsureDir on a path under a directory owned by root or another user while headscale runs unprivileged; read-only filesystems; containers where the volume is mounted with restrictive ownership.

Common situations: Running headscale as a non-root user for the first time against /var/lib/headscale created by root; Docker bind-mounts with wrong uid/gid; SELinux/AppArmor denials surfacing as EACCES.

Related errors


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