valyala/fasthttp · error
directory with unexpected suffix found: %q: suffix: %q
Error message
directory with unexpected suffix found: %q: suffix: %q
What it means
The FS handler opened a path that turned out to be a directory even though its name carries the compressed-file suffix (e.g. .gz/.br). fasthttp refuses to serve a directory as a compressed file because suffix and content contradict. Depending on mustCompress it returns this error, or errDirIndexRequired to fall back to directory index handling.
Source
Thrown at fs.go:1905
// If the file is not found and the path is empty, let's return errDirIndexRequired error.
if filePath == "" && (errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrInvalid)) {
return nil, errDirIndexRequired
}
return nil, err
}
fileInfo, err := f.Stat()
if err != nil {
_ = f.Close()
return nil, fmt.Errorf("cannot obtain info for file %q: %w", filePath, err)
}
if fileInfo.IsDir() {
_ = f.Close()
if mustCompress {
return nil, fmt.Errorf("directory with unexpected suffix found: %q: suffix: %q",
filePath, h.compressedFileSuffixes[fileEncoding])
}
return nil, errDirIndexRequired
}
if mustCompress {
fileInfoOriginal, err := fs.Stat(h.filesystem, filePathOriginal)
if err != nil {
_ = f.Close()
return nil, fmt.Errorf("cannot obtain info for original file %q: %w", filePathOriginal, err)
}
// Only re-create the compressed file if there was more than a second between the mod times.
// On macOS the gzip seems to truncate the nanoseconds in the mod time causing the original file
// to look newer than the gzipped file.
if fileInfoOriginal.ModTime().Sub(fileInfo.ModTime()) >= time.Second {
// The compressed file became stale. Re-create it.
_ = f.Close()View on GitHub (pinned to c96f600972)
Solutions
- Remove or rename the directory that carries a compressed suffix (e.g. 'app.js.gz')
- Rebuild static assets so .gz entries are real files, not directories
- Adjust Root/PathRewrite so the resolved path points at a file
- Disable compression options if you do not intend to serve pre-compressed files
Example fix
// before $ mkdir dist/bundle.js.gz // after $ gzip -k dist/bundle.js # creates a real file dist/bundle.js.gz
Defensive patterns
Strategy: validation
Validate before calling
if fi, err := os.Stat(path); err == nil && fi.IsDir() { return 404 } // also ensure no dir ends in .gz/.br Try / catch
if err := serveFS(ctx); err != nil { if strings.Contains(err.Error(), "unexpected suffix") { /* fix path or 404 */ } } Prevention
- Never name directories with .gz/.br suffixes
- Verify build output with file(1) — compressed entries must be regular files
- Test static file serving after build changes
When it happens
Trigger: Request path resolves to a directory whose name ends in h.compressedFileSuffixes[fileEncoding] while mustCompress is true — e.g. a literal directory named 'data.gz' exists and FS is configured with Compress/CompressBrotli.
Common situations: Accidentally creating a directory named like a gzip asset (mkdir out/app.js.gz instead of gzip); build scripts leaving compressed-named dirs; path rewrite rules mapping routes to directories with such names.
Related errors
- cannot create temporary file for %q: %w
- error when compressing file %q to %q: %w
- cannot change modification time to %v for tmp file %q: %v
- cannot move compressed file from %q to %q: %w
- cannot obtain info for original file %q: %w
AI-assisted analysis of valyala/fasthttp@c96f600972 (2026-08-31).
Data as JSON: /api/errors/caac24cce1cb0618.
Report an issue: GitHub.