# pqcrypta-proxy benchmark configuration — LEAN arm. # # pqc-bench.toml turns off every *feature* HAProxy lacks, but a feature switched # off in config still left its work on the wire: sixteen security headers, an # x-ratelimit pair, alt-svc and x-webtransport-port on every response, the # backend's Server header replaced, and response compression that could not be # switched off at all — ~900 bytes per response against HAProxy's zero. This # file is that config plus the header, Alt-Svc, Server and compression settings # HAProxy's own config does not use, so both proxies put the same response on # the wire. It needs the build in which a disabled layer leaves the middleware # chain; the startup log names every layer left out. # # Original header of pqc-bench.toml follows. # # Matched to the HAProxy config: same certificate, same backend, 4 workers, # TLS 1.3 only. Every security feature that HAProxy has no equivalent for is # OFF for the like-for-like run — WAF, fingerprinting, rate limiting, audit # logging. A second run turns them back on, because that is the configuration # anyone actually deploys and the cost of it is a fair thing to publish. version = 1 [server] bind_address = "127.0.0.1" udp_port = 18444 # One physical core each (CPUs 4-5 are SMT siblings), two workers each, matched # to HAProxy nbthread 2. The backend needs two physical cores of its own: on one # it saturated and became the ceiling in ten of HAProxy's eighteen cells. worker_threads = 2 max_connections = 60000 enable_ipv6 = false graceful_shutdown_timeout_secs = 1 # The extension is negotiated, so leaving it on cannot disadvantage HAProxy — # but multipath is off so both sides run a plain single-path QUIC connection. max_concurrent_multipath_paths = 1 enable_ack_frequency = true enable_quic_retry = false enable_quic_migration = true # This box already runs a production pqcrypta-proxy node. The bench instance must # not touch its ports: WebTransport off its default 4433, and the port-80 HTTP # redirect server disabled outright (see [http_redirect]). webtransport_port = 18445 # HAProxy's TCP frontend advertises no Alt-Svc (its QUIC frontend does, and so # does ours: the HTTP/3 response always names h2 on this port). alt_svc_ports = [] # HAProxy passes the backend's Server header through; so does an empty value. server_header = "" [tls] cert_path = "/root/bench/certs/bench.local.crt" key_path = "/root/bench/certs/bench.local.key" min_version = "1.3" alpn_protocols = ["h3", "h2", "http/1.1"] ocsp_stapling = false enable_0rtt = false pqc_session_tickets = false [pqc] # Classical for the like-for-like run. The PQC handshake cost is measured # separately, against HAProxy also configured for X25519MLKEM768, so that # number is a comparison rather than a penalty applied to one side only. enabled = false [backends.static] name = "static" type = "http1" address = "127.0.0.1:18080" tls = false timeout_ms = 30000 max_connections = 20000 [[routes]] name = "all" host = "bench.local" path_prefix = "/" backend = "static" # The HTTP/1.1 column measures a route that permits HTTP/1.1. This used to be # implicit: the per-route gate read a header the plain accept loop never set, so # it was inert here and h1 was served regardless. Now that the gate reads the # request version, the route has to say what it always meant. allow_http11 = true [waf] enabled = false [fingerprint] enabled = false [rate_limiting] enabled = false [advanced_rate_limiting] enabled = false [security] dos_protection = false [logging] level = "error" access_log = false audit_log_enabled = false [admin] enabled = false [ocsp] enabled = false [acme] enabled = false [cache] enabled = false [otel] enabled = false [conformance] enabled = false [masque] enabled = false [http_redirect] # Off: the default binds port 80, which the production node on this box owns. enabled = false [connection_pool] # Matched to what production actually runs, and comparable to HAProxy's # maxconn 20000 on the same backend. Leaving this at the built-in default of 10 # idle connections per host meant that at concurrency 100 most requests opened a # fresh backend TCP connection while HAProxy reused freely — which is a # difference between two configs, not between two proxies. idle_timeout_secs = 90 max_idle_per_host = 500 max_connections_per_host = 20000 acquire_timeout_ms = 5000 [http3] # OFF for the like-for-like run. These default to on, and each is per-request # work HAProxy is not doing: coalescing takes a DashMap lookup and possible # subscriber registration on every GET, early hints builds and writes a 103 # before the real response, priority hints parses and emits a Priority header. # They are real features and the default is defensible — but a run that leaves # them on while HAProxy does plain proxying is not the like-for-like comparison # it claims to be. conf/pqc-bench-features.toml keeps them on for the second # arm, which is the number anyone actually deploying this should read. early_hints_enabled = false priority_hints_enabled = false coalescing_enabled = false preconnect_origins = [] [headers] # Same reasoning: Server-Timing instruments every request and emits a header. server_timing_enabled = false # Every response header the proxy adds of its own accord. HAProxy adds none; # an empty value now omits the header rather than sending it empty. hsts = "" x_frame_options = "" x_content_type_options = "" referrer_policy = "" permissions_policy = "" cross_origin_opener_policy = "" cross_origin_embedder_policy = "" cross_origin_resource_policy = "" x_permitted_cross_domain_policies = "" x_download_options = "" x_dns_prefetch_control = "" x_quantum_resistant = "" x_security_level = "" content_security_policy = "" accept_ch = "" nel = "" report_to = "" priority = "" addin_csp = "" [compression] # HAProxy compresses only when `compression algo` is configured, and it is not. enabled = false