labstack/echo · error
static middleware failed to create sub-filesystem: %w
Error message
static middleware failed to create sub-filesystem: %w
What it means
Returned at request time (static.go:249, inside the middleware closure guarded by sync.Once) when fs.Sub(c.Echo().Filesystem, config.Root) fails for the DEFAULT filesystem path (config.Filesystem was nil). Because no custom filesystem was provided, Echo lazily creates the sub-filesystem on the first request. The once.Do ensures it only attempts once; on failure every subsequent request returns fsErr.
Source
Thrown at middleware/static.go:249
// 2. path.Clean() provides platform-independent behavior for URL paths
// 3. The "/" prefix forces absolute path interpretation, removing ".." components
// 4. Backslashes are treated as literal characters (not path separators), preventing traversal
// See static_windows.go for Go 1.20+ filepath.Clean compatibility notes
filePath := path.Clean("./" + p)
if config.IgnoreBase {
routePath := path.Base(strings.TrimRight(c.Path(), "/*"))
baseURLPath := path.Base(p)
if baseURLPath == routePath {
i := strings.LastIndex(filePath, routePath)
filePath = filePath[:i] + strings.Replace(filePath[i:], routePath, "", 1)
}
}
if once != nil {
once.Do(func() {
if tmp, tmpErr := fs.Sub(c.Echo().Filesystem, config.Root); tmpErr != nil {
fsErr = fmt.Errorf("static middleware failed to create sub-filesystem: %w", tmpErr)
} else {
currentFS = tmp
}
})
if fsErr != nil {
return fsErr
}
}
file, err := currentFS.Open(filePath)
if err != nil {
if !isIgnorableOpenFileError(err) {
return err
}
// file with that path did not exist, so we continue down in middleware/handler chain, hoping that we end up in
// handler that is meant to handle this request
err = next(c)
if err == nil {View on GitHub (pinned to 05489dc173)
Solutions
- Verify config.Root exists relative to the process working directory (the default filesystem is os.DirFS('.')).
- Use an absolute path or run the binary from the directory containing the assets.
- Prefer embedding assets with embed.FS + StaticConfig.Filesystem for predictable, portable paths.
- Test the path with os.Stat or fs.Sub(e.Filesystem, root) during setup.
Example fix
// before
e.Use(middleware.StaticWithConfig(middleware.StaticConfig{Root: "./public"})) // dir missing at runtime
// after: embed for portability
//go:embed public
var publicFS embed.FS
sub, _ := fs.Sub(publicFS, "public")
e.Use(middleware.StaticWithConfig(middleware.StaticConfig{Filesystem: sub, Root: "."})) Defensive patterns
Strategy: validation
Validate before calling
// For the default OS filesystem, verify Root resolves before serving.
func rootExists(root string) error {
info, err := os.Stat(root)
if err != nil { return err }
if !info.IsDir() { return fmt.Errorf("%s is not a directory", root) }
return nil
} Prevention
- Prefer embed.FS for portable, immutable assets.
- Run the binary from a known working directory or use absolute paths for Root.
- Call echo.MustSubFS or fs.Sub at startup to fail fast rather than on first request.
When it happens
Trigger: Using Static middleware (or StaticWithConfig) with the default OS filesystem and a Root directory that does not exist relative to the working directory. The error surfaces on the first request that hits the static handler.
Common situations: Root points to a directory that isn't present at runtime (typo, wrong relative path, deployed without assets); running the binary from a different working directory than expected; Root cleaned to a path the OS fs.FS rejects.
Related errors
- file does not implement io.ReadSeeker
- static middleware failed to create sub-filesystem from confi
- static middleware failed to read directory for listing: %w
- static middleware failed to get file info for listing: %w
- echo static middleware directory list template parsing error
AI-assisted analysis of labstack/echo@05489dc173 (2026-08-04).
Data as JSON: /data/errors/b0638c61ed4c9095.json.
Report an issue: GitHub.