diff --git a/.vitepress/config.v3.mts b/.vitepress/config.v3.mts index 5a32120..9a04f26 100644 --- a/.vitepress/config.v3.mts +++ b/.vitepress/config.v3.mts @@ -51,6 +51,8 @@ export const v3: DefaultTheme.Config = { { text: 'Docker', link: '/v3/features/docker' }, { text: 'Graceful Shutdown', link: '/v3/features/graceful-shutdown' }, { text: 'Idle Connection Timeout', link: '/v3/features/idle-timeout' }, + { text: 'Maximum Connections', link: '/v3/features/max-connections' }, + { text: 'Maximum Request Header Size', link: '/v3/features/max-header-size' }, { text: 'File Descriptor Socket Passing', link: '/v3/features/file-descriptor-socket-passing' }, { text: 'Unix Domain Socket', link: '/v3/features/unix-domain-socket' }, { text: 'Worker Threads Customization', link: '/v3/features/worker-threads' }, diff --git a/src/v3/configuration/cli.md b/src/v3/configuration/cli.md index 4c66f58..5dfcfac 100644 --- a/src/v3/configuration/cli.md +++ b/src/v3/configuration/cli.md @@ -89,7 +89,7 @@ Options: --directory-listing-download= Specify list of enabled format(s) for directory download. Format supported: `targz`. Default to empty list (disabled) [env: SERVER_DIRECTORY_LISTING_DOWNLOAD=] [possible values: targz] --security-headers [] - Enable security headers by default when TLS feature is activated. Headers included: "Strict-Transport-Security: max-age=63072000; includeSubDomains; preload" (2 years max-age), "X-Frame-Options: DENY" and "Content-Security-Policy: frame-ancestors 'self'" [env: SERVER_SECURITY_HEADERS=] [default: true] [possible values: true, false] + Enable security headers. Enabled by default when the TLS feature is activated. Headers included: "X-Frame-Options: DENY", "X-Content-Type-Options: nosniff", "Content-Security-Policy: frame-ancestors 'self'" and "Referrer-Policy: strict-origin-when-cross-origin". With `--tls`, also "Strict-Transport-Security: max-age=63072000; includeSubDomains" (2 years max-age). Behind a TLS-terminating proxy, add `Strict-Transport-Security` at the proxy or with an `[[advanced.headers]]` rule [env: SERVER_SECURITY_HEADERS=] [default: true] [possible values: true, false] -e, --cache-control-headers [] Enable cache control headers for incoming requests based on a set of file types. The file type list can be found on `src/control_headers.rs` file [env: SERVER_CACHE_CONTROL_HEADERS=] [default: true] [possible values: true, false] --etag [] @@ -100,6 +100,10 @@ Options: Defines a grace period in seconds after a `SIGTERM` signal is caught which will delay the server before to shut it down gracefully. The maximum value is 255 seconds [env: SERVER_GRACE_PERIOD=] [default: 0] --idle-timeout Defines the time in seconds after which a connection without any reads or writes is closed. An HTTP/1 request header must also arrive within this time. Use `0` to disable it. The maximum value is 65535 seconds [env: SERVER_IDLE_TIMEOUT=] [default: 30] + --max-header-size + Defines the maximum size in bytes of the request headers. For HTTP/1 it includes the request line, for HTTP/2 it applies to the header list. Larger requests get `431 Request Header Fields Too Large`. The value must be greater than `0` [env: SERVER_MAX_HEADER_SIZE=] [default: 16384] + --max-connections + Defines the maximum number of connections served at the same time, across all listeners. Once reached, new connections wait until another one closes. Use `0` for no limit [env: SERVER_MAX_CONNECTIONS=] [default: 0] -w, --config-file Server TOML configuration file path [env: SERVER_CONFIG_FILE=] [default: ./sws.toml] --access-log-format diff --git a/src/v3/configuration/env.md b/src/v3/configuration/env.md index d71e5bb..06c1fcb 100644 --- a/src/v3/configuration/env.md +++ b/src/v3/configuration/env.md @@ -149,7 +149,7 @@ Specify list of enabled format(s) for directory download. Format supported: `tar ## SERVER_SECURITY_HEADERS -Enable security headers by default when TLS feature is activated. Headers included: `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload` (2 years max-age), `X-Frame-Options: DENY` and `Content-Security-Policy: frame-ancestors 'self'`. Default `true` (enabled). +Enable security headers. Headers included: `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Content-Security-Policy: frame-ancestors 'self'` and `Referrer-Policy: strict-origin-when-cross-origin`. With `SERVER_TLS`, also `Strict-Transport-Security: max-age=63072000; includeSubDomains` (2 years max-age). See [Security Headers](../features/security-headers.md). Default `true` (enabled) when the TLS feature is activated. ## SERVER_CACHE_CONTROL_HEADERS @@ -171,6 +171,14 @@ Defines a grace period in seconds after a `SIGTERM` signal is caught which will Defines the time in seconds after which a connection without any reads or writes is closed. An HTTP/1 request header must also arrive within this time. Use `0` to disable it. The maximum value is `65535` seconds. See [Idle Connection Timeout](../features/idle-timeout.md). Default `30`. +## SERVER_MAX_HEADER_SIZE + +Defines the maximum size in bytes of the request headers. For HTTP/1 it includes the request line, for HTTP/2 it applies to the header list. Larger requests get `431 Request Header Fields Too Large`. The value must be greater than `0`. See [Maximum Request Header Size](../features/max-header-size.md). Default `16384`. + +## SERVER_MAX_CONNECTIONS + +Defines the maximum number of connections served at the same time, across all listeners. Once reached, new connections wait until another one closes. Use `0` for no limit. See [Maximum Connections](../features/max-connections.md). Default `0`. + ## SERVER_CONFIG_FILE Server TOML configuration file path. See [The TOML Configuration File](../configuration/file). Default `./sws.toml`. diff --git a/src/v3/configuration/file.md b/src/v3/configuration/file.md index d8dea9b..c8233fe 100644 --- a/src/v3/configuration/file.md +++ b/src/v3/configuration/file.md @@ -80,6 +80,12 @@ grace-period = 0 #### Connection idle timeout idle-timeout = 30 +#### Maximum request header size in bytes +max-header-size = 16384 + +#### Maximum connections served at the same time (0 means no limit) +max-connections = 0 + #### Page fallback for 404s # page-fallback = "" diff --git a/src/v3/defaults.md b/src/v3/defaults.md index aef3c19..eb64b06 100644 --- a/src/v3/defaults.md +++ b/src/v3/defaults.md @@ -56,17 +56,17 @@ When running `static-web-server` with no flags, you get: SWS applies a **layered defence model** on every request: containment -> symlink policy -> hidden file policy. -| Setting | Default | Rationale | -| -------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Follow symlinks | disabled | Symlinks can escape the webroot. SWS rejects them with `403 Forbidden` and walks every path component with `lstat(2)` to detect them. | -| Include hidden files | disabled | Dotfiles (`.env`, `.git`, etc.) return `404 Not Found`, never accidentally served. | -| Containment check | always on | Verifies every resolved path stays inside the canonical webroot. Uses a per-thread cache (1024 entries, 60s TTL) to keep it fast. | -| TLS (HTTPS) | disabled | Requires explicit setup: certificate + private key paths. | -| HTTP/2 | disabled | Requires TLS first. | -| HTTPS redirect | disabled | Requires TLS. When enabled, redirects HTTP to HTTPS. | -| Security headers | enabled (with TLS) | When TLS is active, every response includes HSTS (2 years), `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Content-Security-Policy: frame-ancestors 'self'`, and `Referrer-Policy: strict-origin-when-cross-origin`. | -| Basic authentication | disabled | Opt-in; passwords must be BCrypt-hashed. | -| CORS | disabled | No `Access-Control-*` headers unless origins are explicitly configured. | +| Setting | Default | Rationale | +| -------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Follow symlinks | disabled | Symlinks can escape the webroot. SWS rejects them with `403 Forbidden` and walks every path component with `lstat(2)` to detect them. | +| Include hidden files | disabled | Dotfiles (`.env`, `.git`, etc.) return `404 Not Found`, never accidentally served. | +| Containment check | always on | Verifies every resolved path stays inside the canonical webroot. Uses a per-thread cache (1024 entries, 60s TTL) to keep it fast. | +| TLS (HTTPS) | disabled | Requires explicit setup: certificate + private key paths. | +| HTTP/2 | disabled | Requires TLS first. | +| HTTPS redirect | disabled | Requires TLS. When enabled, redirects HTTP to HTTPS. | +| Security headers | enabled | Every response includes `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Content-Security-Policy: frame-ancestors 'self'`, and `Referrer-Policy: strict-origin-when-cross-origin`. With TLS, also HSTS (2 years). | +| Basic authentication | disabled | Opt-in; passwords must be BCrypt-hashed. | +| CORS | disabled | No `Access-Control-*` headers unless origins are explicitly configured. | ## HTTP headers (automatic) @@ -110,4 +110,6 @@ The types considered compressible include all `text/*` MIME types, plus `applica | Maintenance mode | disabled | Opt-in for planned downtime. Returns `503` with a custom page. | | Grace period | `0s` (no delay) | SIGTERM triggers an immediate graceful shutdown. Set a delay (max 255s) if your load balancer needs time to drain. | | Idle timeout | `30s` | Connections with no traffic are closed, so abandoned ones don't pile up and use up file descriptors. | +| Max header size | `16384` bytes | Larger request headers get `431`, so a client can't make SWS buffer large headers on each connection. | +| Max connections | `0` (no limit) | Opt-in. The right value depends on the file descriptor limit and the expected traffic. | | Config file | `./sws.toml` | Auto-loaded if present. TOML format for settings that need more structure than CLI flags. | diff --git a/src/v3/features/idle-timeout.md b/src/v3/features/idle-timeout.md index 646a205..e0a9cf5 100644 --- a/src/v3/features/idle-timeout.md +++ b/src/v3/features/idle-timeout.md @@ -32,6 +32,7 @@ static-web-server -d ./public/ --idle-timeout 60 - A download stays open for as long as data keeps moving, however long it takes. If the client reads nothing, or reads so slowly that nothing can be sent for the whole timeout, the connection is closed. - If SWS can't start or continue a response within the timeout (for example, on very slow storage), the connection is closed too. - Over TLS, only decrypted bytes count, so the TLS handshake must also finish within the timeout. +- With `--log-level debug`, SWS logs each connection it closes this way, with the client address. > [!TIP] Reverse proxies > @@ -39,4 +40,4 @@ static-web-server -d ./public/ --idle-timeout 60 > [!WARNING] Limitations > -> A client can still keep a connection open indefinitely by making some progress within every timeout period, for example by sending a new request or reading part of a response. SWS doesn't limit the number of connections, so use a firewall or a reverse proxy if you need to limit connections per client. +> A client can still keep a connection open indefinitely by making some progress within every timeout period, for example by sending a new request or reading part of a response. Use [`--max-connections`](./max-connections.md) to cap the number of open connections, and a firewall or a reverse proxy if you need to limit connections per client. diff --git a/src/v3/features/max-connections.md b/src/v3/features/max-connections.md new file mode 100644 index 0000000..cfef232 --- /dev/null +++ b/src/v3/features/max-connections.md @@ -0,0 +1,45 @@ +# Maximum Connections + +**SWS** can limit the number of connections served at the same time. + +Each open connection uses a file descriptor and some memory. The [idle timeout](./idle-timeout.md) closes connections that stall, but a client that keeps sending or reading a little data can hold connections open for as long as it wants. A connection limit caps what such clients, or a sudden burst of traffic, can use. + +The limit is disabled by default and can be set with the numeric `--max-connections` option or its equivalent [SERVER_MAX_CONNECTIONS](./../configuration/env.md#server-max-connections) env. + +> [!TIP] Tip +> +> The default is `0`, which means no limit. + +Here is an example of serving at most `1000` connections at the same time. + +```sh +static-web-server -d ./public/ --max-connections 1000 +``` + +Or in the [configuration file](./../configuration/file.md): + +```toml +[general] +max-connections = 1000 +``` + +## Behavior + +- The limit counts every open connection: HTTP/1, HTTP/2, TLS, the [HTTP to HTTPS redirect](./http-https-redirect.md) server and [Unix domain sockets](./unix-domain-socket.md) share it. +- Once the limit is reached, new connections are not rejected. They wait until another connection closes, then they are served. Clients see a connection that opens but doesn't answer yet. +- Connections that keep waiting queue up in the operating system's listen backlog. When the backlog is full, the operating system refuses or drops new connection attempts. +- Each listener accepts one connection beyond the limit and holds it until a slot frees up. With the HTTPS redirect server on, SWS can have up to two connections more than the limit open. +- Idle keep-alive connections keep their slot until the client closes them or the [idle timeout](./idle-timeout.md) does. Over HTTP/1, browsers open up to 6 connections per site, so a low limit can make other clients wait for up to the idle timeout. +- When the limit is reached, SWS logs a warning, at most once a minute. +- On shutdown, connections still waiting for a slot are closed without a response. Connections being served are drained as described in [Graceful Shutdown](./graceful-shutdown.md). +- Requests to the [health endpoints](./health-endpoint.md) (`/livez`, `/readyz`) also wait for a slot, so a liveness probe can time out while the limit is reached. + +> [!WARNING] Idle timeout +> +> With `--idle-timeout 0`, a connection that sends nothing keeps its slot until the client closes it, so a few such connections can block the server. SWS logs a warning at startup for this combination. Keep the idle timeout enabled when using a connection limit. + +> [!TIP] Choosing a value +> +> When SWS is exposed directly to clients, `1024` is a good starting point. Behind a reverse proxy, the proxy opens far fewer connections and its own limits usually apply first. +> +> Keep the limit below the process file descriptor limit (`ulimit -n`), which also has to cover open files being served. Since a limit is reached by any client that holds connections open, it protects the server's resources but not the availability of the site: use a firewall or a reverse proxy if you need to limit connections per client. diff --git a/src/v3/features/max-header-size.md b/src/v3/features/max-header-size.md new file mode 100644 index 0000000..1ed459d --- /dev/null +++ b/src/v3/features/max-header-size.md @@ -0,0 +1,34 @@ +# Maximum Request Header Size + +**SWS** limits the size of the request headers. Requests with larger headers get `431 Request Header Fields Too Large`. + +SWS keeps the headers of a request in memory until they have fully arrived, so the limit also caps how much data a client can make SWS hold on each connection. + +The limit is enabled by default and can be changed with the numeric `--max-header-size` option or its equivalent [SERVER_MAX_HEADER_SIZE](./../configuration/env.md#server-max-header-size) env. + +> [!TIP] Tip +> +> The value is in bytes. The default is `16384` (16 KiB) and the value must be greater than `0`. + +Here is an example of allowing request headers of up to `32768` bytes (32 KiB). + +```sh +static-web-server -d ./public/ --max-header-size 32768 +``` + +Or in the [configuration file](./../configuration/file.md): + +```toml +[general] +max-header-size = 32768 +``` + +## Behavior + +- The limit applies to every connection: HTTP/1, HTTP/2, TLS, the [HTTP to HTTPS redirect](./http-https-redirect.md) server and [Unix domain sockets](./unix-domain-socket.md). +- On HTTP/1, the limit covers the request line (method, path and query string) and all headers. After the `431` response, SWS closes the connection. +- On HTTP/2, the limit is the maximum header list size SWS announces to clients. HTTP/2 counts each header as its name and value plus 32 bytes, so the same requests come out slightly larger than on HTTP/1. + +> [!TIP] Choosing a value +> +> Most requests carry well under 16 KiB of headers. Raise the limit if clients send large cookies or tokens and get `431` responses. diff --git a/src/v3/features/security-headers.md b/src/v3/features/security-headers.md index 945bcbc..75f282c 100644 --- a/src/v3/features/security-headers.md +++ b/src/v3/features/security-headers.md @@ -6,9 +6,7 @@ outline: deep **`SWS`** provides several [security headers](https://web.dev/security-headers/) support. -When the [TLS](../features/tls.md) feature is activated _security headers_ are enabled automatically. - -This feature is enabled by default on HTTP/1 + HTTP/2 and can be controlled by the boolean `--security-headers` option or the equivalent [SERVER_SECURITY_HEADERS](./../configuration/env#server_security_headers) env. +The feature is enabled by default when SWS is built with the [TLS](../features/tls.md) feature, as all release binaries are, and can be controlled by the boolean `--security-headers` option or the equivalent [SERVER_SECURITY_HEADERS](./../configuration/env#server_security_headers) env. > [!TIP] Customize HTTP headers > @@ -16,12 +14,37 @@ This feature is enabled by default on HTTP/1 + HTTP/2 and can be controlled by t ## Headers included -The following headers are included by default. +The following headers are included on every response. ```http -Strict-Transport-Security: max-age=63072000; includeSubDomains; preload X-Frame-Options: DENY X-Content-Type-Options: nosniff Content-Security-Policy: frame-ancestors 'self' Referrer-Policy: strict-origin-when-cross-origin ``` + +When SWS serves [TLS](../features/tls.md) (`--tls`), the responses also include: + +```http +Strict-Transport-Security: max-age=63072000; includeSubDomains +``` + +## HSTS + +`Strict-Transport-Security` (HSTS) tells browsers to use only HTTPS for the host and its subdomains for two years. SWS doesn't send it over plain HTTP, since [RFC 6797](https://datatracker.ietf.org/doc/html/rfc6797#section-7.2) forbids it there. + +If SWS serves plain HTTP behind a proxy that terminates TLS, add HSTS at the proxy, or with a [custom header](custom-http-headers.md) rule. + +The default value doesn't include `preload`, since adding a domain to the browser preload lists is up to its owner. To opt in, set the header with a custom header rule: + +```toml +[advanced] + +[[advanced.headers]] +source = "**" +headers.Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload" +``` + +> [!WARNING] Custom header rules apply to every response +> +> Unlike the built-in header, a custom header rule also applies over plain HTTP. Only add HSTS this way when every client reaches SWS over HTTPS. diff --git a/src/v3/index.md b/src/v3/index.md index 62d5ca6..f3672c8 100644 --- a/src/v3/index.md +++ b/src/v3/index.md @@ -52,6 +52,8 @@ SWS is designed to be **simple by default, powerful when needed**. You can point | [**Maintenance Mode**](./features/maintenance-mode) | Display a custom maintenance page while you deploy updates. | | [**Graceful Shutdown**](./features/graceful-shutdown) | Drain in-flight connections before stopping, zero dropped requests. | | [**Idle Connection Timeout**](./features/idle-timeout) | Close idle or stalled connections after a configurable timeout. | +| [**Maximum Connections**](./features/max-connections) | Limit the number of connections served at the same time. | +| [**Maximum Request Header Size**](./features/max-header-size) | Reject requests whose headers are larger than a configurable limit. | | [**Windows Service**](./features/windows-service) | Install SWS as a native Windows Service with `sc` or the installer. | | [**Docker**](./features/docker) | Official images on `scratch`, `Alpine`, and `Debian` for several architectures. | | [**Unix Domain Socket**](./features/unix-domain-socket) | Listen on a UDS path for secure, low-latency local proxying. |