caddyserver/caddy · error

cannot read etag from file %s: %v

Error message

cannot read etag from file %s: %v

What it means

Returned by FileServer.getEtagFromFile while looking for a sidecar etag file: for a configured etag_file_extensions suffix, the file exists (stat succeeded, so fs.ErrNotExist did not apply) but reading it failed with another error. Typical causes are permission denial or an I/O error on the sidecar file.

Source

Thrown at modules/caddyhttp/fileserver/staticfiles.go:786

	var sb strings.Builder
	sb.WriteRune('"')
	sb.WriteString(strconv.FormatInt(mtime.UnixNano(), 36))
	sb.WriteRune('-')
	sb.WriteString(strconv.FormatInt(d.Size(), 36))
	sb.WriteRune('"')
	return sb.String()
}

// Finds the first corresponding etag file for a given file in the file system and return its content
func (fsrv *FileServer) getEtagFromFile(fileSystem fs.FS, filename string) (string, error) {
	for _, suffix := range fsrv.EtagFileExtensions {
		etagFilename := filename + suffix
		etag, err := fs.ReadFile(fileSystem, etagFilename)
		if errors.Is(err, fs.ErrNotExist) {
			continue
		}
		if err != nil {
			return "", fmt.Errorf("cannot read etag from file %s: %v", etagFilename, err)
		}

		// Etags should not contain newline characters
		etag = bytes.ReplaceAll(etag, []byte("\n"), []byte{})

		return string(etag), nil
	}
	return "", nil
}

// redirect performs a redirect to a given path. The 'toPath' parameter
// MUST be solely a path, and MUST NOT include a query.
func redirect(w http.ResponseWriter, r *http.Request, toPath string) error {
	for strings.HasPrefix(toPath, "//") {
		// prevent path-based open redirects
		toPath = strings.TrimPrefix(toPath, "/")
	}
	// preserve the query string if present

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Fix permissions on the sidecar files so the Caddy process can read them (chown/chmod, or align the container user).
  2. Regenerate the .etag files with the same tooling/user that serves the content.
  3. If the read failure is transient (NFS), address the underlying storage issue; the error surfaces per request.
  4. As a stopgap, remove the offending suffix from etag_file_extensions (normal filesystem etag behavior applies).

Example fix

# before: sidecar unreadable by Caddy user
-rw------- root root app.js.etag
# after
chmod 644 app.js.etag  # or chown to the caddy runtime user
Defensive patterns

Strategy: fallback

Validate before calling

# Deploy-time check: every .etag sidecar must be readable by the Caddy user
runuser -u caddy -- find "$WEBROOT" -name '*.etag' ! -readable -print | grep . && { echo 'unreadable etag files'; exit 1; }

Prevention

When it happens

Trigger: Configuring etag_file_extensions (e.g. [".etag"]) and a request hits a file whose companion .etag file exists but is unreadable — wrong ownership/permissions (0600 root file with non-root worker), or transient disk/NFS errors.

Common situations: Deploying pre-built artifacts where .etag files were created by a different user; containerized Caddy reading a mounted volume with restrictive modes; network filesystems returning errors other than not-exist.

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/af139fb78f431feb. Report an issue: GitHub.