No Such File or Directory Errors in Go Programs Fixed
Fix no such file or directory in Go: wrong working directory, missing parent folders, stray newlines in paths, dangling symlinks and missing container files.

open config.yaml: no such file or directory is the first runtime error many Go programs print. The message is exact and unhelpful, because it never says where the program looked. The file exists on your machine, you are sure of it, and the program disagrees. The answer is almost always that the path is not the path you think it is.
Tested with Go 1.25.6, macOS on Apple Silicon (arm64)
This page lists the causes in the order they occur, with a reproduction and fix for each. It belongs to the error handling guide, has a close relative in permission denied, and uses the file functions from reading files in Go. Everything ran on Go 1.25.6.
First, ask where it looked
The message includes the path you gave. If it was relative, the process resolved it against its current working directory, so the first diagnostic is to print that directory and the resolved path:
package main
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
)
func main() {
wd, _ := os.Getwd()
fmt.Println("working directory ends in:", filepath.Base(wd))
_, err := os.ReadFile("config.yaml")
fmt.Println(err)
fmt.Println("not exist:", errors.Is(err, fs.ErrNotExist))
abs, _ := filepath.Abs("config.yaml")
fmt.Println("resolved to a path ending:", filepath.Base(filepath.Dir(abs))+"/config.yaml")
}working directory ends in: where
open config.yaml: no such file or directory
not exist: true
resolved to a path ending: where/config.yamlerrors.Is(err, fs.ErrNotExist) is the portable test for this error. It matches ENOENT on Unix and the corresponding Windows codes, through any layers of wrapping, so use it instead of string matching or the older os.IsNotExist, which does not see through fmt.Errorf("%w"). The resolved absolute path answers the actual question: is that where the file is? If not, the cause is one of the following.
Cause 1: the working directory is not what you expect
This is the most common one by far. A relative path is relative to where the process was started, not to where the source file or the binary lives.
go run .uses the directory you ran it from.go testsets the working directory to the package's directory, so a test that openstestdata/sample.jsonworks there, and the same relative path frommainrun at the repository root may not.- A compiled binary run by a service manager or cron starts in
/or the user's home, so files next to the binary are not found. - A container starts in the image's
WORKDIR, which may not contain your files unless the Dockerfile copied them.
The fixes depend on where the file belongs. For files that ship with the program, embed them with //go:embed, which puts them in the binary and removes the question:
package main
import (
_ "embed"
"fmt"
)
//go:embed greeting.txt
var greeting string
func main() {
fmt.Print(greeting)
}Hello from an embedded fileHello from an embedded fileThe file is read at build time and compiled in, so it cannot be missing at run time. For files that the user or operator supplies, take an absolute path from a flag or an environment variable (environment variables), and print it in the error. For paths relative to the executable, resolve them from os.Executable() rather than assuming the working directory.
Cause 2: the parent directory does not exist
os.Create, os.OpenFile with O_CREATE and os.WriteFile create the file, never its directories. Writing logs/app.log into a missing logs directory fails with the same message, which is confusing because you are creating, not opening:
package main
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
)
func main() {
dir, _ := os.MkdirTemp("", "demo")
defer os.RemoveAll(dir)
os.Chdir(dir)
err := os.WriteFile("logs/app.log", []byte("x"), 0o644)
fmt.Println("without MkdirAll:", errors.Is(err, fs.ErrNotExist))
if err := os.MkdirAll(filepath.Dir("logs/app.log"), 0o755); err != nil {
panic(err)
}
err = os.WriteFile("logs/app.log", []byte("x"), 0o644)
fmt.Println("with MkdirAll: ", err)
}without MkdirAll: true
with MkdirAll: <nil>os.MkdirAll creates every missing directory in the path and succeeds if they already exist, so it is safe to call unconditionally before writing. Mind the permission bits (0o755 for directories) and the process umask. If the directory is created by another process, a race between the check and the write can still fail; creating the directory and writing in the same function, as above, is the pattern.
Cause 3: whitespace or a newline in the path
A path read from standard input, a file or an environment variable often carries a trailing newline, and that character becomes part of the file name you are looking for:
package main
import (
"errors"
"fmt"
"io/fs"
"os"
"strings"
)
func main() {
dir, _ := os.MkdirTemp("", "demo")
defer os.RemoveAll(dir)
os.Chdir(dir)
os.WriteFile("data.txt", []byte("ok"), 0o644)
typed := "data.txt\n" // what bufio.Reader.ReadString('\n') returns
_, err := os.ReadFile(typed)
fmt.Println("raw: ", errors.Is(err, fs.ErrNotExist))
_, err = os.ReadFile(strings.TrimSpace(typed))
fmt.Println("trimmed:", err)
}raw: true
trimmed: <nil>The raw path fails and the trimmed one works. Trim input at the point you read it (reading user input shows bufio.Scanner, which strips the newline for you). When you print a path in an error, use %q so a newline shows up escaped in the message and not as an unexplained line break.
Cause 4: a dangling symlink
If the path is a symbolic link whose target does not exist, opening it reports no such file or directory even though the link itself is there, and ls shows it:
package main
import (
"errors"
"fmt"
"io/fs"
"os"
)
func main() {
dir, _ := os.MkdirTemp("", "demo")
defer os.RemoveAll(dir)
os.Chdir(dir)
os.Symlink("missing-target.txt", "link.txt")
_, err := os.Open("link.txt")
fmt.Println("open: ", errors.Is(err, fs.ErrNotExist))
info, err := os.Lstat("link.txt")
fmt.Println("lstat: ", err, info.Mode()&fs.ModeSymlink != 0)
}open: true
lstat: <nil> trueos.Open follows the link and finds nothing at the end. os.Lstat looks at the link itself and succeeds, with the symlink mode bit set. If an error message names a path you can see in a listing, check with Lstat or ls -l for a dangling link; deployment tooling that creates links before the files they point to is a frequent source.
Cause 5: it is not the file that is missing
The same message appears when a program tries to run a binary and something it needs is missing. On Linux, executing a dynamically linked binary whose interpreter or shared library does not exist reports no such file or directory for the executable itself, which is why a Go binary built with cgo and copied into a minimal container (scratch, or Alpine without glibc) fails with that error even though the file is plainly present. The remedy is to build statically with CGO_ENABLED=0 or match the libc in the image (what ships inside every Go binary).
When starting a subprocess, a missing program gives a different error: exec: "tool": executable file not found in $PATH. That one is about the search path, not the file system.
A checklist
- Print the full path with
%q, andos.Getwd()once. - Is it relative? Run from the directory you think, or switch to an absolute path or an embed.
- Creating a file? Call
os.MkdirAllon its directory first. - Does the path come from input? Trim it.
- Is it a symlink? Check with
Lstat. - In a container? List the directory inside it, and check the
WORKDIRand the copied files. - A binary that "exists" but won't run? Check its linking with
fileandldd.
Detect the condition with errors.Is(err, fs.ErrNotExist), and decide whether it is an error at all: for an optional config file, a missing file is the normal case, and the right code uses defaults instead of failing. The io/fs package documents the portable error values, and the os.MkdirAll reference covers the directory creation.


