GioJSdocs
On this page

[server]

The listener, connection limits and timeouts, request body limit, reverse proxies, request ids, render workers and skew protection of the Rust server.

gio.toml
[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

KeyDefaultDescription
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.Env override: GIO_HOST
portinteger3000The 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.Env override: GIO_PORT, then PORT
http2booleantrueServe 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.0 / false / empty: HTTP/1.1 only
max_body_bytesinteger2097152Largest 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.0 / false / empty: No limit of its own; the 64 MiB worker message cap (about 48 MiB of binary body) is the ceiling. Warns.
max_connectionsinteger10000Concurrent 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.0 / false / empty: Unlimited. Warns.
tls_handshake_timeout_secsinteger10Deadline for a TLS handshake when [server.tls] is on. A client that stalls is disconnected.0 / false / empty: No deadline
header_read_timeout_secsinteger10Deadline 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.0 / false / empty: No deadline
request_body_timeout_secsinteger30Deadline for receiving a whole request body. A client that sends it too slowly gets 408 Request Timeout.0 / false / empty: No deadline
render_timeout_secsinteger30Deadline 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.0 / false / empty: No deadline. Warns.
idle_timeout_secsinteger60Close 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.0 / false / empty: Never closed for idleness
http2_max_concurrent_streamsinteger250Concurrent requests (streams) one HTTP/2 connection may carry.0 / false / empty: No limit advertised
http2_keep_alive_interval_secsinteger20Send an HTTP/2 PING this often; a peer that does not answer within http2_keep_alive_timeout_secs is closed.0 / false / empty: No pings
http2_keep_alive_timeout_secsinteger20How long a PING may go unanswered. Setting either keep-alive key to 0 turns the pings off.0 / false / empty: No pings
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.0 / false / empty: Empty: trust nobody. A /0 entry warns.
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_idbooleantrueKeep 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.0 / false / empty: Every id is generated here
skew_protectionbooleantrueAnswer 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.0 / false / empty: The x-deployment-id header is ignored. Warns.
workersinteger | "auto"1Node 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_bucketsinteger100000Live [[rate_limits]] buckets kept across all rules and clients. Past it, refilled buckets are dropped first, then the least recently seen.0 / false / empty: Unlimited. Warns when [[rate_limits]] rules exist.

Behavior

  • Refusals before any handler runs. A body over max_body_bytes gets 413 Payload Too Large with x-gio-refused: unread, which tells <GioForm> the submission never reached the app. A body that arrives too slowly gets 408 Request Timeout.
  • Keep-alive hint. HTTP/1.1 responses carry Keep-Alive: timeout=N, where N is the smaller of header_read_timeout_secs and idle_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_secs applies 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 a 409.
  • The client resolved through trusted_proxies is what rate limits, prefetch budgets, the metrics allowlist, req.ip / ctx.ip and 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:

WhenStartup 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:

gio.toml
[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:

gio.toml
[server]
max_body_bytes = 26214400        # 25 MiB
request_body_timeout_secs = 120

Behind 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:

gio.toml
[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 = 75

Render on every core

gio.toml
[server]
workers = "auto"                   # one Node worker per core, at most 8

Good to know

  • The environment wins over the file for the address: GIO_HOST over host, GIO_PORT then PORT over port. A plain HOST variable 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 = 0 a body is still refused with 413 once 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 (nginx keepalive, ingress-nginx and AWS ALB default to 60 seconds) can reuse a connection as GioJS closes it and answer 502. 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 gets 400. Every matcher then agrees on one spelling.
  • The /_gio namespace is closed. A path under it that is not a built-in endpoint answers 404 from Rust and never reaches the app.
  • Request id validation. An incoming X-Request-Id is 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_bytes says.

Version history

VersionChanges
v0.1.0-beta.8host 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.5Added max_body_bytes.
v0.1.0-beta.1Introduced with host, port and http2.