{"record":{"id":"a793236b0b2423bf","repo":"go-gitea/gitea","slug":"unable-to-create-directory-for-log-q-v","errorCode":null,"errorMessage":"unable to create directory for log %q: %v","messagePattern":"unable to create directory for log %q: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"modules/setting/log.go","lineNumber":106,"sourceCode":"\tif !sec.HasKey(\"logger.xorm.MODE\") {\n\t\tsec.Key(\"logger.xorm.MODE\").MustString(\",\") // use default logger\n\t}\n\tif sec.HasKey(\"ENABLE_XORM_LOG\") && !sec.Key(\"ENABLE_XORM_LOG\").MustBool() {\n\t\tsec.Key(\"logger.xorm.MODE\").SetValue(\"\")\n\t}\n}\n\nfunc LogPrepareFilenameForWriter(fileName, defaultFileName string) string {\n\tif fileName == \"\" {\n\t\tfileName = defaultFileName\n\t}\n\tif !filepath.IsAbs(fileName) {\n\t\tfileName = filepath.Join(Log.RootPath, fileName)\n\t} else {\n\t\tfileName = filepath.Clean(fileName)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(fileName), os.ModePerm); err != nil {\n\t\tpanic(fmt.Sprintf(\"unable to create directory for log %q: %v\", fileName, err.Error()))\n\t}\n\treturn fileName\n}\n\nfunc loadLogModeByName(rootCfg ConfigProvider, loggerName, modeName string) (writerName, writerType string, writerMode log.WriterMode, err error) {\n\tsec := rootCfg.Section(\"log.\" + modeName)\n\n\twriterMode = log.WriterMode{}\n\twriterType = ConfigSectionKeyString(sec, \"MODE\")\n\tif writerType == \"\" {\n\t\twriterType = modeName\n\t}\n\n\twriterName = modeName\n\tdefaultFlags := \"stdflags\"\n\tdefaultFilaName := \"gitea.log\"\n\tif loggerName == \"access\" {\n\t\t// \"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.","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/go-gitea/gitea/blob/43ace7cc8ad5fa20027b1ca5b3ab5f1134972ed5/modules/setting/log.go#L88-L124","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"# before (in app.ini, log root owned by root, gitea runs as 'git')\n[log]\nROOT_PATH = /var/lib/gitea/log\n# panic: unable to create directory for log \"/var/lib/gitea/log\": mkdir ...: permission denied\n\n# after\nsudo chown -R git:git /var/lib/gitea\n# or point ROOT_PATH at a writable dir:\n[log]\nROOT_PATH = /home/git/gitea-log","handlingStrategy":"validation","validationCode":"// Pre-flight check before starting the server\nfunc checkLogDirWritable(path string) error {\n\tif err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {\n\t\treturn fmt.Errorf(\"log path %q unusable: %w\", path, err)\n\t}\n\tf, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"log path %q not writable: %w\", path, err)\n\t}\n\treturn f.Close()\n}","typeGuard":null,"tryCatchPattern":"// Startup panic: recover only in a wrapper that reports and exits non-zero\n// (e.g. systemd/docker restart policy). The panic is intentional — Gitea refuses\n// to run with an unusable log destination. Fix the filesystem, don't swallow it.","preventionTips":["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"],"tags":["config","app-ini","logging","filesystem","permissions","panic","startup"],"backgroundTag":null,"analyzedSha":"43ace7cc8ad5fa20027b1ca5b3ab5f1134972ed5","analyzedAt":"2026-08-15T09:36:00.065Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}