{"record":{"id":"3c7b3e1011df9b52","repo":"caddyserver/caddy","slug":"unable-to-open-destination-file-v","errorCode":null,"errorMessage":"unable to open destination file: %v","messagePattern":"unable to open destination file: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/packagesfuncs.go","lineNumber":323,"sourceCode":"}\n\nfunc getPluginPackages(modules []moduleInfo) (map[string]pluginPackage, error) {\n\tpluginPkgs := make(map[string]pluginPackage)\n\tfor _, mod := range modules {\n\t\tif mod.goModule.Replace != nil {\n\t\t\treturn nil, fmt.Errorf(\"cannot auto-upgrade when Go module has been replaced: %s => %s\",\n\t\t\t\tmod.goModule.Path, mod.goModule.Replace.Path)\n\t\t}\n\t\tpluginPkgs[mod.goModule.Path] = pluginPackage{Version: mod.goModule.Version, Path: mod.goModule.Path}\n\t}\n\treturn pluginPkgs, nil\n}\n\nfunc writeCaddyBinary(path string, body *io.ReadCloser, fileInfo os.FileInfo) error {\n\tl := caddy.Log()\n\tdestFile, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileInfo.Mode())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to open destination file: %v\", err)\n\t}\n\tdefer destFile.Close()\n\n\tl.Info(\"downloading binary\", zap.String(\"destination\", path))\n\n\t_, err = io.Copy(destFile, *body)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to download file: %v\", err)\n\t}\n\n\terr = destFile.Sync()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"syncing downloaded file to device: %v\", err)\n\t}\n\n\treturn nil\n}\n","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/cmd/packagesfuncs.go#L305-L341","documentation":"writeCaddyBinary() cannot open the destination path (the original executable's path, just vacated by the rename to .tmp) with O_RDWR|O_CREATE|O_TRUNC. At this point the old binary has already been moved aside, but the deferred restore will put it back because err is set, so the system ends up in its original state.","triggerScenarios":"Write permission on the binary's directory lost between the rename and create (unlikely) or, more typically, the file mode from os.Stat being unusable, SELinux/AppArmor denying creation at the path, a full inode table, or the path being a dangling symlink target on a read-only mount.","commonSituations":"SELinux denying unlabeled_t file creation in /usr/local/bin; container with a read-only upper layer after the rename somehow succeeded on a different mount; disk out of inodes.","solutions":["Check the restore happened: run 'caddy version' — the original binary should be back","Inspect permissions and MAC labels on the binary directory: ls -laZ $(dirname $(which caddy))","Free inodes/space if exhausted: df -i, df -h","Re-run the upgrade with sudo / from a writable location"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"DIR=\"$(dirname \"$(command -v caddy)\")\"\n[ -w \"$DIR\" ] || echo 'directory not writable — run with appropriate privileges'","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Confirm writable directory and sane SELinux/AppArmor context before upgrading","Watch df -i (inodes) on long-lived servers"],"tags":["upgrade","filesystem","permissions"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}