Skip to content

Config

Configdataclass

Config(*, root_path: str = _option(default='', doc='ASGI root path (to mount the application at a subpath).', env_parse=str, cli_flags=('-r', '--root-path')), lifespan: LifespanMode = _option(default='auto', doc='ASGI lifespan handling mode.', env_parse=_normalize_lifespan, normalize=_normalize_lifespan, cli_choices=_LIFESPAN_MODES), timeout_lifespan_startup: float = _option(default=60.0, doc='Maximum time to wait for ASGI lifespan startup in seconds. Use 0 to disable.', env_parse=float, normalize=_non_negative('timeout_lifespan_startup'), cli_type=float), timeout_lifespan_shutdown: float = _option(default=30.0, doc='Maximum time to wait for ASGI lifespan shutdown in seconds. Use 0 to disable.', env_parse=float, normalize=_non_negative('timeout_lifespan_shutdown'), cli_type=float), bind: tuple[str, ...] = _option(default_factory=lambda: _DEFAULT_BIND, doc='Listener addresses to bind. Repeat the flag to add more listeners. Supports HOST:PORT, [IPv6]:PORT, unix:PATH, and fd://N.', env_parse=_parse_bind_env, normalize=_normalize_bind_specs, cli_flags=('-b', '--bind'), cli_action='append', cli_type=str, cli_metavar='ADDRESS'), uds_permissions: int | None = _option(default=None, doc='Octal mask for Unix Domain Socket permissions.', env_parse=_parse_octal, cli_type=_parse_octal), backlog: int = _option(default=1024, doc='Maximum number of queued connections on the listening socket.', env_parse=int, normalize=_minimum('backlog', 1), cli_type=int), reuse_port: bool = _option(default=False, doc='Set SO_REUSEPORT on the TCP listeners so other server processes can bind the same port — for zero-downtime deploys or independent processes. Workers of one server always share the inherited listener. TCP listeners only.', env_parse=_parse_bool, cli_action='bool'), certfile: Path | None = _option(default=None, doc='PEM certificate chain file for direct TLS.', env_parse=Path, normalize=_optional_path, cli_type=Path), keyfile: Path | None = _option(default=None, doc='PEM private key file for direct TLS. Encrypted keys are not supported.', env_parse=Path, normalize=_optional_path, cli_type=Path), ca_certs: Path | None = _option(default=None, doc='PEM CA bundle used to verify client certificates for direct TLS.', env_parse=Path, normalize=_optional_path, cli_type=Path), cert_reqs: CertReqsMode = _option(default='none', doc='Client certificate verification mode for direct TLS.', env_parse=_normalize_cert_reqs, normalize=_normalize_cert_reqs, cli_choices=_CERT_REQS_MODES), pid: Path | None = _option(default=None, doc='Write the server process PID to this file.', env_parse=Path, normalize=_optional_path, cli_type=Path), user: str | int | None = _option(default=None, doc='User name or numeric UID for worker processes and created Unix sockets.', env_parse=str, normalize=_optional_principal('user'), cli_flags=('-u', '--user'), cli_type=str), group: str | int | None = _option(default=None, doc='Group name or numeric GID for worker processes and created Unix sockets.', env_parse=str, normalize=_optional_principal('group'), cli_flags=('-g', '--group'), cli_type=str), umask: int | None = _option(default=None, doc='Octal process umask to apply before creating files and sockets. Leave unset to preserve the inherited umask.', env_parse=_parse_octal, normalize=_optional_octal_mask('umask'), cli_flags=('-m', '--umask'), cli_type=_parse_octal), workers: int = _option(default=1, doc='Number of worker processes to spawn.', env_parse=int, normalize=_minimum('workers', 1), cli_flags=('-w', '--workers'), cli_type=int), runtime_threads: int = _option(default=2, doc='Number of Tokio runtime worker threads per worker process.', env_parse=int, normalize=_minimum('runtime_threads', 1), cli_type=int), loop: LoopImpl = _option(default='auto', doc="Python event-loop implementation: 'auto' uses uvloop when installed (the 'h2corn[uvloop]' extra), otherwise the stdlib asyncio loop. h2corn runs its I/O in Rust, so this only affects how application callbacks are scheduled.", env_parse=_normalize_loop, normalize=_normalize_loop, cli_choices=_LOOP_IMPLS), loop_threads: int = _option(default=1, doc='Number of Python event-loop threads per worker, balanced round-robin. Requires a free-threaded (no-GIL) interpreter; ignored on GIL builds. Composes with workers — keep workers x loop_threads at or below the core count.', env_parse=int, normalize=_minimum('loop_threads', 1), cli_type=int), max_requests: int = _option(default=0, doc='Maximum number of requests or WebSocket sessions a worker should complete before retiring. Use 0 to disable.', env_parse=int, normalize=_non_negative('max_requests'), cli_type=int), max_requests_jitter: int = _option(default=0, doc='Maximum jitter added to max_requests to stagger worker retirements. Use 0 to disable.', env_parse=int, normalize=_non_negative('max_requests_jitter'), cli_type=int), timeout_worker_healthcheck: float = _option(default=30.0, doc='Maximum time between worker healthcheck heartbeats before the supervisor replaces the worker. Use 0 to disable.', env_parse=float, normalize=_non_negative('timeout_worker_healthcheck'), cli_type=float), http1: bool = _option(default=True, doc='Whether to accept HTTP/1.1 connections. Intended for development; disable in production.', env_parse=_parse_bool, cli_action='bool'), access_log: bool = _option(default=True, doc='Whether to log requests to stderr.', env_parse=_parse_bool, cli_action='bool'), max_concurrent_streams: int = _option(default=256, doc='Maximum active HTTP/2 streams per connection.', env_parse=int, normalize=_non_negative_u32('max_concurrent_streams'), cli_type=int), limit_request_head_size: int = _option(default=1048576, doc='Maximum total HTTP/1.1 request head size in bytes. Use 0 for no limit.', env_parse=int, normalize=_non_negative('limit_request_head_size'), cli_type=int), limit_request_line: int = _option(default=16384, doc='Maximum HTTP/1.1 request line size in bytes. Use 0 for no limit.', env_parse=int, normalize=_non_negative('limit_request_line'), cli_type=int), limit_request_fields: int = _option(default=100, doc='Maximum number of request header fields; HTTP/2 counts every decoded field, including duplicates. Use 0 for no limit.', env_parse=int, normalize=_non_negative('limit_request_fields'), cli_type=int), limit_request_field_size: int = _option(default=32768, doc='Maximum individual HTTP/1.1 header field size in bytes. Use 0 for no limit.', env_parse=int, normalize=_non_negative('limit_request_field_size'), cli_type=int), h2_max_header_list_size: int = _option(default=1048576, doc='Maximum decoded HTTP/2 header list size in bytes. Use 0 for no limit.', env_parse=int, normalize=_non_negative_u32('h2_max_header_list_size'), cli_type=int), h2_max_header_block_size: int = _option(default=1048576, doc='Maximum compressed HTTP/2 header block size in bytes while collecting HEADERS and CONTINUATION frames. Use 0 for no limit.', env_parse=int, normalize=_non_negative('h2_max_header_block_size'), cli_type=int), h2_max_inbound_frame_size: int = _option(default=65536, doc='Maximum inbound HTTP/2 frame payload size to accept and advertise via SETTINGS_MAX_FRAME_SIZE.', env_parse=int, normalize=_h2_frame_size('h2_max_inbound_frame_size'), cli_type=int), h2_initial_stream_window_size: int = _option(default=1048576, doc='HTTP/2 per-stream receive flow-control window in bytes (SETTINGS_INITIAL_WINDOW_SIZE). Bounds worst-case buffered upload bytes per stream; raise for high-bandwidth-delay uploads.', env_parse=int, normalize=_h2_window_size('h2_initial_stream_window_size'), cli_type=int), h2_initial_connection_window_size: int = _option(default=2097152, doc='HTTP/2 connection-wide receive flow-control window in bytes. Bounds total buffered upload bytes per connection.', env_parse=int, normalize=_h2_window_size('h2_initial_connection_window_size'), cli_type=int), max_request_body_size: int = _option(default=1073741824, doc='Maximum request body size in bytes. Use 0 for no limit.', env_parse=int, normalize=_non_negative('max_request_body_size'), cli_type=int), limit_concurrency: int = _option(default=0, doc='Maximum number of concurrent ASGI request or session tasks per worker. Use 0 for no limit.', env_parse=int, normalize=_non_negative('limit_concurrency'), cli_type=int), limit_connections: int = _option(default=0, doc='Maximum number of live client connections per worker. Use 0 for no limit.', env_parse=int, normalize=_non_negative('limit_connections'), cli_type=int), timeout_handshake: float = _option(default=5.0, doc='Maximum time to establish a connection and TLS handshake, in seconds. Use 0 to disable.', env_parse=float, normalize=_non_negative('timeout_handshake'), cli_type=float), timeout_graceful_shutdown: float = _option(default=30.0, doc='Maximum time for workers to finish in-flight requests on shutdown, in seconds.', env_parse=float, normalize=_non_negative('timeout_graceful_shutdown'), cli_type=float), timeout_keep_alive: float = _option(default=120.0, doc='Idle keep-alive timeout in seconds. Use 0 to disable.', env_parse=float, normalize=_non_negative('timeout_keep_alive'), cli_type=float), timeout_request_header: float = _option(default=10.0, doc='Idle timeout in seconds while reading an HTTP request head or an HTTP/2 header block. Use 0 to disable.', env_parse=float, normalize=_non_negative('timeout_request_header'), cli_type=float), timeout_request_body_idle: float = _option(default=60.0, doc='Idle timeout in seconds while reading an HTTP request body. Use 0 to disable.', env_parse=float, normalize=_non_negative('timeout_request_body_idle'), cli_type=float), h2_timeout_response_stall: float = _option(default=60.0, doc='Timeout in seconds for HTTP/2 response body bytes pinned behind peer flow control. Use 0 to disable.', env_parse=float, normalize=_non_negative('h2_timeout_response_stall'), cli_type=float), websocket_max_message_size: int | None = _option(default=16777216, doc="Maximum WebSocket message size in bytes. Defaults to 16 MiB. Use 'inherit' to follow `max_request_body_size`, or 0 for no limit.", env_parse=_parse_optional_non_negative_int, normalize=_optional_non_negative('websocket_max_message_size'), cli_type=_parse_optional_non_negative_int), websocket_per_message_deflate: bool = _option(default=True, doc='Whether to negotiate permessage-deflate for WebSockets when the client offers it.', env_parse=_parse_bool, cli_action='bool'), websocket_ping_interval: float = _option(default=60.0, doc='Interval in seconds between server WebSocket ping frames. Use 0 to disable.', env_parse=float, normalize=_non_negative('websocket_ping_interval'), cli_type=float), websocket_ping_timeout: float = _option(default=30.0, doc='Time limit in seconds to wait for a pong after a server WebSocket ping. Use 0 to disable.', env_parse=float, normalize=_non_negative('websocket_ping_timeout'), cli_type=float), proxy_headers: bool = _option(default=False, doc='Trust proxy headers (e.g., Forwarded, X-Forwarded-*) if the client IP is in `forwarded_allow_ips`.', env_parse=_parse_bool, cli_action='bool'), forwarded_allow_ips: tuple[str, ...] = _option(default_factory=lambda: ('127.0.0.1', '::1', 'unix'), doc="Allowed IPs or networks (in CIDR notation) for proxy headers. Use '*' to trust all.", env_parse=_parse_csv_tuple, normalize=_normalize_forwarded_allow_ips, cli_type=_parse_csv_tuple), proxy_protocol: ProxyProtocolMode = _option(default='off', doc="Expect HAProxy's PROXY protocol on inbound connections.", env_parse=_normalize_proxy_protocol, normalize=_normalize_proxy_protocol, cli_choices=_PROXY_PROTOCOL_MODES), server_header: bool = _option(default=False, doc='Whether to add a default Server header when the application sets none.', env_parse=_parse_bool, cli_action='bool'), date_header: bool = _option(default=True, doc='Whether to add a default Date header when the application sets none.', env_parse=_parse_bool, cli_action='bool'), response_headers: tuple[str, ...] = _option(default_factory=tuple, doc='Additional default response headers in `name: value` form. Repeat the flag to add more headers.', env_parse=_parse_csv_tuple, normalize=_normalize_str_tuple, cli_flags=('--header',), cli_action='append', cli_type=str, cli_metavar='HEADER'), host: str | None = None, port: int | None = None)

Immutable server configuration.

Every option is also exposed as a CLI flag and an H2CORN_* environment variable. The full list and defaults are documented in the configuration reference of the project documentation.

Resolution order (highest precedence first):

  1. CLI flags
  2. Environment variables
  3. TOML file (via --config or H2CORN_CONFIG)
  4. Built-in defaults

Validation runs in __post_init__. Invalid combinations (for example, certfile without keyfile, or a Unix listener with TLS) raise ValueError at construction time.

Unix listeners (unix:PATH) and the user/group/umask options are POSIX-only.

from_envclassmethod

from_env(env: Mapping[str, str]) -> Self

Build a Config from H2CORN_* environment variables.

Each option's environment variable name is its field name in upper case, prefixed with H2CORN_ (for example, H2CORN_BIND, H2CORN_WORKERS, H2CORN_PROXY_HEADERS). Variables that are not set fall through to defaults; unrecognized variables are ignored.

The convenience pair H2CORN_HOST / H2CORN_PORT is accepted only when H2CORN_BIND is unset.

from_mappingclassmethod

from_mapping(data: Mapping[str, Any]) -> Self

Build a Config from a plain mapping (e.g. parsed JSON or TOML).

Keys must match either a field name on Config or the convenience keys host/port. Unknown keys raise ValueError. String values are coerced through the same parsing rules as the matching environment variable; non-string values are passed through and validated by __post_init__.

from_tomlclassmethod

from_toml(path: str | PathLike[str]) -> Self

Build a Config from a TOML file.

The file must be a flat table whose keys correspond to Config field names. Example:

bind = ["127.0.0.1:8000"]
workers = 4
proxy_headers = true
forwarded_allow_ips = ["127.0.0.1", "::1", "unix"]
http1 = false

ProxyProtocolModemodule-attribute

ProxyProtocolMode = Literal['off', 'v1', 'v2']