[server]
The listener, connection limits and timeouts, request body limit, reverse proxies, request ids, render workers and skew protection of the Rust server.
[server]
host = "0.0.0.0"
port = 3000
trusted_proxies = ["10.0.0.0/8"] # your load balancer
workers = "auto"Every key is optional and a partial table keeps the defaults for the rest. TLS has its own table, [server.tls].
Reference
| Key | Default | Description |
|---|---|---|
hoststring | "0.0.0.0" | The IP address to bind: 0.0.0.0 (every interface), 127.0.0.1 (this machine only), or IPv6 in brackets ([::], [::1]). A hostname such as localhost is a startup error. See Listen address. |
portinteger | 3000 | The port to bind, 0 to 65535. GIO_PORT wins, then PORT (set by Heroku, Render, Railway, Fly.io and Cloud Run), then this key. The startup log names where the port came from. |
http2boolean | true | Serve HTTP/2 as well as HTTP/1.1. Without TLS a client must speak HTTP/2 with prior knowledge (h2c); browsers only use HTTP/2 over TLS. With [server.tls] on, false also drops h2 from the TLS handshake (ALPN), so clients settle on HTTP/1.1. |
max_body_bytesinteger | 2097152 | Largest request body, in bytes (2 MiB). A bigger one is answered 413 Payload Too Large before any handler runs. Every body is buffered in memory before the worker sees it, so raising this raises memory per request. |
max_connectionsinteger | 10000 | Concurrent TCP connections. At the cap the server stops accepting, and new clients wait in the kernel backlog until a connection closes. Upgraded WebSockets do not count here ([websocket] max_connections caps them). Keep ulimit -n above it. |
tls_handshake_timeout_secsinteger | 10 | Deadline for a TLS handshake when [server.tls] is on. A client that stalls is disconnected. |
header_read_timeout_secsinteger | 10 | Deadline for receiving a complete request head (the slowloris guard). It also bounds how long a new connection may wait for its first request and, because the timer restarts while an HTTP/1.1 connection is idle, it is the HTTP/1.1 keep-alive idle timeout. |
request_body_timeout_secsinteger | 30 | Deadline for receiving a whole request body. A client that sends it too slowly gets 408 Request Timeout. |
render_timeout_secsinteger | 30 | Deadline for the Node worker's answer: a whole buffered response, the head of a streamed one, and every gap between its chunks. Past it the request gets 504, or a streamed body ends where it is. Server-sent event streams are not bounded by it. |
idle_timeout_secsinteger | 60 | Close a connection with no request in flight for this long (gracefully, with GOAWAY, for HTTP/2). In practice it reaps HTTP/2 connections; HTTP/1.1 ones are closed by header_read_timeout_secs first. Streaming and SSE responses count as in flight. |
http2_max_concurrent_streamsinteger | 250 | Concurrent requests (streams) one HTTP/2 connection may carry. |
http2_keep_alive_interval_secsinteger | 20 | Send an HTTP/2 PING this often; a peer that does not answer within http2_keep_alive_timeout_secs is closed. |
http2_keep_alive_timeout_secsinteger | 20 | How long a PING may go unanswered. Setting either keep-alive key to 0 turns the pings off. |
trusted_proxiesstring[] | [] | Reverse proxies (IPs or CIDR blocks, IPv4 and IPv6) whose forwarding headers name the client, scheme and host. A malformed entry is a startup error. See Reverse proxies & client IPs. |
proxy_headersstring | "x-forwarded" | "x-forwarded" reads X-Forwarded-For, -Proto and -Host; "forwarded" reads the RFC 7239 Forwarded header. Exactly one family is read, and only from a trusted proxy. |
accept_request_idboolean | true | Keep a valid X-Request-Id a trusted proxy sends. Turn it off behind proxies that pass a client's own header through (AWS ALB, Google Cloud's load balancer). See Request IDs. |
skew_protectionboolean | true | Answer a client navigation or prefetch from another deployment (its x-deployment-id differs from the server's) with 409 and x-gio-action: hard-reload, so the browser loads the new version with a full page load. |
workersinteger | "auto" | 1 | Node render processes: a count from 1 to 64, or "auto" for one per CPU core, at most 8. Development always runs one. See Render workers. |
rate_limit_max_bucketsinteger | 100000 | Live [[rate_limits]] buckets kept across all rules and clients. Past it, refilled buckets are dropped first, then the least recently seen. |
Behavior
- Refusals before any handler runs. A body over
max_body_bytesgets413 Payload Too Largewithx-gio-refused: unread, which tells<GioForm>the submission never reached the app. A body that arrives too slowly gets408 Request Timeout. - Keep-alive hint. HTTP/1.1 responses carry
Keep-Alive: timeout=N, where N is the smaller ofheader_read_timeout_secsandidle_timeout_secs, so clients stop reusing a connection before the server closes it. - Long responses are never cut by the connection deadlines. Streaming SSR, SSE streams, streamed route handler bodies and WebSockets stay open as long as they run. Only
render_timeout_secsapplies to a stream, between its chunks. - Skew protection only looks at requests that carry
x-deployment-id, which only the GioJS client runtime sends (soft navigations and prefetches). A full page load never gets a409. - The client resolved through
trusted_proxiesis what rate limits, prefetch budgets, the metrics allowlist,req.ip/ctx.ipand the logs use.
Startup warnings
Loosening one of these keys logs one warn line at startup, and giojs-server --check-config (and gio doctor) report the same text under warnings:
| When | Startup warning |
|---|---|
max_body_bytes = 0 | [server] max_body_bytes = 0: request bodies have no limit of their own - each is buffered in memory up to the worker's 64 MiB message cap |
max_body_bytes above 50331648 (48 MiB) | [server] max_body_bytes = 60000000 is more than a worker message can carry: binary bodies above 48 MiB (base64-encoded to the 64 MiB message cap) still get a 413 |
max_connections = 0 | [server] max_connections = 0: concurrent connections are unlimited - a connection flood can exhaust file descriptors and memory |
trusted_proxies has a /0 | [server] trusted_proxies includes 0.0.0.0/0: any client of that family can pick its own IP, rate-limit bucket and request id - list only your proxies' addresses |
skew_protection = false | [server] skew_protection = false: a browser still running an older deployment's code keeps navigating without a reload, against pages and actions that may no longer match it |
render_timeout_secs = 0 | [server] render_timeout_secs = 0: a render that never answers holds its connection and a worker slot indefinitely |
rate_limit_max_buckets = 0 with rate limit rules | [server] rate_limit_max_buckets = 0: rate-limit buckets are never evicted - clients rotating addresses grow memory without bound |
The other timeouts can be set to 0 without a warning. Each one you lift gives slow or idle clients a way to hold connections open: keep them on when GioJS faces the internet directly.
Examples
Listen on this machine only
Behind a reverse proxy on the same host, bind the loopback address so nothing else can reach the port:
[server]
host = "127.0.0.1"
port = 3000
trusted_proxies = ["127.0.0.1", "::1"]Accept larger uploads
Raise the body limit and give slow uploads more time. Bodies are buffered in memory, so keep the limit near the largest upload you expect:
[server]
max_body_bytes = 26214400 # 25 MiB
request_body_timeout_secs = 120Behind a load balancer
Trust the balancer's address range, keep its idle timeout below the server's, and let it set request ids only if it always sets its own:
[server]
trusted_proxies = ["10.0.0.0/8"]
accept_request_id = false # the balancer passes a client's X-Request-Id through
header_read_timeout_secs = 75 # above the balancer's 60 s upstream idle timeout
idle_timeout_secs = 75Render on every core
[server]
workers = "auto" # one Node worker per core, at most 8Good to know
- The environment wins over the file for the address:
GIO_HOSToverhost,GIO_PORTthenPORToverport. A plainHOSTvariable is not read. A malformed value stops startup. workers = 0, a negative count or more than 64 stops startup (workers = 0: expected 1 to 64, or "auto").- With
max_body_bytes = 0a body is still refused with413once it cannot fit in one 64 MiB worker message. A binary body is base64-encoded on the way, so about 48 MiB is the real ceiling. - Proxies that pool upstream connections longer than
header_read_timeout_secs(nginxkeepalive, ingress-nginx and AWS ALB default to 60 seconds) can reuse a connection as GioJS closes it and answer502. Keep the proxy's idle timeout below it, or raise both timeouts as in the example above.
Not configurable
- Path canonicalization. Repeated and trailing slashes collapse and escapes of unreserved characters are decoded before any rule, rate limit or route sees the path; a path with a
.or..segment or a malformed%escape gets400. Every matcher then agrees on one spelling. - The
/_gionamespace is closed. A path under it that is not a built-in endpoint answers404from Rust and never reaches the app. - Request id validation. An incoming
X-Request-Idis only kept when it matches^[A-Za-z0-9._:-]{1,128}$, so it is always safe to log and echo. - The worker message cap (64 MiB per request) is fixed: it bounds what one request can cost the Node worker whatever
max_body_bytessays.
Related
[server.tls]- terminate TLS in GioJS[[rate_limits]]- per-client request budgets- Connection limits, Reverse proxies and Render workers on the overview
- Proxies, Sizing & Scaling - per-proxy settings
- Turning Protections On and Off
- Environment variables
Version history
| Version | Changes |
|---|---|
v0.1.0-beta.8 | host and port default to 0.0.0.0:3000 and honor GIO_HOST, GIO_PORT and PORT. Added the connection limits and timeouts, render_timeout_secs, workers, trusted_proxies, proxy_headers, accept_request_id, skew_protection and rate_limit_max_buckets. max_body_bytes = 0 means no limit of its own (it used to refuse every body). Loosened limits log a startup warning, once. With TLS, http2 = false offers only HTTP/1.1 in ALPN (it used to offer h2 too, and clients that picked it could not connect). |
v0.1.0-beta.5 | Added max_body_bytes. |
v0.1.0-beta.1 | Introduced with host, port and http2. |