Resource limits

View Source

Roadrunner bounds how much memory and CPU a single connection or peer can pull in before any handler runs. The goal is that no client, by sending oversized, malformed, or slow input, can grow a connection's memory without bound or pin a worker. Every limit on this page is on by default.

This page is an operator reference. For the reporting policy, see SECURITY.md. For the exact option types, see roadrunner_listener:opts/0.

HTTP request limits

LimitDefaultConfigurableOver-limit behavior
Request line8 KBno (fixed)400, connection closed
Header line8 KBno (fixed)400, connection closed
Header block (cumulative)10 KBno (fixed)400, connection closed
Header count100no (fixed)400, connection closed
Body (max_content_length)10 MByes413 Payload Too Large

The body cap is enforced the same way for both Content-Length and Transfer-Encoding: chunked requests: the cap is checked as the body is read, so an oversized chunked body is rejected without buffering the whole thing.

WebSocket limits

A WebSocket message is the WS analog of a request body, so its caps default to the same value as max_content_length. Both are enforced before the payload reaches the handler, and crossing either closes the connection with RFC 6455 code 1009 (message too big).

These are configured under the ws listener option as a nested map, e.g. ws => #{max_frame_size => N, max_message_size => N}.

LimitDefaultWhat it bounds
ws.max_frame_size10 MBone frame's declared payload, checked on the frame header before the body is buffered
ws.max_message_size10 MBa reassembled message: the running total across fragments, and the decompressed size when permessage-deflate is negotiated

Notes:

  • max_message_size must be >= max_frame_size; a listener configured otherwise refuses to start
  • each fragment is charged at least a small fixed overhead toward max_message_size, so a flood of empty or tiny continuation frames is bounded by the cap, not just the total payload bytes
  • permessage-deflate is inflated in bounded chunks against max_message_size, so a small high-ratio frame cannot expand into gigabytes before the cap fires

When a cap closes a connection, roadrunner emits [roadrunner, ws, frame_rejected] (metadata reason, measurement size) so oversize and flood attempts are visible to subscribers.

HTTP/2 framing limits

These are advertised to the peer in the initial SETTINGS frame and enforced by the framing layer.

LimitDefaultConfigurable
SETTINGS_MAX_FRAME_SIZE16 KBno (fixed)
SETTINGS_MAX_CONCURRENT_STREAMS100no (fixed)
HPACK decoder table4 KBno (fixed)
Connection receive window (conn_window)65535yes
Stream receive window (stream_window)65535yes

A frame whose declared length exceeds the negotiated max frame size is rejected on its 9-byte header, before the body is buffered. Streams over the concurrency limit are refused.

Connection and slow-client guards

LimitDefaultConfigurablePurpose
max_clients150yesconcurrent connection cap per listener
request_timeout30 syesheader-read timeout on a fresh connection
keep_alive_timeout60 syesidle timeout between requests
max_keep_alive_requests1000yesrequests served per connection before close
min_bytes_per_second100yes (0 disables)slow-loris guard on the request-read phase

Configuring

Pass any configurable limit in the listener options map:

roadrunner:start_listener(my_api, #{
    port => 8080,
    routes => my_handler,
    max_content_length => 5_242_880,
    ws => #{max_frame_size => 1_048_576, max_message_size => 8_388_608}
}).

See roadrunner_listener:opts/0 for the full list and the canonical defaults.