# Configuration: static file serving

This page documents directives that configure static file serving, directory listings, compression, caching behavior, and custom error pages for requests resolved to the filesystem (via `root`).

> [!info]
> Static file serving is handled by the `http-static` module. For related features, see [Routing and URL processing](https://ferron.sh/docs/v3/configuration/routing/url-processing), [HTTP cache](https://ferron.sh/docs/v3/configuration/content/cache), [HTTP response control](https://ferron.sh/docs/v3/configuration/routing/response), [URL rewriting](https://ferron.sh/docs/v3/configuration/routing/rewrite), and [HTTP compression](https://ferron.sh/docs/v3/configuration/content/compression).

## Directives

### Index and directory listings

- `index <filename: string>...`
  - This directive specifies one or more filenames to try when a request path resolves to a directory. Files are tried in order; the first existing file replaces the directory path in the file context. Only applies when the resolved path is a directory and no `path_info` is present. Default: `index index.html index.htm index.xhtml`
- `directory_listing [bool: boolean]` (`http-static`)
  - This directive specifies whether auto-generated HTML directory listings are enabled when a request path resolves to a directory and no index file is found. Default: `directory_listing false`

**Configuration example:**

```ferron
example.com {
    root /srv/www/example
    index index.html index.htm
    directory_listing
}
```

> [!note]
>
> - Only generates a listing if no `index` file was found for the directory.
> - Dotfiles (names starting with `.`) are excluded from the listing, except `.maindesc` which is read as a description.
> - A `.maindesc` file in the directory, if present, is displayed as a `<pre>` block below the file table.

### Caching headers

- `etag [bool: boolean]` (`http-static`)
  - This directive specifies whether ETag generation for static file responses is enabled. ETags are weak ETags (`W/"..."`) generated from an xxHash3 hash of the file path, size, and modification time. Default: `etag true`
- `file_cache_control <value: string>` (`http-static`)
  - This directive specifies the `Cache-Control` response header for all static file responses. The value is passed through as-is. Default: not set

**Configuration example:**

```ferron
example.com {
    root /srv/www/example
    etag
    file_cache_control "public, max-age=3600"
}
```

> [!note]
>
> - When compression is used, a suffix is appended to the ETag (e.g. `W/"abc123-br"` for Brotli).
> - `If-None-Match` requests that match the current ETag return `304 Not Modified`.
> - Pre-compressed sidecar files receive their own ETag based on the sidecar file's own metadata.

### MIME types

- `mime_type <extension: string> <mime-type: string>` (`http-static`)
  - This directive maps a file extension (with or without leading dot) to a MIME type. Custom MIME type mappings override the built-in database for matching extensions. Multiple `mime_type` directives can be used to map different extensions. Default: built-in MIME database

**Configuration example:**

```ferron
example.com {
    root /srv/www/example
    mime_type ".wasm" "application/wasm"
    mime_type ".webmanifest" "application/manifest+json"
}
```

> [!note]
>
> - If the extension is not found in custom mappings, the built-in database is used as a fallback.
> - If neither custom nor built-in mappings match, the response is sent with no `Content-Type` header.

### Error pages

- `error_page <status-code: integer>... <file-path: string>`
  - This directive specifies one or more HTTP status codes followed by a file path to serve as the error response body. The last argument is always the file path; all preceding arguments are status codes. Default: built-in error pages

**Configuration example:**

```ferron
example.com {
    root /srv/www/example
    error_page 404 /custom/404.html
    error_page 500 502 503 504 /custom/50x.html
}
```

> [!note]
>
> - Only applies when an error response is being generated and no custom response has already been set.
> - The file path is absolute or relative to the current working directory.
> - If the specified error page file does not exist, the directive is skipped and the built-in error page is used.
> - Multiple status codes can be mapped to the same error page in a single directive.

## Observability

### Metrics

#### Static file serving

| Metric | Type | Attributes | Description |
|--------|------|------------|-------------|
| `ferron.static.files_served` | Counter | `ferron.compression` (`"identity"`, `"gzip"`, `"br"`, `"deflate"`, `"zstd"`), `ferron.cache_hit` (`"true"` or `"false"`) | Number of static files served |
| `ferron.static.bytes_sent` | Histogram | `ferron.compression` (`"identity"`, `"gzip"`, `"br"`, `"deflate"`, `"zstd"`), `ferron.cache_hit` (`"true"` or `"false"`) | Bytes sent for static file responses. Buckets: 1KB, 10KB, 100KB, 1MB, 10MB, 100MB |
| `ferron.static.responses` | Counter | `http.response.status.code` (HTTP response status code), `ferron.static.outcome` (static file serving outcome) | Static-file responses across normal, conditional, range, and error paths |

### Logs

- **`WARN`**: logged when an `error_page` file cannot be opened. The directive is skipped and the built-in error page is used instead.

## Best practices

The following best-practice check is reported by `ferron doctor` for directives on this page.

- **`directory_listing` enabled** — Auto-generated directory indexes expose file structure. Enable only for intentionally public file listings.