ErrLookup › Background articles › "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it
"open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it
File open errors — logged as open() "/path" failed, failed to open file, cannot create reader, or could not open file — mean the OS refused to open a file: it doesn't exist, the process lacks permission, the path is a directory, a symlink is broken, or the system hit its open-file limit. This guide explains what produces these errors, how to read the wrapped errno, and the fixes that apply across tools like nginx, sops, argo-workflows, and Pulumi.
Distilled from 107 documented records across 42 repositories.
Background
This family is produced at the boundary between application code and the operating system: a library or daemon calls open(), fopen(), os.Open, or File::open and the syscall returns an error. Unlike a parse error or a network failure, nothing about the request itself is wrong — the program could not get a file descriptor, so the operation aborts before any reading or writing begins. Well-built wrappers preserve the OS-level cause: nginx maps errno directly to a log level and HTTP status (EACCES → 403, ENOENT-family → 404, else 500); Go code wraps the error with %w so errors.Is(err, fs.ErrNotExist) and fs.ErrPermission still work; Rust and PHP embed the cause text after the path in the message. Reading that wrapped errno is always the first diagnostic step, because it selects the fix.
A few errnos dominate across the records. ENOENT means the path does not exist — sometimes a genuine misconfiguration, sometimes expected behavior (nginx's gzip_static probes for .gz twins that may legitimately be missing; try_files deliberately falls through to the next candidate). EACCES means the process user cannot read or traverse the path — files need the read bit, and every parent directory needs search (o+x) permission, which is why chown/chmod and SELinux/AppArmor label fixes (restorecon) recur in almost every solution set. EISDIR and ENOTDIR mean the path is (or passes through) a directory where a file was expected. EMFILE/ENFILE and ELOOP round out the family: fd exhaustion in long-running daemons like Weaviate and containerd, and symlink cycles in deployed trees.
From the caller's side the error looks different depending on where the open happens. Some libraries open files lazily, so the failure surfaces far from the code that requested it: googleworkspace/cli opens the upload file inside the streamed request body, so the error appears during reqwest .send() rather than at argument parsing; VictoriaMetrics opens snapshot part files inside parallel upload workers. Others validate first and open second, which introduces a check-then-open race: Intervention/image passes a readability pre-check but can still fail in fopen() because another process unlinked the file in between. Many tools also use open not only for reading inputs but for creating outputs — Pulumi stack export --file, delve breakpoints -save, and Hubble's flow exporter all fail this way when the destination directory is missing, unwritable, or read-only, which is why the same family covers both 'cannot read input' and 'cannot create output' failures.
The surrounding discipline is similar everywhere. Distinguish expected ENOENT (optional files, precompressed twins, tolerated absent outputs — argo-workflows explicitly treats missing output parameters as nil with a warning) from real failures (EACCES, EIO, ENOSPC), which indicate permissions, storage health, or capacity problems. Watch for races where files are deleted or permissions change between a stat/listing and the open — backup tools racing snapshot deletion, downloaders whose partial file got locked by antivirus, containerd losing a file between stat and open. And in containerized or CI environments, remember the extra failure modes: read-only root filesystems, missing volume mounts, open_basedir in PHP, umask on files generated as root, and relative paths resolving against a different working directory.
Common causes
- File does not exist (ENOENT). Wrong or misspelled path, a relative path resolved from an unexpected working directory, or a file deleted between listing and open. In some libraries (nginx gzip_static, try_files, optional output parameters) ENOENT is expected and handled by falling through or skipping.
- Permission denied (EACCES). The process user cannot read the file or search a parent directory, or SELinux/AppArmor denies access. Files need the read bit and every path component needs o+x for the daemon or worker user.
- Cannot create or write the destination. Output-opening calls (os.Create, OpenFile with O_CREATE) fail when the parent directory is missing, the target is itself a directory, the filesystem is read-only, or the disk is full (ENOSPC). Affects exporters, export/save commands, and log writers alike.
- Path is a directory, not a file (EISDIR/ENOTDIR). The configured or passed path points to a directory, or a path component does. Tools that validate existence but not file-ness still fail at the open.
- File deleted or changed mid-operation (race). The file passed an earlier stat or readability check but vanished or became unreadable before the open: temp-file cleanup jobs, snapshot deletion racing backups, antivirus or sync tools locking partial files.
- File-descriptor exhaustion (EMFILE/ENFILE). Long-running daemons that open many files hit RLIMIT_NOFILE. Raising ulimit -n or systemd LimitNOFILE (e.g. to 65536) and fixing fd leaks resolves it.
- Broken symlink or symlink cycle (ELOOP). The path is a dangling symlink, or a cycle exists in deployed directory trees. Flatten or repair the symlinks; nginx logs this at CRIT.
- Storage or environment-level failure (EIO, EROFS, open_basedir). Failing disks surface at open time; check dmesg/SMART. Read-only mounts and PHP open_basedir restrictions also block opens that would otherwise succeed.
What usually fixes it
- Read the wrapped errno first — ENOENT, EACCES, EISDIR, EMFILE, ELOOP each point to a different fix, and nearly every library in this family preserves the OS cause in the message or error chain.
- Fix ownership and permissions as the actual runtime user: files readable, parent directories searchable (o+x), and on SELinux/AppArmor hosts relabel with restorecon or adjust the profile.
- Pre-create output directories and verify writability before running: mkdir -p the destination, chown it to the daemon user, and smoke-test with a touch or cat as that user.
- Eliminate races: don't delete snapshots, temp files, or shard contents while a process may still open them; give each worker exclusive files; copy concurrently-written files to a stable location before opening.
- Raise fd limits (ulimit -n / LimitNOFILE) for daemons that open many files, and monitor fd usage before exhaustion.
- In containers and CI, check the environment-specific blockers: read-only root filesystems, missing or read-only volume mounts, open_basedir, umask on root-generated files, and absolute vs relative path resolution.
Documented occurrences
- cannot create reader for %s from %s: %w (VictoriaMetrics/VictoriaMetrics)
- %s \"%s\" failed (nginx/nginx)
- Failed to open file from path "' . $path . '" (Intervention/image)
- failed to open upload file '{}': {} (googleworkspace/cli)
- NGX_LOG_CRIT: %s \"%s\" failed (nginx/nginx)
- Error creating {} (apache/hadoop)
- %s \"%s\" failed (nginx/nginx)
- failed to open stdout: %w (argoproj/argo-workflows)
- sd_file: path=#{@path} couldn't open #{e} (fluent/fluentd)
- open source file (weaviate/weaviate)
- failed to open %s: %w (argoproj/argo-workflows)
- could not open file: %w (pulumi/pulumi)
- NGX_LOG_CRIT: <ngx_open_file_n> \"%s\" failed (nginx/nginx)
- failed to create writer: %w (cilium/cilium)
- failed to open %s file: %w (getsops/sops)
- open combined output file: %w (dagger/dagger)
- Failed to open file for resume {}: {} (Zackriya-Solutions/meetily)
- failed to open %s to check for modifications (hashicorp/nomad)
- error opening block file writer for file %s (hyperledger/fabric)
- open dependency file: %w (gastownhall/beads)
…and 87 more across the corpus — use search.
Honest provenance: generated on 2026-09-05 from AI-assisted analysis of the linked records. See how records are made.