go-gitea/gitea · critical

unable to create directory for log %q: %v

Error message

unable to create directory for log %q: %v

What it means

A start-time panic in Gitea's logging setup. For every configured log writer, LogPrepareFilenameForWriter resolves the log file path (relative paths are joined onto LOG_ROOT_PATH, absolute paths are cleaned) and then runs os.MkdirAll on its directory. If the filesystem refuses to create that directory, Gitea panics with this message instead of continuing with a misconfigured logger.

Source

Thrown at modules/setting/log.go:106

	if !sec.HasKey("logger.xorm.MODE") {
		sec.Key("logger.xorm.MODE").MustString(",") // use default logger
	}
	if sec.HasKey("ENABLE_XORM_LOG") && !sec.Key("ENABLE_XORM_LOG").MustBool() {
		sec.Key("logger.xorm.MODE").SetValue("")
	}
}

func LogPrepareFilenameForWriter(fileName, defaultFileName string) string {
	if fileName == "" {
		fileName = defaultFileName
	}
	if !filepath.IsAbs(fileName) {
		fileName = filepath.Join(Log.RootPath, fileName)
	} else {
		fileName = filepath.Clean(fileName)
	}
	if err := os.MkdirAll(filepath.Dir(fileName), os.ModePerm); err != nil {
		panic(fmt.Sprintf("unable to create directory for log %q: %v", fileName, err.Error()))
	}
	return fileName
}

func loadLogModeByName(rootCfg ConfigProvider, loggerName, modeName string) (writerName, writerType string, writerMode log.WriterMode, err error) {
	sec := rootCfg.Section("log." + modeName)

	writerMode = log.WriterMode{}
	writerType = ConfigSectionKeyString(sec, "MODE")
	if writerType == "" {
		writerType = modeName
	}

	writerName = modeName
	defaultFlags := "stdflags"
	defaultFilaName := "gitea.log"
	if loggerName == "access" {
		// "access" logger is special, by default it doesn't have output flags, so it also needs a new writer name to avoid conflicting with other writers.

View on GitHub (pinned to 43ace7cc8a)

Solutions

  1. Check the panic's path and the OS error: 'permission denied' => chown/chmod the log root for the Gitea user (e.g. chown -R git:git /var/lib/gitea/log); 'not a directory' => remove/rename the conflicting file
  2. Verify LOG_ROOT_PATH (or LOG.ROOT_PATH) in app.ini points to a writable, existing location
  3. For containers: ensure the log volume is mounted read-write and has correct ownership; never point it at a read-only mount
  4. Restart Gitea and confirm the log files are created and rotated normally

Example fix

# before (in app.ini, log root owned by root, gitea runs as 'git')
[log]
ROOT_PATH = /var/lib/gitea/log
# panic: unable to create directory for log "/var/lib/gitea/log": mkdir ...: permission denied

# after
sudo chown -R git:git /var/lib/gitea
# or point ROOT_PATH at a writable dir:
[log]
ROOT_PATH = /home/git/gitea-log
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight check before starting the server
func checkLogDirWritable(path string) error {
	if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
		return fmt.Errorf("log path %q unusable: %w", path, err)
	}
	f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
	if err != nil {
		return fmt.Errorf("log path %q not writable: %w", path, err)
	}
	return f.Close()
}

Try / catch

// Startup panic: recover only in a wrapper that reports and exits non-zero
// (e.g. systemd/docker restart policy). The panic is intentional — Gitea refuses
// to run with an unusable log destination. Fix the filesystem, don't swallow it.

Prevention

When it happens

Trigger: os.MkdirAll fails: permission denied (the Gitea user cannot write under the log root), the resolved path crosses a file (a regular file exists where a directory is needed, e.g. app.ini names FILE_NAME = logs where 'logs' is a file), read-only filesystem/container volume, or an invalid path (unwritable mount, disk full for directory metadata).

Common situations: Running Gitea as a different user than the one owning /data or the log root after a migration; Docker installations where the log volume is mounted read-only or owned by root; FILE_NAME in app.ini set to an absolute path on a volume the container cannot write; SELinux/AppArmor denying writes; a file named like the intended directory left behind by an old setup.

Related errors


AI-assisted analysis of go-gitea/gitea@43ace7cc8a (2026-08-15). Data as JSON: /api/errors/a793236b0b2423bf. Report an issue: GitHub.