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

  1. Verify config.Root exists relative to the process working directory (the default filesystem is os.DirFS('.')).
  2. Use an absolute path or run the binary from the directory containing the assets.
  3. Prefer embedding assets with embed.FS + StaticConfig.Filesystem for predictable, portable paths.
  4. 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

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


AI-assisted analysis of labstack/echo@05489dc173 (2026-08-04). Data as JSON: /data/errors/b0638c61ed4c9095.json. Report an issue: GitHub.