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
- 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
- Verify LOG_ROOT_PATH (or LOG.ROOT_PATH) in app.ini points to a writable, existing location
- For containers: ensure the log volume is mounted read-write and has correct ownership; never point it at a read-only mount
- 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
- After changing LOG_ROOT_PATH or any [log.*] FILE_NAME, run the server once in staging and confirm files appear
- In Docker, ensure the log volume is mounted rw and chown it to the runtime user (chown -R git:git /data/log)
- Never let a regular file exist where a log directory is expected; names like 'log' vs 'log/' collide easily
- Automate a writable-check for the log root in the deployment entrypoint before exec'ing gitea
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
- Markup sanitizer rule regexp must start with ^ and end with
- Failed to update issue title: ${resp.statusText}
- Failed to update PR target branch: ${resp.statusText}
AI-assisted analysis of go-gitea/gitea@43ace7cc8a (2026-08-15).
Data as JSON: /api/errors/a793236b0b2423bf.
Report an issue: GitHub.