PQ CRYPTA PLATFORM

๐Ÿ  Main

๐Ÿงช Interactive Apps

๐Ÿ“ฐ News

๐Ÿ›ก๏ธ PQ Crypta Proxy

๐Ÿ‘ค Account

โŸจ QUANTUM ERROR PORTAL โŸฉ

Navigate the Error Dimensions

PQ Crypta Logo

Proxy Control Plane

PQCrypta Reverse Proxy — Live Configuration Dashboard

๐Ÿ”’ Read Only Live Config · redacted proxy-config.sanitized.toml · 2026-09-07 07:17 UTC
๐Ÿ”’This is a read-only control plane. All controls display current configuration values from proxy-config.toml. No changes can be made from this interface.
๐ŸŒ
Primary Port
443
QUIC/HTTP3/WebTransport
QUIC, HTTP/3 and WebTransport listener. Also serves HTTP/2 and HTTP/1.1 via ALPN negotiation on the same port.server.udp_port
โšก
Max Connections
10,000
Concurrent QUIC sessions
Maximum concurrent QUIC sessions across all clients. Prevents connection exhaustion under high load.server.max_connections
๐Ÿ›ก
PQC Algorithm
X25519MLKEM768
Hybrid key exchange active
Post-quantum key encapsulation mechanism used for TLS key exchange. ML-KEM-1024 provides NIST Level 5 quantum resistance. Applied in hybrid mode alongside classical X25519.pqc.preferred_kem
๐Ÿ”€
Backends
6
Defined single backends
Named upstream servers available for routing. Each backend has its own TLS mode, timeouts, health check path, and connection limits. Does not include pool members.[backends.NAME]
๐Ÿ—บ
Routes
60
Active routing rules
HTTP routing rules evaluated in order by host and path prefix or regex pattern. Each rule selects a backend or load-balanced pool and can override headers.[[routes]]
โš™
LB Algorithm
least_connections
Load balancing strategy
Default strategy for distributing requests across backend pool servers. Options: round_robin, least_connections, ip_hash, random, weighted. Individual pools can override this.load_balancer.default_algorithm
๐Ÿ”
Circuit Breaker
Active
Backend protection
Stops forwarding to backends that exceed the failure threshold. Uses a half-open state to probe recovery. Prevents cascading failures across the backend pool.circuit_breaker.enabled
โฑ
Rate Limit
Configured
Multi-layer rate limiting
Multi-layer rate limiting: global RPS cap, per-IP limits, per-API-key quotas, and AI-based adaptive rate control. Redis-backed for distributed enforcement across instances.[rate_limiting] ยท [advanced_rate_limiting]
๐Ÿงฑ
WAF
Block
OWASP Top 10 inspection
Request inspection for SQL injection, XSS, path traversal, NoSQL injection and scanner probes. In block mode a rule hit is rejected with 403; detect mode logs only. Runs on both the TCP and HTTP/3 paths.waf.enabled ยท waf.mode
๐Ÿ’พ
Response Cache
128
MB RFC 9111 cache
In-memory HTTP response cache following RFC 9111 (Cache-Control semantics). Reduces backend load for cacheable GET responses. Shared across all routes.cache.max_size_mb
๐Ÿ“ก
ALPN Protocols
h3, h2, http/1.1
+ 1 more
TLS Application-Layer Protocol Negotiation list advertised during the handshake. Controls which HTTP versions clients can request. h3=HTTP/3, h2=HTTP/2, http/1.1.tls.alpn_protocols
๐Ÿ”
TLS Min Version
TLS 1.3
QUIC requires TLS 1.3
Minimum accepted TLS version for incoming connections. QUIC mandates TLS 1.3. TLS 1.2 may be enabled for HTTP/1.1 and HTTP/2 legacy client compatibility.tls.min_version
๐Ÿค–
JA3/JA4 Fingerprint
Enabled
TLS fingerprint detection
Fingerprints the TLS ClientHello to identify bot families, automated scanners, and malicious clients by their TLS stack signature. Cross-referenced against blocklists.fingerprint.enabled
โšก
Server
bind_address, udp_port, MASQUE
QUIC/HTTP3 listener settings: bind address, ports, connection limits, worker threads, idle timeouts, graceful shutdown, per-host transport overrides, and MASQUE CONNECT-UDP relaying.
๐Ÿ”
TLS & PKI
cert_path, alpn_protocols, OCSP
TLS certificate paths, ALPN protocol list, minimum TLS version, OCSP stapling and refresh policy, mTLS client auth, and ACME/Let's Encrypt auto-renewal.
๐Ÿ›ก
Post-Quantum Crypto
provider, preferred_kem, fallback
Post-quantum cryptography: KEM algorithm selection (ML-KEM-1024), hybrid mode toggle, OQS provider path, and classical fallback policy.
๐Ÿ”’
Security & WAF
dos_protection, [waf], JA3/JA4
Threat protection: DoS limits, static IP/CIDR blocklists, GeoIP country blocking, the OWASP Top 10 WAF ruleset and its mode, and TLS fingerprint detection.
โฑ
Rate Limiting
basic + advanced, redis, adaptive
Basic global RPS caps plus advanced per-IP, per-API-key, and per-route quotas. Redis-backed for distributed enforcement. Includes AI-based adaptive rate control.
๐Ÿ”
Circuit Breaker
failure_threshold, half_open
Automatically opens when a backend exceeds the failure threshold, then probes recovery via a half-open state. Prevents cascading failures across the pool.
โšก
HTTP/3 & Cache
103 early hints, coalescing, cache
HTTP/3 features: 103 Early Hints with per-host preload lists, RFC 9218 priority hints, and in-flight request coalescing. Plus the RFC 9111 response cache and OpenTelemetry tracing export.
๐Ÿ“‹
Security Headers
HSTS, CSP, CORS, NEL, Report-To
Security and compliance response headers: HSTS, Content-Security-Policy, CORS policy, Permissions-Policy, NEL, Report-To, and custom header injection/removal rules.
๐Ÿ”€
Backends & LB
6 backends, least_connections
Upstream backend definitions (individual servers and load-balanced pools), default LB algorithm, connection pool sizing, health check paths, and canary traffic splitting.
๐Ÿ—บ
Routes
60 routing rules across 11 host(s)
HTTP routing rules matched by host and path. Each rule selects a backend or pool, can override headers, enforce HTTPS redirect, and apply per-route CORS or rate limit overrides.
๐Ÿ“
Logging & Admin
json logs, admin management API
Structured JSON logging level and output, access log destination, and the admin management API: bind address, port, mTLS requirement, bearer token and IP allowlist.
๐Ÿ”’This is a read-only control plane. All controls display current configuration values from proxy-config.toml. No changes can be made from this interface.
โšก Server Configuration [server] 17 settings โ–ผ
bind_address

Bind address for the QUIC/UDP and TCP listeners. "[::]" is dual-stack โ€” with net.ipv6.bindv6only=0 it accepts IPv6 and IPv4 (v4-mapped) on one socket; brackets are required because listeners build "{bind_address}:{port}".

udp_portport

Primary UDP port for QUIC/HTTP3/WebTransport

max_connectionsconnections

Maximum concurrent connections

max_streams_per_connectionstreams

Maximum concurrent streams per QUIC connection

keepalive_interval_secsseconds

Keep-alive interval

max_idle_timeout_secsseconds

Maximum idle timeout before connection close

worker_threadsthreads

Tokio runtime worker threads, read before the runtime is built. 0 = one per CPU core.

graceful_shutdown_timeout_secsseconds

Graceful shutdown drain timeout after SIGTERM before forced exit

enable_ipv6

Dual-stack binding. When off, a wildcard [::] bind is downgraded to 0.0.0.0 so only IPv4 is served; an explicitly chosen IPv6 address is honoured with a warning rather than rewritten.

enable_ack_frequency

QUIC ACK Frequency extension (draft-ietf-quic-ack-frequency) โ€” lets the peer request fewer, batched acknowledgements. Negotiated, so clients without it are unaffected.

enable_quic_retry

QUIC Retry for explicit source-address validation (RFC 9000 ยง8.1.2). Hardens against spoofed-source amplification at the cost of one extra round trip per new connection.

max_concurrent_multipath_pathspaths

Concurrent data-carrying paths per connection (draft-ietf-quic-multipath, via the noq stack). Paths are created, validated and torn down automatically once the peer negotiates multipath; 1 keeps every connection single-path and 0 stops the extension being negotiated at all.

normalize_paths

Lowercase request paths at the proxy. Disabled where backends enforce their own casing or use case-sensitive hashes.

additional_ports

Additional ports to listen on (all support QUIC/HTTP3/WebTransport)

[configured]
webtransport_allowed_origins

Allowed WebTransport origins โ€” browser Origin header validation. Empty = reject all browser connections.

https://pqcrypta.comhttps://api.pqcrypta.comhttps://pqpdf.comhttps://www.pqpdf.comhttps://stlweb.devhttps://office.stlweb.devhttps://fated.orghttps://www.fated.org
tcp_only_hosts

Hosts that must never advertise an HTTP/3 upgrade. Responses carry Alt-Svc: clear, evicting any cached QUIC upgrade so the browser always uses TCP/TLS.

tcp.pqcrypta.com
http11_only_hosts

Hosts served without h2 in the ALPN list. Prevents browser HTTP/2 connection coalescing so parallel fetches open independent TCP connections instead of sharing one pipe.

tcp.pqcrypta.comapi.pqpdf.com
๐Ÿ“จ MASQUE / CONNECT-UDP (RFC 9298) [masque] 4 settings โ–ผ
enabled

Proxy UDP over HTTP/3 Extended CONNECT. Off by default โ€” an open UDP relay is an amplification and SSRF risk.

allowed_targets

Relay allowlist as host:port. A request is permitted only if it matches an entry; an empty list permits nothing. Targets withheld.

None โ€” no relay target permitted
session_idle_timeout_secsseconds

A session with no datagrams in either direction for this long is closed

max_sessions_per_connectionsessions

Maximum concurrent CONNECT-UDP sessions per QUIC connection

โ†ฉ HTTP Redirect [http_redirect] 4 settings โ–ผ
enabled

Enable HTTP redirect server on port 80

portport

HTTP port โ€” listens for plain HTTP and redirects to HTTPS

redirect_to_https

Redirect plain HTTP to HTTPS. When off, HTTP requests are refused with 400 rather than served โ€” ACME challenges are answered either way, so renewal does not depend on this.

allowed_domains

Allowed hostnames for the HTTPโ†’HTTPS redirect. Requests with a Host header not in this list receive 400 (prevents open-redirect abuse).

None configured
๐Ÿ”’This is a read-only control plane. All controls display current configuration values from proxy-config.toml. No changes can be made from this interface.
๐Ÿ” TLS Configuration [tls] 9 settings โ–ผ
cert_path

Path to TLS certificate chain (PEM format)

key_path

Path to TLS private key (PEM format)

ca_cert_path

Optional CA certificate for client certificate verification (mTLS). Not set = mTLS disabled unless require_client_cert is true.

require_client_cert

Require a client certificate on every inbound TLS connection (mTLS). Enabling this on a public listener makes the site unreachable to any visitor without an issued client certificate โ€” for admin-surface mTLS use admin.require_mtls instead.

min_version

Minimum TLS version โ€” only TLS 1.3 is supported for QUIC

  • 1.3 required for QUIC
  • 1.2
alpn_protocols

ALPN protocols advertised during TLS handshake

h3h2http/1.1webtransport
ocsp_stapling

TLS-side stapling switch. Both this and [ocsp].enabled must be on for the proxy to fetch and staple revocation responses.

cert_reload_interval_secsseconds

Certificate hot-reload interval (0 = disabled)

enable_0rtt

Enable 0-RTT early data for faster reconnections. โš ๏ธ Vulnerable to replay attacks.

๐Ÿ“œ OCSP Stapling [ocsp] 6 settings โ–ผ
enabled

Fetch and staple OCSP responses so clients need no separate revocation round trip

cache_duration_secsseconds

How long a fetched OCSP response is reused

refresh_before_expiry_secsseconds

Refresh lead time โ€” a new response is fetched this long before the cached one expires

timeout_secsseconds

Timeout for a request to the CA responder

max_retriesattempts

Retry attempts before giving up on a responder

retry_delay_msms

Delay between retry attempts

๐Ÿ”„ ACME Certificate Automation [acme] 13 settings โ–ผ
enabled

Enable ACME certificate automation (Let's Encrypt)

domains

Domains to manage certificates for

pqcrypta.comwww.pqcrypta.comapi.pqcrypta.compqpdf.comwww.pqpdf.comapi.pqpdf.comfated.orgwww.fated.orgtcp.pqcrypta.comstlweb.devwww.stlweb.devoffice.stlweb.devconformance.pqcrypta.com
challenge_type

ACME challenge type for domain validation

  • http-01 port 80 required
  • dns-01
  • tls-alpn-01
directory_url

ACME directory URL

email

Contact email for Let's Encrypt notifications

certs_path

Directory to store managed certificates

account_path

ACME account credentials storage

renewal_daysdays

Renew certificates this many days before expiry

check_interval_hourshours

Check interval for certificate expiry

http_portport

Port the http-01 challenge responder listens on โ€” the CA always validates on 80

use_ecdsa

Use ECDSA keys โ€” smaller keys and faster TLS handshakes than RSA

ecdsa_curve

ECDSA curve for issued certificates. P-384 = 192-bit, equivalent to 7680-bit RSA; P-256 = 128-bit.

accept_tos

Accept CA terms of service (required for Let's Encrypt)

๐Ÿ”’This is a read-only control plane. All controls display current configuration values from proxy-config.toml. No changes can be made from this interface.
๐Ÿ›ก Post-Quantum Cryptography [pqc] 13 settings โ–ผ
enabled

Enable PQC hybrid key exchange in TLS handshake

provider

PQC provider โ€” auto uses rustls for QUIC, OpenSSL for broader algorithms

  • auto recommended
  • rustls pure Rust, QUIC-native
  • openssl3.5 ML-KEM native
openssl_path

Path to OpenSSL 3.5+ binary with PQC support

openssl_lib_path

OpenSSL library path for dynamic linking

preferred_kem

Preferred KEM algorithm for hybrid key exchange (IETF standard)

  • X25519MLKEM768 recommended hybrid
  • x25519_kyber768 hybrid classical+PQ
  • SecP256r1MLKEM768
  • SecP384r1MLKEM1024 Level 5
  • ML-KEM-1024 FIPS 203 pure PQC
fallback_to_classical

Fall back to classical TLS if PQC is unavailable

min_security_levelNIST level

Minimum NIST security level (1=128-bit, 3=192-bit, 5=256-bit)

additional_kems

Additional KEM algorithms to offer (in preference order after preferred_kem)

None configured
enable_signaturesnot set · proxy default

Serve ML-DSA-87 (FIPS 204) certificate keys, signed through the dedicated PQDSA signer (TLS signature scheme 0x0906). With this off, a host configured with an ML-DSA certificate is refused at load rather than silently falling back to a classical key.

require_hybridnot set · proxy default

Offer only hybrid (classical + post-quantum) key-exchange groups. Suppresses both a pure-PQC preferred_kem and the classical P-384 fallback, so a client supporting neither fails the handshake. Left off here: on a public listener it turns away every browser without PQC support.

verify_providernot set · proxy default

Verify OpenSSL provider integrity at startup

check_key_permissionsnot set · proxy default

Check TLS key file permissions (should be 0600 or 0400)

strict_key_permissionsnot set · proxy default

Fail startup if key permissions are insecure (vs just warning)

๐Ÿ”’This is a read-only control plane. All controls display current configuration values from proxy-config.toml. No changes can be made from this interface.
๐Ÿ”’ Security Settings [security] 16 settings โ–ผ
max_request_sizebytes

Maximum request body size

max_header_sizebytes

Maximum header size

connection_timeout_secsseconds

Connection establishment timeout

dos_protection

Enable DoS protection layer

max_connections_per_ipconnections

Maximum concurrent connections from a single IP

blocked_ips

Static IP/CIDR blocklist applied before any other check. Entries withheld.

1 entry โ€” withheld
allowed_ips

Allowed IP allowlist (empty = allow all). Entries withheld.

1 entry โ€” withheld
blocked_countries

Blocked ISO 3166-1 alpha-2 country codes

CNRUKPIRNZ
error_4xx_threshold

4xx errors before triggering suspicious-pattern check

min_requests_for_error_check

Minimum requests before error rate check activates

error_rate_threshold

Error rate (0.0โ€“1.0) that triggers suspicious pattern (e.g. 0.7 = 70%)

error_window_secsseconds

Sliding window duration for error detection

auto_block_threshold

Suspicious pattern count before auto-block triggers

auto_block_duration_secsseconds

Duration of automatic IP block after threshold exceeded

geoip_db_path

MaxMind GeoLite2 database backing country blocking

pentest_bypass_ips

Authorised pentest sources: rate limiting and auto-block are skipped for these addresses, but every request is still WAF-inspected and blocked attacks still return 403. Addresses withheld.

1 entry โ€” withheld
๐Ÿงฑ Web Application Firewall [waf] 12 settings โ–ผ
enabled

Enable request inspection for OWASP Top 10 attack patterns

mode

Action taken on a rule hit

  • block reject with 403
  • detect log only, for tuning
sqli

OWASP A03 โ€” SQL injection pattern matching

xss

OWASP A03 โ€” cross-site scripting pattern matching

path_traversal

OWASP A01/A05 โ€” path traversal detection (../, %2e%2e and encodings)

nosqli

OWASP A03 โ€” NoSQL injection detection ($where, $gt operator abuse)

ssrf

OWASP A10 โ€” SSRF pattern detection. High false-positive rate on URL form fields.

scan_json_body

Inspect JSON request bodies, not just the query string and headers

max_body_scan_bytesbytes

Maximum request body bytes inspected โ€” bodies beyond this are passed through unscanned

scanner_probe

Block known reconnaissance probe paths (Nikto, sqlmap, directory scanners)

block_scanner_uas

Block known scanner and bot user-agents (sqlmap, nikto, masscan, headless clients)

custom_patterns

Operator-supplied regex rules layered on top of the built-in ruleset. Pattern bodies withheld.

None โ€” built-in ruleset only
๐Ÿ”ฌ TLS Fingerprint Detection (JA3/JA4) [fingerprint] 10 settings โ–ผ
enabled

Enable TLS fingerprint detection middleware

tls_layer_capture

Capture the raw ClientHello in a custom accept loop, giving a full JA3/JA4 before the handshake completes. Disabled falls back to header-derived fingerprints from the middleware layer.

block_scanners

Block fingerprints belonging to scanning tools (Nmap, Nikto, Burp Suite)

add_response_headers

Attach the computed fingerprint to responses for debugging and monitoring

block_maliciousnot set · proxy default

Automatically block connections presenting known-malicious JA3/JA4 fingerprints. false = advisory-only (log but not block).

malicious_block_duration_secsseconds

Block duration for confirmed malicious fingerprints

suspicious_block_duration_secsseconds

Block duration for suspicious fingerprints with high request rate

suspicious_rate_threshold

Request count threshold to trigger suspicious fingerprint rate check

suspicious_rate_window_secsseconds

Time window for suspicious rate detection

cache_max_age_secsseconds

Fingerprint cache max age before cleanup

๐Ÿ”’This is a read-only control plane. All controls display current configuration values from proxy-config.toml. No changes can be made from this interface.
โฑ Basic Rate Limiting [rate_limiting] 5 settings โ–ผ
enabled

Enable basic rate limiting (overridden by advanced when both enabled)

requests_per_secondreq/s

Maximum requests per second per IP address

burst_size

Token bucket burst size โ€” allows short spikes above the rate limit

connection_rate_limit

Enable connection rate limiting (new connections per second)

connections_per_secondconn/s

New connections allowed per second per IP

๐Ÿง  Advanced Rate Limiting [advanced_rate_limiting] composite keys, JA3, Redis, adaptive ML โ–ผ
Core Settings
enabled

Enable advanced multi-dimensional rate limiting (overrides basic)

ipv6_subnet_bitsbits

IPv6 subnet grouping โ€” /64 subnets treated as a single client (NAT-aware)

trusted_proxies

Trusted proxies whose X-Forwarded-For header is honoured when deriving the client IP. Addresses withheld โ€” publishing them would name the hops that can assert an arbitrary client identity.

1 entry โ€” withheld
Key Strategy [advanced_rate_limiting.key_strategy]
order

Priority order for rate limit key resolution (first found wins)

1: api_key 2: jwt_subject 3: ja3_fingerprint 4: real_ip 5: source_ip
fallback

Key resolution fallback when no other key is found

  • source_ip default fallback
  • real_ip
  • ja3_fingerprint
use_composite

Use composite key (combines multiple dimensions)

Headers [advanced_rate_limiting.headers]
api_key

Header name for API key extraction

user_id

Header name for user ID extraction

tenant_id

Header name for tenant ID extraction

real_ip

Header name for real IP extraction

Global Limits [advanced_rate_limiting.global_limits]
requests_per_secondreq/s

Global DDoS protection layer req/s

burst_size

Global burst bucket size

Per-IP Limits [advanced_rate_limiting.global_limits.per_ip]
requests_per_secondreq/s

Per-IP requests per second

burst_size

Per-IP burst size

requests_per_minutereq/min

Per-IP requests per minute

requests_per_hourreq/hr

Per-IP requests per hour

Per-Fingerprint Limits [advanced_rate_limiting.global_limits.per_fingerprint]
requests_per_secondreq/s

Per JA3/JA4 fingerprint req/s. Base limits for a fingerprint key; fingerprint_limiting refines these when it is enabled

burst_size

Per-fingerprint burst size

requests_per_minutereq/min

Per-fingerprint requests per minute

requests_per_hourreq/hr

Per-fingerprint requests per hour

Per-API-Key Limits [advanced_rate_limiting.global_limits.per_api_key]
requests_per_secondreq/s

Per API key requests per second

burst_size

Per API key burst size

requests_per_minutereq/min

Per API key requests per minute

requests_per_hourreq/hr

Per API key requests per hour

Per-Composite Limits [advanced_rate_limiting.global_limits.per_composite]
requests_per_secondreq/s

Per composite key req/s

burst_size

Per composite key burst size

requests_per_minutereq/min

Per composite key requests per minute

requests_per_hourreq/hr

Per composite key requests per hour

Redis Distributed [advanced_rate_limiting.redis]
url

Redis URL for distributed counter sharing across proxy instances

key_prefix

Redis key prefix for all rate limit counters

connect_timeout_msms

Redis connection timeout

command_timeout_msms

Redis command timeout โ€” fast fail to prevent blocking

distribute_per_second

Use per-second distributed sliding window

JA3/JA4 Fingerprint Limiting [advanced_rate_limiting.fingerprint_limiting]
enabled

Enable fingerprint-based rate limiting (NAT-friendly)

prefer_over_ip

Prefer fingerprint key over IP address for limiting

blocked_fingerprints

Explicitly blocked JA3/JA4 fingerprint hashes

No fingerprints blocked
Unknown Fingerprint Limits [advanced_rate_limiting.fingerprint_limiting.unknown_limits]
requests_per_secondreq/s

Rate limit for unknown/unclassified fingerprints

burst_size

Burst size for unknown fingerprints

requests_per_minutereq/min

Per-minute limit for unknown fingerprints

requests_per_hourreq/hr

Per-hour limit for unknown fingerprints

Adaptive Rate Limiting [advanced_rate_limiting.adaptive]
enabled

Enable ML-inspired adaptive rate limiting with anomaly detection

baseline_window_secsseconds

Baseline measurement window for normal traffic profiling

sensitivity

Detection sensitivity, 0.0 (laxest) to 1.0 (strictest). Scales std_dev_multiplier and is anchored at 0.7, where it yields the raw multiplier unchanged

auto_adjust

Tighten each key's minute and hour limits toward that key's own learned baseline. One-directional: the configured limit stays a ceiling and burst_size is the floor

min_samples

Minimum sample count before adaptive limiting activates

std_dev_multiplier

Standard deviation multiplier for anomaly threshold (requests > mean + N*stddev = anomaly)

Per-Route Limits [advanced_rate_limiting.route_limits.*] โ€” 1 route

Applied as a secondary check inside the request handler after route matching, in addition to the global limits above — on HTTP/1.1, HTTP/2 and HTTP/3 alike. Each route gets its own bucket, keyed by route name, so other traffic from the same client cannot drain a route's budget. exempt_keys lists key values this route does not limit at all.

Route Pattern Req/s Burst Req/min Req/hr Exempt keys
office-login /api/method/login โ€” โ€” โ€” โ€” โ€”
๐Ÿ”’This is a read-only control plane. All controls display current configuration values from proxy-config.toml. No changes can be made from this interface.
๐Ÿ” Circuit Breaker [circuit_breaker] 6 settings โ–ผ
enabled

Enable circuit breaker for backend protection against cascading failures

failure_threshold

Failure count to open the circuit

half_open_delay_secsseconds

Time before circuit transitions from Open โ†’ Half-Open state

half_open_max_requests

Maximum test requests allowed in Half-Open state

success_threshold

Success count required to close circuit from Half-Open state

stale_counter_cleanup_secsseconds

Stale request counter cleanup interval

๐Ÿ”— HTTP Connection Pool [connection_pool] 4 settings โ–ผ
idle_timeout_secsseconds

How long idle connections remain in pool before being closed

max_idle_per_host

Maximum idle connections kept per backend host

max_connections_per_host

Maximum total connections per backend host

acquire_timeout_msms

Connection acquire timeout from pool

๐Ÿ”’This is a read-only control plane. All controls display current configuration values from proxy-config.toml. No changes can be made from this interface.
โšก HTTP/3 Features [http3] 8 settings + preloads โ–ผ
early_hints_enabled

Send a 103 Early Hints response so the browser starts fetching critical resources while the backend is still generating the page

priority_hints_enabled

Honour RFC 9218 extensible priorities (Priority header and PRIORITY_UPDATE frames) when scheduling streams

coalescing_enabled

Deduplicate identical in-flight requests โ€” concurrent requests for the same resource share one backend fetch

preconnect_origins

Origins advertised for preconnect in the 103 response

None configured
coalescing_max_wait_msms

Maximum time a coalesced request waits on the in-flight fetch before issuing its own

coalescing_max_subscriberssubscribers

Maximum requests that may attach to a single in-flight fetch

coalescing_methods

HTTP methods eligible for coalescing

None configured
coalescing_exclude_paths

Paths never coalesced โ€” APIs, WebSocket upgrades and streaming endpoints

None configured
Early Hints Preloads [[http3.preload_resources]] โ€” 20 entries

An href is only used by the browser if it byte-matches the URL in the page HTML, cache-buster included. Versioned entries are pinned to the file's current mtime and must be re-pinned when that file changes.

Host Path Match Preloaded href as
stlweb.dev / exact /css/style.css?v=1788490295 style
stlweb.dev / exact /js/main.js?v=1778453331 script
stlweb.dev /about.php prefix /css/style.css?v=1788490295 style
stlweb.dev /about.php prefix /js/main.js?v=1778453331 script
stlweb.dev /services.php prefix /css/style.css?v=1788490295 style
stlweb.dev /services.php prefix /js/main.js?v=1778453331 script
stlweb.dev /work.php prefix /css/style.css?v=1788490295 style
stlweb.dev /work.php prefix /js/main.js?v=1778453331 script
stlweb.dev /contact.php prefix /css/style.css?v=1788490295 style
stlweb.dev /contact.php prefix /js/main.js?v=1778453331 script
stlweb.dev /legal/ prefix /css/style.css?v=1788490295 style
stlweb.dev /legal/ prefix /js/main.js?v=1778453331 script
stlweb.dev /proposals/ofallonconcreteco.com/ prefix /proposals/ofallonconcreteco.com/css/style.css?v=1788489646 style
stlweb.dev /proposals/ofallonconcreteco.com/ prefix /proposals/ofallonconcreteco.com/js/head.js?v=1788405277 script
pqcrypta.com / exact /css/pqcrypta-loader.css?v=1767900410 style
pqcrypta.com / exact /css/homepage.css?v=1783392618 style
pqpdf.com / prefix /css/pdf.css?v=1779328120 style
pqpdf.com / prefix /css/site-nav.css?v=1780707281 style
fated.org / prefix /css/style.css?v=1788490295 style
fated.org / prefix /js/app.js script
๐Ÿ“ก OpenTelemetry Distributed Tracing [otel] section absent โ€” defaults โ–ผ

No [otel] table is present in the loaded config, so the proxy falls back to the compiled defaults shown below โ€” tracing export is off.

enablednot set · proxy default

Enable OTLP span export to tracing backend (Jaeger, Grafana Tempo, Honeycomb, etc.)

service_name

Service name shown in the tracing UI

otlp_endpoint

OTLP HTTP/JSON endpoint โ€” use your collector or tracing backend

sample_ratio

Sampling ratio: 1.0 = every trace, 0.1 = 10%, 0.0 = off. Uses ParentBased(TraceIdRatio).

๐Ÿ’พ Response Cache (RFC 9111) [cache] 7 settings โ–ผ
enabled

Enable RFC 9111 compliant response cache

max_size_mbMB

Maximum cache size in memory

default_ttl_secsseconds

Default TTL for cacheable responses without explicit Cache-Control

max_body_size_bytesbytes

Maximum response body size to cache

no_cache_set_cookie

Never cache responses that set cookies

excluded_paths

Paths excluded from caching

/api//ws/stream/auth/admin
excluded_hosts

Hosts whose responses are never cached, whatever the path

api.pqcrypta.comoffice.stlweb.dev
๐Ÿ”’This is a read-only control plane. All controls display current configuration values from proxy-config.toml. No changes can be made from this interface.
๐Ÿ“‹ Security Headers [headers] 21 settings โ–ผ
๐Ÿ”
Header / Setting Value
Strict-Transport-Security
hsts
max-age=63072000; includeSubDomains; preload
X-Frame-Options
x_frame_options
DENY
X-Content-Type-Options
x_content_type_options
nosniff
Referrer-Policy
referrer_policy
strict-origin-when-cross-origin
Permissions-Policy
permissions_policy
camera=(), microphone=(), geolocation=(), interest-cohort=(), fullscreen=(self), payment=()
Cross-Origin-Opener-Policy
cross_origin_opener_policy
same-origin
Cross-Origin-Embedder-Policy
cross_origin_embedder_policy
unsafe-none
Cross-Origin-Resource-Policy
cross_origin_resource_policy
same-origin
X-Permitted-Cross-Domain-Policies
x_permitted_cross_domain_policies
none
X-Download-Options
x_download_options
noopen
X-DNS-Prefetch-Control
x_dns_prefetch_control
off
X-Quantum-Resistant
x_quantum_resistant
ML-KEM-1024, ML-DSA-87, X25519MLKEM768
X-Security-Level
x_security_level
Post-Quantum Ready
HTTP/3 Priority
priority
u=3
Accept-CH
accept_ch
DPR, Viewport-Width, Width, ECT, RTT, Downlink, Sec-CH-UA-Platform, Sec-CH-UA-Mobile
NEL
nel
{"report_to":"default","max_age":86400,"include_subdomains":true}
Report-To
report_to
{"group":"default","max_age":86400,"endpoints":[{"url":"https://api.pqcrypta.com/reports"}]}
Outlook add-in path prefix
addin_path_prefix
/outlook/
Outlook add-in CSP
addin_csp
default-src 'self'; script-src 'self' 'unsafe-inline' https://appsforoffice.microsoft.com https://*.officeapps.live.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.pqpdf.com https://*.office.com https://*.officeapps.live.com; frame-ancestors https://outlook.office.com https://outlook.office365.com https://outlook.live.com https://*.office.com https://*.officeapps.live.com 'self'
Outlook add-in hosts
addin_hosts
pqpdf.com, www.pqpdf.com
These hosts do not receive X-Frame-Options on the add-in path โ€” Office embeds the task pane in a cross-origin iframe โ€” and get the add-in CSP instead.
Server-Timing
server_timing_enabled
Enabled
๐Ÿ”’This is a read-only control plane. All controls display current configuration values from proxy-config.toml. No changes can be made from this interface.
โš– Load Balancer [load_balancer] 10 settings โ–ผ
enabled

Enable load balancing (auto-enabled when backend_pools defined)

default_algorithm

Default load balancing algorithm for all pools

  • least_connections active
  • round_robin
  • weighted_round_robin
  • random
  • ip_hash
  • least_response_time
Session Affinity [load_balancer.session_affinity]
enabled

Enable sticky sessions (session affinity via cookie)

cookie_name

Cookie name for sticky session tracking

cookie_ttl_secsseconds

Cookie TTL in seconds (0 = session cookie)

cookie_secure

Use Secure cookies (HTTPS only)

cookie_httponly

Use HttpOnly cookies (prevent XSS access)

cookie_samesite

SameSite attribute for sticky session cookie

  • lax cross-site allowed
  • strict
  • none requires Secure
Queue [load_balancer.queue]
enabled

Enable request queue for when all backends are saturated

max_size

Maximum queued requests before returning 503

timeout_msms

Maximum wait time in queue before timeout

Slow Start [load_balancer.slow_start]
enabled

Gradually increase traffic to recovering backends

duration_secsseconds

Duration of slow start ramp-up period

initial_weight_percent%

Initial traffic weight during slow start

Connection Draining [load_balancer.connection_draining]
enabled

Enable graceful connection draining on backend removal

timeout_secsseconds

Maximum drain time before forceful close

pqcrypta-api HTTP1
address [loopback]
tls_mode terminate
timeout_ms 120,000 ms
max_connections 200
health_check /health
check_interval 30s
chatbot_timeout_ms 300000
apache HTTP1
address [loopback]
tls_mode terminate
timeout_ms 120,000 ms
max_connections 100
health_check /
check_interval 30s
prometheus HTTP1
address [loopback]
tls_mode terminate
timeout_ms 30,000 ms
max_connections 50
health_check /prometheus/-/healthy
check_interval 30s
grafana HTTP1
address [loopback]
tls_mode terminate
timeout_ms 30,000 ms
max_connections 50
health_check /grafana/api/health
check_interval 30s
pqpdf-api HTTP1
address [loopback]
tls_mode terminate
timeout_ms 180,000 ms
max_connections 200
health_check /v1/health
check_interval 30s
office-node HTTP1
address [loopback]
tls_mode terminate
timeout_ms 120,000 ms
max_connections 500
check_interval 30s
๐Ÿ”’This is a read-only control plane. All controls display current configuration values from proxy-config.toml. No changes can be made from this interface.
๐Ÿ—บ Route Definitions [[routes]] 60 routes โ–ผ
๐Ÿ”
60 routes
Name Host Path Backend Type Priority
api-chatbot api.pqcrypta.com prefix:/chatbot pqcrypta-api HTTP 5
Routing
backend:pqcrypta-api
type:http
priority:5
timeout_override_ms:300000
allow_http11:true
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
webtransport-encrypt api.pqcrypta.com prefix:/encrypt pqcrypta-api WebTransport 10
Routing
backend:pqcrypta-api
type:webtransport
priority:10
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
Add Headers [routes.add_headers]
X-Forwarded-Proto:https
X-WebTransport:true
webtransport-decrypt api.pqcrypta.com prefix:/decrypt pqcrypta-api WebTransport 10
Routing
backend:pqcrypta-api
type:webtransport
priority:10
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
webtransport-keys api.pqcrypta.com prefix:/keys pqcrypta-api WebTransport 10
Routing
backend:pqcrypta-api
type:webtransport
priority:10
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
webtransport-stream api.pqcrypta.com prefix:/stream pqcrypta-api WebTransport 10
Routing
backend:pqcrypta-api
type:webtransport
priority:10
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
webtransport-default api.pqcrypta.com prefix:/webtransport pqcrypta-api WebTransport 50
Routing
backend:pqcrypta-api
type:webtransport
priority:50
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
api-prometheus api.pqcrypta.com prefix:/prometheus prometheus HTTP 15
Routing
backend:prometheus
type:http
priority:15
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, OPTIONS
allow_headers:Content-Type, Authorization, X-API-Key
allow_credentials:true
max_age:86400s
api-default api.pqcrypta.com prefix:/ pqcrypta-api HTTP 100
Routing
backend:pqcrypta-api
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
CORS [routes.cors]
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
main-webtransport pqcrypta.com prefix:/webtransport pqcrypta-api WebTransport 5
Routing
backend:pqcrypta-api
type:webtransport
priority:5
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
main-webtransport-root pqcrypta.com exact:/ pqcrypta-api WebTransport 5
Routing
backend:pqcrypta-api
type:webtransport
priority:5
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
www-webtransport www.pqcrypta.com prefix:/webtransport pqcrypta-api WebTransport 5
Routing
backend:pqcrypta-api
type:webtransport
priority:5
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
seo-redirect-http3_quic pqcrypta.com prefix:/http3_quic โ†’ /http3-quic 301 REDIRECT 1
Routing
backend:โ€”
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/http3-quic
permanent:301 (permanent)
seo-redirect-pqc_ready pqcrypta.com prefix:/pqc_ready โ†’ /pqc-ready 301 REDIRECT 1
Routing
backend:โ€”
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/pqc-ready
permanent:301 (permanent)
seo-redirect-key_vault pqcrypta.com prefix:/key_vault โ†’ /key-vault 301 REDIRECT 1
Routing
backend:โ€”
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/key-vault
permanent:301 (permanent)
seo-redirect-security_systems pqcrypta.com prefix:/security_systems โ†’ /security-systems 301 REDIRECT 1
Routing
backend:โ€”
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/security-systems
permanent:301 (permanent)
seo-redirect-bot_threat_remediation pqcrypta.com prefix:/bot_threat_remediation โ†’ /bot-threat-remediation 301 REDIRECT 1
Routing
backend:โ€”
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/bot-threat-remediation
permanent:301 (permanent)
seo-redirect-threat_bot pqcrypta.com prefix:/threat_bot โ†’ /threat-bot 301 REDIRECT 1
Routing
backend:โ€”
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/threat-bot
permanent:301 (permanent)
speedtest-tcp pqcrypta.com regex:^/speedtest/tcp-(ping|download|upload)\.php apache HTTP 3
Routing
backend:apache
type:http
priority:3
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:no-store, no-cache, must-revalidate
X-Accel-Buffering:no
main-sitemaps pqcrypta.com regex:^/sitemaps/.*\.xml$ apache HTTP 5
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
X-Robots-Tag:noindex, follow
Cache-Control:public, max-age=3600
main-robots pqcrypta.com exact:/robots.txt apache HTTP 5
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
Cache-Control:public, max-age=3600
main-fonts pqcrypta.com regex:\.(woff|woff2|ttf|eot|otf)$ apache HTTP 5
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:public, max-age=31536000
Access-Control-Allow-Origin:*
main-audio pqcrypta.com regex:\.(mp3|ogg|wav|m4a|aac|flac)$ apache HTTP 5
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
Access-Control-Allow-Methods:GET, HEAD, POST, OPTIONS
Access-Control-Allow-Headers:Range, Content-Range, Content-Type, Accept, User-Agent, X-API-Key, signature-agent
Access-Control-Expose-Headers:Content-Range, Content-Length, Accept-Ranges
main-billing pqcrypta.com prefix:/dashboard/billing/ apache HTTP 5
Routing
backend:apache
type:http
priority:5
allow_http11:true
stripe_compatibility:true โ€” removes COEP/COOP headers
main-api-config pqcrypta.com exact:/api_config.php apache HTTP 5
Routing
backend:apache
type:http
priority:5
allow_http11:true
skip_bot_blocking:true
Security [routes.security]
skip_bot_blocking:yes โ€” scanner/bot user-agent checks bypassed for this route
main-prometheus pqcrypta.com prefix:/prometheus prometheus HTTP 3
Routing
backend:prometheus
type:http
priority:3
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
Headers Override [routes.headers_override]
cross_origin_embedder_policy:unsafe-none
cross_origin_opener_policy:unsafe-none
cross_origin_resource_policy:cross-origin
main-grafana pqcrypta.com prefix:/grafana grafana HTTP 3
Routing
backend:grafana
type:http
priority:3
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
Headers Override [routes.headers_override]
cross_origin_embedder_policy:unsafe-none
cross_origin_opener_policy:unsafe-none
cross_origin_resource_policy:cross-origin
main-default pqcrypta.com prefix:/ apache HTTP 100
Routing
backend:apache
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
www-default www.pqcrypta.com prefix:/ apache HTTP 100
Routing
backend:apache
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
api-robots api.pqcrypta.com exact:/robots.txt apache HTTP 1
Routing
backend:apache
type:http
priority:1
allow_http11:true
pqpdf-sitemaps pqpdf.com regex:^/sitemaps/.*\.xml$ apache HTTP 5
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
X-Robots-Tag:noindex, follow
Cache-Control:public, max-age=3600
pqpdf-sitemap pqpdf.com exact:/sitemap.xml apache HTTP 200
Routing
backend:apache
type:http
priority:200
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
Cache-Control:public, max-age=3600
pqpdf-robots pqpdf.com exact:/robots.txt apache HTTP 200
Routing
backend:apache
type:http
priority:200
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
Cache-Control:public, max-age=3600
pqpdf-webtransport pqpdf.com prefix:/webtransport apache WebTransport 3
Routing
backend:apache
type:webtransport
priority:3
stream_to_method:POST
pqpdf-default pqpdf.com prefix:/ apache HTTP 100
Routing
backend:apache
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
www-pqpdf-webtransport www.pqpdf.com prefix:/webtransport apache WebTransport 3
Routing
backend:apache
type:webtransport
priority:3
stream_to_method:POST
www-pqpdf-default www.pqpdf.com prefix:/ apache HTTP 100
Routing
backend:apache
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
fated-webtransport fated.org prefix:/webtransport apache WebTransport 3
Routing
backend:apache
type:webtransport
priority:3
stream_to_method:POST
fated-default fated.org prefix:/ apache HTTP 100
Routing
backend:apache
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
www-fated-webtransport www.fated.org prefix:/webtransport apache WebTransport 3
Routing
backend:apache
type:webtransport
priority:3
stream_to_method:POST
www-fated-default www.fated.org prefix:/ apache HTTP โ€”
Routing
backend:apache
type:http
priority:โ€”
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
pqpdf-api-default api.pqpdf.com prefix:/ pqpdf-api HTTP 100
Routing
backend:pqpdf-api
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
CORS [routes.cors]
allow_methods:GET, POST, DELETE, OPTIONS
allow_headers:Content-Type, X-API-Key, X-Session-Id, Authorization
allow_credentials:true
max_age:86400s
stlweb-webtransport stlweb.dev prefix:/webtransport apache WebTransport 3
Routing
backend:apache
type:webtransport
priority:3
stream_to_method:POST
stlweb-robots stlweb.dev exact:/robots.txt apache HTTP 5
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
Cache-Control:public, max-age=3600
stlweb-sitemap stlweb.dev exact:/sitemap.xml apache HTTP 5
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
Cache-Control:public, max-age=3600
stlweb-fonts stlweb.dev regex:\.(woff|woff2|ttf|eot|otf)$ apache HTTP 5
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:public, max-age=31536000
Access-Control-Allow-Origin:*
stlweb-css stlweb.dev regex:\.css$ apache HTTP 4
Routing
backend:apache
type:http
priority:4
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:public, max-age=2592000
stlweb-js stlweb.dev regex:\.js$ apache HTTP 4
Routing
backend:apache
type:http
priority:4
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:public, max-age=2592000
stlweb-static stlweb.dev regex:\.(webp|avif|jpg|jpeg|png|gif|ico|svg)$ apache HTTP 5
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:public, max-age=2592000
stlweb-contact-submit stlweb.dev exact:/contact/submit.php apache HTTP 3
Routing
backend:apache
type:http
priority:3
allow_http11:true
skip_bot_blocking:true
Security [routes.security]
skip_bot_blocking:yes โ€” scanner/bot user-agent checks bypassed for this route
www-stlweb-default www.stlweb.dev prefix:/ apache HTTP 100
Routing
backend:apache
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
stlweb-default stlweb.dev prefix:/ apache HTTP 100
Routing
backend:apache
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
office-wt-speedtest office.stlweb.dev exact:/speedtest apache WebTransport 1
Routing
backend:apache
type:webtransport
priority:1
stream_to_method:โ€”
office-wt-telemetry office.stlweb.dev exact:/telemetry apache WebTransport 1
Routing
backend:apache
type:webtransport
priority:1
stream_to_method:โ€”
office-wt-root office.stlweb.dev prefix:/webtransport apache WebTransport 2
Routing
backend:apache
type:webtransport
priority:2
stream_to_method:โ€”
office-assets office.stlweb.dev prefix:/assets/ apache HTTP 20
Routing
backend:apache
type:http
priority:20
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:public, max-age=31536000, immutable
Cross-Origin-Resource-Policy:cross-origin
office-login office.stlweb.dev exact:/api/method/login apache HTTP 15
Routing
backend:apache
type:http
priority:15
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:no-store
Cross-Origin-Resource-Policy:cross-origin
Cross-Origin-Opener-Policy:unsafe-none
office-api office.stlweb.dev prefix:/api/ apache HTTP 20
Routing
backend:apache
type:http
priority:20
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:no-store
Cross-Origin-Resource-Policy:cross-origin
Cross-Origin-Opener-Policy:unsafe-none
office-socketio office.stlweb.dev prefix:/socket.io/ office-node HTTP 20
Routing
backend:office-node
type:http
priority:20
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:no-store
office-files office.stlweb.dev prefix:/files/ apache HTTP 20
Routing
backend:apache
type:http
priority:20
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:no-store, private
office-app office.stlweb.dev prefix:/ apache HTTP 100
Routing
backend:apache
type:http
priority:100
allow_http11:true
Headers Override [routes.headers_override]
X-Frame-Options:SAMEORIGIN
Cache-Control:no-store, no-cache
Cross-Origin-Opener-Policy:unsafe-none
Cross-Origin-Resource-Policy:cross-origin
๐Ÿ”’This is a read-only control plane. All controls display current configuration values from proxy-config.toml. No changes can be made from this interface.
๐Ÿ“ Logging Configuration [logging] 4 settings โ–ผ
level

Log verbosity level

  • trace very verbose
  • debug
  • info active
  • warn
  • error errors only
format

Log output format

  • json structured logging
  • text human readable
access_log

Enable access logging for all requests

access_log_file

Access log file path (empty/commented = stdout)

๐Ÿ”ง Admin API [admin] 6 settings โ–ผ
enabled

Enable admin HTTP API for runtime management

bind_address

Admin API bind address โ€” 127.0.0.1 for local-only access

portport

Admin API listening port

require_mtls

Require mutual TLS for admin API connections

auth_token

Bearer token for admin API authentication (AUD-01)

โ—โ—โ—โ—โ—โ—โ—โ—โ—โ—โ—โ—โ—โ—โ—โ—โ—โ—โ—โ— [REDACTED]
allowed_ips

Allowed IP addresses for admin API access

[REDACTED]
๐Ÿ”’This is a read-only control plane. All controls display current configuration values from proxy-config.toml. No changes can be made from this interface.
๐Ÿ“„ proxy-config.toml
# GENERATED โ€” do not edit.
# Redacted snapshot of /etc/pqcrypta/proxy-config.toml, published for /proxy-plane/,
# which runs as www-data and cannot read the live file (0640 root:root).
# Policy: includes/proxy-config-redact.php  Publisher: scripts/sanitize-proxy-config.php
# Generated: 2026-09-07 07:17:01 UTC

# PQCrypta Proxy Configuration - Production
# ==========================================
# Complete configuration with all available options.
# Handles all HTTP/HTTPS/HTTP3/WebTransport traffic on ports 443, 4433, 4434

# =============================================================================
# Server Configuration
# =============================================================================
[server]
# Bind address for QUIC/UDP and TCP listeners.
# "[::]" is dual-stack: with net.ipv6.bindv6only=0 it accepts BOTH IPv6 and
# IPv4 (as v4-mapped). Brackets are required โ€” the listeners build addresses
# as format!("{bind_address}:{port}"), so a bare "::" would yield the
# unparseable ":::443".
# Previously "[redacted-ip]" (IPv4 only) while DNS published an AAAA record, so
# IPv6-preferring clients hit a refused port: TCP fell back to IPv4 via Happy
# Eyeballs (HTTP still worked) but QUIC/UDP simply failed, which is why
# external HTTP/3 checkers reported "QUIC connection could not be established".
bind_address = "[::]"

# Primary UDP port for QUIC/HTTP3/WebTransport
udp_port = 443

# Additional ports to listen on (all support QUIC/HTTP3/WebTransport)
additional_ports = ["[configured]"]

# Allowed WebTransport origins (browser Origin header validation)
# Empty list = reject all browser connections. Must list every domain that
# opens a WebTransport session from a browser context.
webtransport_allowed_origins = ["https://pqcrypta.com", "https://api.pqcrypta.com", "https://pqpdf.com", "https://www.pqpdf.com", "https://stlweb.dev", "https://office.stlweb.dev", "https://fated.org", "https://www.fated.org"]

# Hosts that must never advertise HTTP/3 upgrade.
# Responses to these hosts include "Alt-Svc: clear" to evict any cached QUIC
# upgrade in the browser, ensuring TCP/TLS is always used.
tcp_only_hosts = ["tcp.pqcrypta.com"]

# Hosts that must negotiate HTTP/1.1 only (no h2 in ALPN).
# Prevents browser HTTP/2 connection coalescing so each parallel fetch() stream
# opens an independent TCP connection. Required for accurate TCP speed tests โ€”
# all 6 streams would otherwise share one HTTP/2 pipe and stall together.
http11_only_hosts = ["tcp.pqcrypta.com", "api.pqpdf.com"]

# Maximum concurrent connections
max_connections = 10000

# Maximum concurrent BIDIRECTIONAL streams per connection (HTTP/3 requests)
max_streams_per_connection = 1000

# Maximum concurrent UNIDIRECTIONAL streams per connection. HTTP/3 needs exactly
# three (control + the two QPACK streams); WebTransport sessions open a few more.
# This used to inherit the bidirectional figure, advertising 1000.
max_uni_streams_per_connection = 100

# Keep-alive interval in seconds
keepalive_interval_secs = 15

# Maximum idle timeout in seconds
max_idle_timeout_secs = 120

# Enable IPv6 dual-stack binding
enable_ipv6 = true

# Worker threads (0 = auto-detect based on CPU cores)
worker_threads = 0

# Enable the QUIC ACK Frequency extension (draft-ietf-quic-ack-frequency).
# Fewer, batched ACKs cut overhead on high-throughput connections (speedtest,
# bulk transfer). Negotiated, so clients that lack it are unaffected.
enable_ack_frequency = true

# Enable QUIC Retry for explicit source-address validation (RFC 9000 ยง8.1.2).
# A new, unvalidated connection is answered with a Retry token the client must
# echo before the handshake proceeds - hardening against spoofed-source
# amplification/DDoS at the cost of one extra round trip per new connection.
# When false, the server uses RFC 9000 implicit validation (the 3x
# anti-amplification limit) with no per-connection latency.
enable_quic_retry = true

# Maximum concurrent data-carrying paths per connection
# (draft-ietf-quic-multipath), served by the noq QUIC stack. Paths are created,
# validated and torn down automatically once the peer negotiates multipath;
# peers that do not advertise the extension are unaffected by this value.
# 1 keeps every connection single-path, 0 stops multipath being negotiated.
max_concurrent_multipath_paths = 4

# Disable proxy-level path lowercasing โ€” stlweb.dev Apache handles its own
# lowercase enforcement via mod_rewrite 301; ERPNext uses case-sensitive hashes
normalize_paths = false

# =============================================================================
# MASQUE / CONNECT-UDP (RFC 9298)
# =============================================================================
# UDP proxying over HTTP/3 Extended CONNECT. Disabled by default; an open UDP
# relay is an amplification/SSRF risk, so targets must be explicitly allowlisted.
#
# A public RFC 9298 relay, documented at https://pqcrypta.com/masque/. There is
# effectively no free public MASQUE target on the internet, so people writing
# CONNECT-UDP clients have nothing to test against; this is that target.
#
# The allowlist is the entire safety model, and it is why this can be public at
# all: a caller cannot aim the relay at a victim of their choosing, because the
# destination has to appear below. Entries are pinned to well-known public
# resolvers on their DNS ports only. Never add "*" to either half of an entry.
[masque]
enabled = true
allowed_targets = [
    # Local stub resolver (systemd-resolved) โ€” the original loopback test target,
    # still used by the HTTP/3 scanner's DNS-over-CONNECT-UDP round trip check.
    "[loopback]:53",
    # Cloudflare: Do53 and DNS-over-QUIC (RFC 9250).
    "[redacted-ip]:53",
    "[redacted-ip]:53",
    "[redacted-ip]:853",
    # Google Public DNS.
    "[redacted-ip]:53",
    "[redacted-ip]:53",
    # Quad9: Do53 and DNS-over-QUIC.
    "[redacted-ip]:53",
    "[redacted-ip]:853",
]
session_idle_timeout_secs = 60
max_sessions_per_connection = 8

# =============================================================================
# HTTP/3 + QUIC client conformance suite
# =============================================================================
# Serves deliberately awkward-but-legal protocol output so a client library can
# find out how it copes: reserved frame types, a duplicated SETTINGS identifier,
# a black-holed path MTU. The server records what the client did and scores it.
#
# Every test owns one UDP port from `port_range`; the listener knows which test
# it is serving from its own local_addr(). `host` serves the catalogue, the
# reports and the badge over ordinary HTTPS โ€” it does not select tests.
#
# `port_range` must not overlap server.udp_port (443) or additional_ports
# (4434); startup refuses rather than warns, because an overlap would hand
# ordinary visitors malformed protocol output. It must also be wide enough for
# the whole catalogue, or the suite would silently serve a truncated one.
[conformance]
enabled = true
host = "conformance.pqcrypta.com"
port_range = [4460, 4600]
session_ttl_secs = 3600
max_sessions = 512
liveness_timeout_ms = 5000

# =============================================================================
# TLS Configuration
# =============================================================================
[tls]
# Path to TLS certificate chain (PEM format)
cert_path = "/[system]/[key-material]"

# Path to TLS private key (PEM format)
key_path = "[REDACTED]"

# Optional: CA certificate for client verification (mTLS)
# ca_cert_path = "/[system]/[key-material]"

# Require client certificates (mTLS mode)
require_client_cert = false

# ALPN protocols to advertise
alpn_protocols = ["h3", "h2", "http/1.1", "webtransport"]

# Minimum TLS version (only "1.3" supported for QUIC)
min_version = "1.3"

# Enable OCSP stapling
ocsp_stapling = true

# Certificate reload interval in seconds (0 = disabled)
cert_reload_interval_secs = 3600

# Issue TLS 1.3 session tickets sealed with ML-KEM-1024 (FIPS 203). Each ticket
# carries its own encapsulation; the keypair rolls every
# session_ticket_lifetime_secs with one generation of overlap.
pqc_session_tickets = true
session_ticket_lifetime_secs = 43200

# Enable 0-RTT (early data) for faster reconnections
# SECURITY WARNING: 0-RTT is vulnerable to replay attacks!
enable_0rtt = false

# =============================================================================
# OCSP Stapling Configuration
# =============================================================================
[ocsp]
# Enable OCSP stapling service
enabled = true

# Cache duration for OCSP responses (seconds)
cache_duration_secs = 3600

# Refresh OCSP response before expiry (seconds)
refresh_before_expiry_secs = 300

# OCSP request timeout (seconds)
timeout_secs = 10

# Maximum retries for OCSP requests
max_retries = 3

# Retry delay between attempts (milliseconds)
retry_delay_ms = 1000

# =============================================================================
# ACME Certificate Automation (Let's Encrypt)
# =============================================================================
[acme]
# Enable ACME certificate automation
enabled = true

# Domains to manage certificates for
domains = ["pqcrypta.com", "www.pqcrypta.com", "api.pqcrypta.com", "pqpdf.com", "www.pqpdf.com", "api.pqpdf.com", "fated.org", "www.fated.org", "tcp.pqcrypta.com", "stlweb.dev", "www.stlweb.dev", "office.stlweb.dev", "conformance.pqcrypta.com"]

# Contact email for Let's Encrypt notifications
email = "[email withheld]"

# ACME directory URL (Let's Encrypt production)
directory_url = "https://acme-v02.api.letsencrypt.org/directory"

# Challenge type: "http-01" or "dns-01"
challenge_type = "http-01"

# Directory to store certificates
certs_path = "/[system]/certs"

use_ecdsa = true
# P-384 = secp384r1 = 192-bit = 7680-bit RSA equivalent = 100% SSL Labs key exchange score
# P-256 = secp256r1 = 128-bit = 3072-bit RSA equivalent = 90% SSL Labs key exchange score
ecdsa_curve = "P-384"

# Renew certificates this many days before expiry
renewal_days = 30

# Check interval for certificate expiry (hours)
check_interval_hours = 12

# HTTP port for HTTP-01 challenge (must be 80)
http_port = 80

# =============================================================================
# Post-Quantum Cryptography
# =============================================================================
[pqc]
# Enable PQC hybrid key exchange
enabled = true

# PQC provider: "openssl3.5" (requires OpenSSL 3.5+ with native ML-KEM)
provider = "openssl3.5"

# Path to OpenSSL 3.5 binary
openssl_path = "/[system]/openssl-pq/..."

# OpenSSL library path
openssl_lib_path = "/[system]/openssl-pq/..."

# Preferred KEM algorithm for key exchange (FIPS 203 / IETF standard names).
# Options: X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, X448MLKEM1024,
#          ML-KEM-512, ML-KEM-768, ML-KEM-1024
# Legacy pre-NIST Kyber names require the `legacy-pqc` build feature (off by default)
# and are NOT interoperable with ML-KEM peers โ€” do not use in new deployments.
preferred_kem = "X25519MLKEM768"

# Fallback to classical TLS if PQC is unavailable
fallback_to_classical = true

# Refuse to start if the TLS private key is group- or world-readable, rather
# than warning and serving anyway. All keys under certs_path are 0600 root:root
# and ACME writes them that way, so this changes nothing today - it exists so a
# key that BECOMES readable is caught at startup instead of in the journal.
strict_key_permissions = "[REDACTED]"

# =============================================================================
# Admin API Configuration
# =============================================================================
[admin]
# Enable admin HTTP API
enabled = true

# Admin API bind address (use [loopback] for local-only access)
bind_address = "[loopback]"

# Admin API port
port = [configured]

# Require mTLS for admin API
require_mtls = false

# Bearer token for admin API authentication (persistent โ€” prevents cooldown spam on restart)
auth_token = "[REDACTED]"

# Allowed IP addresses for admin API access
allowed_ips = ["[REDACTED]"]

# =============================================================================
# Logging Configuration
# =============================================================================
[logging]
# Log level: trace, debug, info, warn, error
level = "info"

# Log format: "json" or "text"
format = "json"

# Log file path (empty = stdout)
# file = "/[logs]/..."

# Enable access logs
access_log = true

# Access log file path
access_log_file = "/[logs]/..."

# =============================================================================
# Rate Limiting (Basic)
# =============================================================================
[rate_limiting]
# Enable basic rate limiting
enabled = true

# Requests per second per IP
requests_per_second = [configured]

# Burst size for rate limiter
burst_size = [configured]

# Enable connection rate limiting
connection_rate_limit = true

# New connections per second per IP.
#
# Was 10, which a single page load can exceed on its own: a browser (or a
# rendering crawler) opens a connection per subresource, and exceeding this
# calls block_ip() for 300s on the FIRST offence, with no counter to cross
# first. That is what banned Googlebot on every Search Console render and
# produced 269 rate-limit events in 24h against ordinary visitors.
#
# 50 matches web2.pqcrypta.com (philibertsecurity.com), which has run at that
# value with no Googlebot ban in 14 days of logs and no abuse problem โ€” the
# scanners it does block are caught by the 4xx error-rate rule instead, which
# is the check actually suited to that job.
connections_per_second = [configured]

# =============================================================================
# Advanced Multi-Dimensional Rate Limiting
# =============================================================================
# Features: composite keys, JA3 fingerprinting, X-Forwarded-For trust, adaptive ML

[advanced_rate_limiting]
# Enable advanced rate limiting (overrides basic when enabled)
enabled = true

# IPv6 subnet grouping (64 = /64 subnets treated as single client)
ipv6_subnet_bits = 64

# Trusted proxies for X-Forwarded-For parsing (CIDR or IP)
trusted_proxies = ["[REDACTED]"]

# Key resolution strategy
[advanced_rate_limiting.key_strategy]
# Priority order for key resolution (first found wins)
order = ["api_key", "jwt_subject", "ja3_fingerprint", "real_ip", "source_ip"]
fallback = "source_ip"
use_composite = false

# Header names for key extraction
[advanced_rate_limiting.headers]
api_key = "X-API-Key"
user_id = "X-User-ID"
tenant_id = "X-Tenant-ID"
real_ip = "X-Real-IP"

# Global limits (DDoS protection layer)
[advanced_rate_limiting.global_limits]
requests_per_second = [configured]
burst_size = [configured]

[advanced_rate_limiting.global_limits.per_ip]
requests_per_second = [configured]
burst_size = [configured]
requests_per_minute = [configured]
requests_per_hour = [configured]

[advanced_rate_limiting.global_limits.per_fingerprint]
requests_per_second = [configured]
burst_size = [configured]
requests_per_minute = [configured]
requests_per_hour = [configured]

[advanced_rate_limiting.global_limits.per_api_key]
requests_per_second = [configured]
burst_size = [configured]
requests_per_minute = [configured]
requests_per_hour = [configured]

[advanced_rate_limiting.global_limits.per_composite]
requests_per_second = [configured]
burst_size = [configured]
requests_per_minute = [configured]
requests_per_hour = [configured]

# Distributed rate limiting via Redis (all proxy instances share counters)
[advanced_rate_limiting.redis]
url = "redis://[loopback]"
key_prefix = "[REDACTED]"
connect_timeout_ms = 2000
command_timeout_ms = 50
distribute_per_second = true

# JA3/JA4 fingerprint-based limiting (NAT-friendly)
[advanced_rate_limiting.fingerprint_limiting]
enabled = true
prefer_over_ip = false
blocked_fingerprints = []

[advanced_rate_limiting.fingerprint_limiting.unknown_limits]
requests_per_second = [configured]
burst_size = [configured]
requests_per_minute = [configured]
requests_per_hour = [configured]

# Adaptive rate limiting (ML-inspired anomaly detection)
[advanced_rate_limiting.adaptive]
enabled = false
baseline_window_secs = 3600
sensitivity = [configured]
auto_adjust = false
min_samples = 1000
std_dev_multiplier = [configured]

# Per-route rate limits โ€” keyed by route name.
# Applied as a secondary check inside the proxy handler after route matching,
# in addition to the global per-IP and per-fingerprint limits above.
[advanced_rate_limiting.route_limits.office-login]
pattern = "/api/method/login"

[advanced_rate_limiting.route_limits.office-login.limits]
requests_per_second = [configured]
burst_size = [configured]
requests_per_minute = [configured]
requests_per_hour = [configured]

# =============================================================================
# Security Settings
# =============================================================================
[security]
# Maximum request body size in bytes (200MB - matches MAX_TOTAL_SIZE in pdf/api.php)
max_request_size = 209715200

# Maximum header size in bytes (64KB)
max_header_size = 65536

# Connection timeout in seconds
connection_timeout_secs = 30

# Enable DoS protection
dos_protection = true

# Blocked IP addresses
blocked_ips = ["[REDACTED]"]

# Allowed IP addresses (whitelist mode - empty = allow all)
allowed_ips = ["[REDACTED]"]

# GeoIP database path for country blocking (optional)
geoip_db_path = "/[data]/..."

# Blocked country codes (ISO 3166-1 alpha-2)
blocked_countries = ["CN", "RU", "KP", "IR", "NZ"]

# How long a GeoIP block lasts, in seconds. 0 = never expires.
# Set to 0 deliberately: a geo-blocked address stays on the blocklist rather
# than ageing out after a day. Applies to every entry in blocked_countries.
geoip_block_duration_secs = 0

# Maximum connections per IP
max_connections_per_ip = [configured]

# Error-based blocking thresholds (prevents false positives from scanners)
error_4xx_threshold = [configured]          # 4xx errors before checking rate
min_requests_for_error_check = [configured] # Minimum requests before error check
error_rate_threshold = [configured]         # 70% error rate triggers suspicious pattern
error_window_secs = 60             # Sliding window duration (seconds)
auto_block_threshold = [configured]          # Suspicious patterns before auto-block
auto_block_duration_secs = [configured]     # Block duration (seconds)

# Authorized pentest IPs โ€” skip rate-limiting and auto-block but still WAF-checked.
# Each attack still returns 403; IP is never auto-banned mid-run.
# REMOVE after pentest engagement ends.
# IPv6 matters here: this box egresses over IPv6 when it is available, so an
# IPv4-only list exempted none of its own requests. Matching is by parsed
# address (see config::ip_list_contains), so any valid spelling works.
pentest_bypass_ips = ["[REDACTED]"]
# [redacted-ip] + [loopback] are this server's own egress/loopback: server-side
# jobs (test suite, news refresh, wizard, health tooling) must never auto-block
# the box's own API access. Still WAF-inspected; only rate/error auto-ban is skipped.

# =============================================================================
# Web Application Firewall (WAF)
# =============================================================================
[waf]
# Enable WAF โ€” injection, traversal, inclusion, scanner probes, request anomalies
enabled = true
# "block" rejects the request with 403; "detect" logs only (for tuning)
mode = "block"
# Anomaly score at which a request is blocked. Severities contribute
# Low 3, Medium 5, High 8, Critical 10. At 5 any single Medium-or-higher rule
# blocks on its own, while two Low-severity signals must agree. Raise it on a
# route that produces false positives; lower it to act on single weak signals.
anomaly_threshold = 5
# OWASP A03: SQL injection pattern matching
sqli = true
# OWASP A03: Cross-site scripting pattern matching
xss = true
# OWASP A01/A05: Path traversal and path confusion (../, ..;/, overlong UTF-8)
path_traversal = true
# OWASP A03: NoSQL injection detection ($where, $gt operator abuse, etc.)
nosqli = true
# OWASP A10: SSRF detection. Metadata IPs are Critical; loopback spellings are
# Low and need corroboration, so a URL field naming localhost no longer 403s alone.
ssrf = true
# OWASP A03: OS command injection (shell metacharacters, ${IFS}, reverse shells)
cmd_injection = true
# OWASP A08: XML external entity injection
xxe = true
# OWASP A08: insecure deserialization (Java/PHP/Python, incl. byte signatures)
deserialization = true
# OWASP A06: JNDI/Log4Shell and expression-language injection (OGNL, SpEL)
jndi = true
# OWASP A03: server-side template injection (Jinja, Twig, ERB)
ssti = true
# Local/remote file inclusion via URL stream wrappers (php://, expect://, phar://)
file_inclusion = true
# CRLF injection / HTTP response splitting. Applied to URL and headers only โ€”
# request bodies use CRLF line endings legitimately.
crlf_injection = true
# JavaScript prototype pollution (__proto__, constructor.prototype)
proto_pollution = true
# GraphQL schema introspection โ€” off, legitimate for a public schema
graphql = false
# Structural anomalies: request smuggling (CL+TE, conflicting Content-Length),
# control characters in headers, diagnostic methods, malformed Host
request_anomaly = true
# Block known scanner/reconnaissance probe paths (.git, .env, wp-login, etc.)
scanner_probe = true
# Block known malicious scanner/bot user-agents (sqlmap, nikto, masscan, curl, etc.)
block_scanner_uas = true
# Scan request bodies, decoding JSON string escapes
scan_json_body = true
# Maximum body bytes to scan (64KB โ€” covers all form submissions)
max_body_scan_bytes = 65536
# Scan every request header except content negotiation, cache validators,
# client hints and credentials
scan_all_headers = true
# Maximum bytes of any single header value to scan
max_header_scan_bytes = 8192
# Header count above which a request is flagged as anomalous
max_header_count = 80
# Percent-decode passes before matching. One pass is a bypass: %253Cscript%253E
# decodes once to %3Cscript%3E, matches nothing, and the origin decodes it again.
max_decode_passes = 3
# Custom extra regex patterns โ€” empty, built-in ruleset covers OWASP Top 10
custom_patterns = []

# Per-path rule exclusions, for tuning a false positive without switching a
# category off site-wide. `path` is a regex; name rule identifiers in `rules`
# and/or whole categories in `categories`.
# [[waf.exclusions]]
# path = "^/regex/"
# categories = ["xss"]
# =============================================================================
# TLS Fingerprint Detection (JA3/JA4)
# =============================================================================
[fingerprint]
# Enable TLS fingerprint detection middleware
enabled = true

# Use TLS-layer capture with custom accept loop (captures raw ClientHello)
# When enabled, uses FingerprintingTlsAcceptor for full JA3/JA4 capture
# before TLS handshake, allowing early blocking of malicious clients.
tls_layer_capture = true

# Block duration for malicious fingerprints (seconds)
malicious_block_duration_secs = [configured]   # 1 hour

# Block duration for suspicious fingerprints with high request rate (seconds)
suspicious_block_duration_secs = [configured]   # 5 minutes

# Request count threshold to trigger suspicious fingerprint rate check
suspicious_rate_threshold = [configured]

# Time window for suspicious rate detection (seconds)
suspicious_rate_window_secs = 60       # 1 minute

# Fingerprint cache max age before cleanup (seconds)
cache_max_age_secs = 3600              # 1 hour

# Block security scanner fingerprints (Nmap, Nikto, Burp Suite, etc.)
# When false, scanners are logged but not blocked
block_scanners = true

# Add fingerprint info headers to responses (x-client-fingerprint, x-client-type)
# Enable for debugging/monitoring, disable in production for security
add_response_headers = false

# =============================================================================
# Circuit Breaker Configuration
# =============================================================================
[circuit_breaker]
# Enable circuit breaker for backend protection
enabled = true

# Time before circuit breaker transitions from Open to Half-Open (seconds)
half_open_delay_secs = 30

# Maximum test requests allowed in Half-Open state
half_open_max_requests = 3

# Failure threshold to open the circuit (only 5xx and timeouts count as failures)
# High threshold to prevent health check cron from tripping circuit breaker
# during intensive crypto endpoint testing (keygen + encrypt + decrypt per algo)
failure_threshold = [configured]

# Success threshold to close the circuit from Half-Open
success_threshold = 2

# Stale request counter cleanup interval (seconds)
stale_counter_cleanup_secs = 300       # 5 minutes

# =============================================================================
# HTTP Connection Pool Configuration
# =============================================================================
[connection_pool]
# Pool idle timeout - how long idle connections stay in pool (seconds)
idle_timeout_secs = 90

# Maximum idle connections per backend host
max_idle_per_host = 100

# Maximum total connections per backend host
max_connections_per_host = 100

# Connection acquire timeout (milliseconds)
acquire_timeout_ms = 30000             # 30 seconds

# =============================================================================
# HTTP/3 Advanced Features Configuration
# =============================================================================
[http3]
# Enable Early Hints (103 status code) - preload critical resources
early_hints_enabled = true

# Enable Priority Hints (RFC 9218) - resource scheduling optimization
priority_hints_enabled = true

# Enable Request Coalescing - deduplicate identical in-flight requests
coalescing_enabled = true

# Default preconnect origins for Early Hints (none โ€” stlweb.dev self-hosts all assets)
preconnect_origins = []

# Resources to preload via 103 Early Hints, scoped per host.
# NOTE: an href is only USED by the browser if it byte-matches the URL in the
# page HTML, including any ?v= cache-buster. Versioned files below are pinned to
# their current filemtime โ€” re-pin these if the underlying file changes.
#
# stlweb.dev: the main site's stylesheet (loaded in _head.php) and script (loaded
# in _footer.php). "/" MUST be exact here โ€” every proposal under /proposals/<name>/
# ships its own css/style.css and js/main.js, so a "/" prefix pushed the main
# site's two files on every proposal page, where they are never used. That tripped
# the browser's "preloaded but not used" warning on all six proposals. Interior
# pages of the main site are listed individually below so they keep the preload.
[[http3.preload_resources]]
host = "stlweb.dev"
path = "/"
exact = true
href = "/css/style.css?v=1788490295"
as_type = "style"

[[http3.preload_resources]]
host = "stlweb.dev"
path = "/"
exact = true
href = "/js/main.js?v=1778453331"
as_type = "script"

[[http3.preload_resources]]
host = "stlweb.dev"
path = "/about.php"
href = "/css/style.css?v=1788490295"
as_type = "style"

[[http3.preload_resources]]
host = "stlweb.dev"
path = "/about.php"
href = "/js/main.js?v=1778453331"
as_type = "script"

[[http3.preload_resources]]
host = "stlweb.dev"
path = "/services.php"
href = "/css/style.css?v=1788490295"
as_type = "style"

[[http3.preload_resources]]
host = "stlweb.dev"
path = "/services.php"
href = "/js/main.js?v=1778453331"
as_type = "script"

[[http3.preload_resources]]
host = "stlweb.dev"
path = "/work.php"
href = "/css/style.css?v=1788490295"
as_type = "style"

[[http3.preload_resources]]
host = "stlweb.dev"
path = "/work.php"
href = "/js/main.js?v=1778453331"
as_type = "script"

[[http3.preload_resources]]
host = "stlweb.dev"
path = "/contact.php"
href = "/css/style.css?v=1788490295"
as_type = "style"

[[http3.preload_resources]]
host = "stlweb.dev"
path = "/contact.php"
href = "/js/main.js?v=1778453331"
as_type = "script"

[[http3.preload_resources]]
host = "stlweb.dev"
path = "/legal/"
href = "/css/style.css?v=1788490295"
as_type = "style"

[[http3.preload_resources]]
host = "stlweb.dev"
path = "/legal/"
href = "/js/main.js?v=1778453331"
as_type = "script"

# O'Fallon Concrete proposal: its own render-blocking assets. The hrefs carry the
# same filemtime version that asset() emits in _head.php โ€” re-pin both whenever
# style.css or head.js changes, or the hint pushes a URL the page never requests.
# A prefix is correct
# here โ€” all 30 routes under this path share the same stylesheet and head script.
[[http3.preload_resources]]
host = "stlweb.dev"
path = "/proposals/ofallonconcreteco.com/"
href = "/proposals/ofallonconcreteco.com/css/style.css?v=1788489646"
as_type = "style"

[[http3.preload_resources]]
host = "stlweb.dev"
path = "/proposals/ofallonconcreteco.com/"
href = "/proposals/ofallonconcreteco.com/js/head.js?v=1788405277"
as_type = "script"

# pqcrypta.com: homepage-only render-blocking stylesheets (versions are
# filemtime-stable). These two files are referenced only by the homepage
# (index.php), so match "/" EXACTLY โ€” otherwise the "/" prefix preloads them on
# every page (e.g. /http3-quic/, which uses a different stylesheet), wasting the
# push and tripping the browser's "preloaded but not used" warning.
[[http3.preload_resources]]
host = "pqcrypta.com"
path = "/"
exact = true
href = "/css/pqcrypta-loader.css?v=1767900410"
as_type = "style"

[[http3.preload_resources]]
host = "pqcrypta.com"
path = "/"
exact = true
href = "/css/homepage.css?v=1783392618"
as_type = "style"

# pqpdf.com: main stylesheet + site nav (filemtime-versioned โ€” re-pin if changed).
[[http3.preload_resources]]
host = "pqpdf.com"
path = "/"
href = "/css/pdf.css?v=1779328120"
as_type = "style"

[[http3.preload_resources]]
host = "pqpdf.com"
path = "/"
href = "/css/site-nav.css?v=1780707281"
as_type = "style"

# fated.org: stylesheet + app script (unversioned, always matches).
[[http3.preload_resources]]
host = "fated.org"
path = "/"
href = "/css/style.css?v=1788490295"
as_type = "style"

[[http3.preload_resources]]
host = "fated.org"
path = "/"
href = "/js/app.js"
as_type = "script"

# Maximum wait time for coalesced requests (milliseconds)
coalescing_max_wait_ms = 100

# Maximum subscribers per coalesced request
coalescing_max_subscribers = 100

# HTTP methods to coalesce (typically only GET and HEAD)
coalescing_methods = ["GET", "HEAD"]

# Paths to exclude from coalescing (API endpoints, WebSocket, streams)
coalescing_exclude_paths = ["/api/", "/ws", "/stream", "/speedtest/tcp-"]

# =============================================================================
# Response Cache (RFC 9111)
# =============================================================================
[cache]
enabled = true
max_size_mb = 128
default_ttl_secs = 60
max_body_size_bytes = 2097152
excluded_paths = ["/api/", "/ws", "/stream", "/auth", "/admin"]
excluded_hosts = ["api.pqcrypta.com", "office.stlweb.dev"]
no_cache_set_cookie = true

# =============================================================================
# Security Headers Configuration
# =============================================================================
[headers]
# HTTP Strict Transport Security (2 years, preload-ready)
hsts = "max-age=63072000; includeSubDomains; preload"

# Prevent clickjacking
x_frame_options = "DENY"

# Prevent MIME-type sniffing
x_content_type_options = "nosniff"

# Control referrer information
referrer_policy = "strict-origin-when-cross-origin"

# Disable browser features (FLoC, camera, microphone, geolocation)
permissions_policy = "camera=(), microphone=(), geolocation=(), interest-cohort=(), fullscreen=(self), payment=()"

# Cross-origin isolation headers
# COOP/CORP set to safe defaults; per-route headers_override takes precedence (proxy
# injects these conditionally โ€” skips if the route handler already set the header).
# Grafana/Prometheus/Frappe routes override to "unsafe-none" / "cross-origin" as needed.
cross_origin_opener_policy = "same-origin"
cross_origin_embedder_policy = "unsafe-none"
cross_origin_resource_policy = "same-origin"

# Additional security headers
x_permitted_cross_domain_policies = "none"
x_download_options = "noopen"
x_dns_prefetch_control = "off"

# PQC branding headers
x_quantum_resistant = "ML-KEM-1024, ML-DSA-87, X25519MLKEM768"
x_security_level = "Post-Quantum Ready"

# HTTP/3 Performance & Monitoring Headers
# Enable Server-Timing header for performance metrics
server_timing_enabled = true

# Accept-CH header for Client Hints (responsive content delivery)
accept_ch = "DPR, Viewport-Width, Width, ECT, RTT, Downlink, Sec-CH-UA-Platform, Sec-CH-UA-Mobile"

# Outlook add-in surface exception.
# Office hosts the PQ PDF task pane in a cross-origin iframe, so this path must NOT
# receive X-Frame-Options and instead gets an add-in CSP (frame-ancestors for the
# Office hosts + the office.js CDN + api.pqpdf.com). Set addin_csp = "" to disable.
addin_hosts = ["pqpdf.com", "www.pqpdf.com"]
addin_path_prefix = "/outlook/"
addin_csp = "default-src 'self'; script-src 'self' 'unsafe-inline' https://appsforoffice.microsoft.com https://*.officeapps.live.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.pqpdf.com https://*.office.com https://*.officeapps.live.com; frame-ancestors https://outlook.office.com https://outlook.office365.com https://outlook.live.com https://*.office.com https://*.officeapps.live.com 'self'"

# NEL (Network Error Logging) for client-side error reporting
nel = '{"report_to":"default","max_age":86400,"include_subdomains":true}'

# Report-To endpoint configuration for NEL and other reports
report_to = '{"group":"default","max_age":86400,"endpoints":[{"url":"https://api.pqcrypta.com/reports"}]}'

# HTTP/3 Priority (RFC 9218) - u=0-7 (urgency), i (incremental)
priority = "u=3"

# =============================================================================
# HTTP Redirect Configuration
# =============================================================================
[http_redirect]
# Enable HTTP redirect server on port 80
enabled = true

# HTTP port (redirects to HTTPS on primary port)
port = 80

# Redirect all HTTP to HTTPS
redirect_to_https = true

# =============================================================================
# Load Balancer Configuration
# =============================================================================
[load_balancer]
# Enable load balancing (automatically enabled when backend_pools defined)
enabled = true

# Default algorithm for all pools
# Options: least_connections, round_robin, weighted_round_robin, random, ip_hash, least_response_time
default_algorithm = "least_connections"

# Session affinity (sticky sessions) configuration
[load_balancer.session_affinity]
enabled = false
cookie_name = "PQCPROXY_BACKEND"
cookie_ttl_secs = 3600
cookie_secure = true
cookie_httponly = true
cookie_samesite = "lax"

# Request queue for when all backends are saturated
[load_balancer.queue]
enabled = true
max_size = 1000
timeout_ms = 5000

# Slow start for recovering backends (gradual traffic increase)
[load_balancer.slow_start]
enabled = true
duration_secs = 30
initial_weight_percent = 10

# Connection draining for graceful backend removal
[load_balancer.connection_draining]
enabled = true
timeout_secs = 30

# =============================================================================
# Backend Definitions
# =============================================================================

# Rust Enterprise API server (port 3003)
[backends.pqcrypta-api]
name = "pqcrypta-api"
type = "http1"
address = "[loopback]"
tls = false
timeout_ms = 120000
max_connections = 200
# Pooled HTTP/1.1 connections to this backend intermittently hang or (with h2c)
# get reset under sustained concurrent load โ€” root cause not yet isolated
# further upstream. Disabling pooling forces a fresh connection per request,
# which sidesteps it; cheap since this is a loopback backend.
disable_pooling = true
health_check = "/health"
health_check_interval_secs = 30
chatbot_timeout_ms = 300000

# Apache (port 8080) - serves main website
[backends.apache]
name = "apache"
type = "http1"
address = "[loopback]"
tls = false
timeout_ms = 120000
max_connections = 100
health_check = "/"
health_check_interval_secs = 30

# Prometheus metrics server (port 9090)
[backends.prometheus]
name = "prometheus"
type = "http1"
address = "[loopback]"
tls = false
timeout_ms = 30000
max_connections = 50
health_check = "/prometheus/-/healthy"
health_check_interval_secs = 30

# Grafana dashboard server (port 3000)
[backends.grafana]
name = "grafana"
type = "http1"
address = "[loopback]"
tls = false
timeout_ms = 30000
max_connections = 50
health_check = "/grafana/api/health"
health_check_interval_secs = 30

# PQPDF external REST API (port 3004)
[backends.pqpdf-api]
name = "pqpdf-api"
type = "http1"
address = "[loopback]"
tls = false
timeout_ms = 180000
max_connections = 200
health_check = "/v1/health"
health_check_interval_secs = 30

# Frappe socket.io Node server (direct โ€” bypasses Apache for WebSocket support)
[backends.office-node]
name = "office-node"
type = "http1"
address = "[loopback]"
tls = false
timeout_ms = 120000
max_connections = 500
health_check_interval_secs = 30


# =============================================================================
# DOMAIN: api.pqcrypta.com - Routes to Rust Enterprise API
# =============================================================================
#
# Traffic Shadowing / Mirroring
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Add a [routes.shadow] block to any route to mirror traffic to a second backend.
# The client only sees the primary response; the shadow response is discarded.
# Example (all values configurable):
#
# [routes.shadow]
# backend             = "pqcrypta-api-canary"  # Must be a key in [backends.*]
# percent             = 10                      # 0โ€“100 % of requests to mirror
# timeout_ms          = 5000                    # Abandon shadow task after this many ms
# shadow_header       = "X-Shadow-Request"      # Header injected on shadow requests
# shadow_header_value = "1"                     # Value for that header
# log_responses       = true                    # Log shadow status + latency at INFO
#
# To activate: uncomment the block, add a [backends.pqcrypta-api-canary] entry
# pointing to the canary instance (e.g. [loopback]:3004), then reload config.

# Chatbot endpoint - extended timeouts for ML/FAISS model loading
[[routes]]
name = "api-chatbot"
# Public REST API: HTTP/1.1 clients (python-requests, node fetch, SDKs)
# must not get 426; h2/h3 still preferred via ALPN.
allow_http11 = true
host = "api.pqcrypta.com"
path_prefix = "/chatbot"
webtransport = false
backend = "pqcrypta-api"
priority = 5
timeout_override_ms = 300000

[routes.cors]
allow_origin = "https://pqcrypta.com"
allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"]
allow_credentials = "[REDACTED]"
max_age = 86400

# WebTransport route for encryption streams
[[routes]]
name = "webtransport-encrypt"
# Public REST API: HTTP/1.1 clients (python-requests, node fetch, SDKs)
# must not get 426; h2/h3 still preferred via ALPN.
allow_http11 = true
host = "api.pqcrypta.com"
path_prefix = "/encrypt"
webtransport = true
backend = "pqcrypta-api"
stream_to_method = "POST"
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 10

[routes.add_headers]
X-Forwarded-Proto = "https"
X-WebTransport = "true"

[routes.cors]
allow_origin = "https://pqcrypta.com"
allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"]
allow_credentials = "[REDACTED]"
max_age = 86400

# WebTransport route for decryption streams
[[routes]]
name = "webtransport-decrypt"
# Public REST API: HTTP/1.1 clients (python-requests, node fetch, SDKs)
# must not get 426; h2/h3 still preferred via ALPN.
allow_http11 = true
host = "api.pqcrypta.com"
path_prefix = "/decrypt"
webtransport = true
backend = "pqcrypta-api"
stream_to_method = "POST"
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 10

[routes.cors]
allow_origin = "https://pqcrypta.com"
allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"]
allow_credentials = "[REDACTED]"
max_age = 86400

# WebTransport route for key generation
[[routes]]
name = "webtransport-keys"
# Public REST API: HTTP/1.1 clients (python-requests, node fetch, SDKs)
# must not get 426; h2/h3 still preferred via ALPN.
allow_http11 = true
host = "api.pqcrypta.com"
path_prefix = "/keys"
webtransport = true
backend = "pqcrypta-api"
stream_to_method = "POST"
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 10

[routes.cors]
allow_origin = "https://pqcrypta.com"
allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"]
allow_credentials = "[REDACTED]"
max_age = 86400

# WebTransport route for streaming
[[routes]]
name = "webtransport-stream"
# Public REST API: HTTP/1.1 clients (python-requests, node fetch, SDKs)
# must not get 426; h2/h3 still preferred via ALPN.
allow_http11 = true
host = "api.pqcrypta.com"
path_prefix = "/stream"
webtransport = true
backend = "pqcrypta-api"
stream_to_method = "POST"
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 10

[routes.cors]
allow_origin = "https://pqcrypta.com"
allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"]
allow_credentials = "[REDACTED]"
max_age = 86400

# General WebTransport catch-all
[[routes]]
name = "webtransport-default"
host = "api.pqcrypta.com"
path_prefix = "/webtransport"
webtransport = true
backend = "pqcrypta-api"
stream_to_method = "POST"
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 50

[routes.cors]
allow_origin = "https://pqcrypta.com"
allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"]
allow_credentials = "[REDACTED]"
max_age = 86400

# Prometheus API (metrics query endpoints)
[[routes]]
name = "api-prometheus"
# Public REST API: HTTP/1.1 clients (python-requests, node fetch, SDKs)
# must not get 426; h2/h3 still preferred via ALPN.
allow_http11 = true
host = "api.pqcrypta.com"
path_prefix = "/prometheus"
webtransport = false
backend = "prometheus"
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 15

[routes.cors]
allow_origin = "https://pqcrypta.com"
allow_methods = ["GET", "POST", "OPTIONS"]
allow_headers = ["Content-Type", "Authorization", "X-API-Key"]
allow_credentials = "[REDACTED]"
max_age = 86400

# API catch-all (all api.pqcrypta.com traffic โ†’ port 3003)
[[routes]]
name = "api-default"
# Public REST API: HTTP/1.1 clients (python-requests, node fetch, SDKs)
# must not get 426; h2/h3 still preferred via ALPN.
allow_http11 = true
host = "api.pqcrypta.com"
path_prefix = "/"
webtransport = false
backend = "pqcrypta-api"
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 100

[routes.cors]
allow_origins = ["https://pqcrypta.com", "https://www.pqcrypta.com", "https://pqpdf.com", "https://www.pqpdf.com", "https://stlweb.dev", "https://conformance.pqcrypta.com"]
allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"]
allow_credentials = "[REDACTED]"
max_age = 86400

# =============================================================================
# DOMAIN: pqcrypta.com / www.pqcrypta.com - Routes to Apache
# =============================================================================

# WebTransport for main site - forward to API backend
[[routes]]
name = "main-webtransport"
host = "pqcrypta.com"
path_prefix = "/webtransport"
webtransport = true
backend = "pqcrypta-api"
stream_to_method = "POST"
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 5

[routes.cors]
allow_origin = "https://pqcrypta.com"
allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"]
allow_credentials = "[REDACTED]"
max_age = 86400

# WebTransport root path for main site
[[routes]]
name = "main-webtransport-root"
host = "pqcrypta.com"
path_exact = "/"
webtransport = true
backend = "pqcrypta-api"
stream_to_method = "POST"
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 5

[routes.cors]
allow_origin = "https://pqcrypta.com"
allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"]
allow_credentials = "[REDACTED]"
max_age = 86400

# www.pqcrypta.com WebTransport
[[routes]]
name = "www-webtransport"
host = "www.pqcrypta.com"
path_prefix = "/webtransport"
webtransport = true
backend = "pqcrypta-api"
stream_to_method = "POST"
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 5

[routes.cors]
allow_origin = "https://pqcrypta.com"
allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"]
allow_credentials = "[REDACTED]"
max_age = 86400

# SEO redirects - underscore to hyphen
[[routes]]
name = "seo-redirect-http3_quic"
allow_http11 = true
host = "pqcrypta.com"
path_prefix = "/http3_quic"
redirect = "/http3-quic"
redirect_permanent = true
priority = 1

[[routes]]
name = "seo-redirect-pqc_ready"
allow_http11 = true
host = "pqcrypta.com"
path_prefix = "/pqc_ready"
redirect = "/pqc-ready"
redirect_permanent = true
priority = 1

[[routes]]
name = "seo-redirect-key_vault"
allow_http11 = true
host = "pqcrypta.com"
path_prefix = "/key_vault"
redirect = "/key-vault"
redirect_permanent = true
priority = 1

[[routes]]
name = "seo-redirect-security_systems"
allow_http11 = true
host = "pqcrypta.com"
path_prefix = "/security_systems"
redirect = "/security-systems"
redirect_permanent = true
priority = 1

[[routes]]
name = "seo-redirect-bot_threat_remediation"
allow_http11 = true
host = "pqcrypta.com"
path_prefix = "/bot_threat_remediation"
redirect = "/bot-threat-remediation"
redirect_permanent = true
priority = 1

[[routes]]
name = "seo-redirect-threat_bot"
allow_http11 = true
host = "pqcrypta.com"
path_prefix = "/threat_bot"
redirect = "/threat-bot"
redirect_permanent = true
priority = 1

# Speed test TCP endpoints - allow HTTP/1.1 so each parallel stream uses a
# separate TCP connection, giving independent congestion windows for accurate
# throughput measurement. HTTP/2 multiplexes over one connection which defeats
# the purpose of parallel streams.
[[routes]]
name = "speedtest-tcp"
host = "pqcrypta.com"
path_regex = "^/speedtest/tcp-(ping|download|upload)\\.php"
webtransport = false
backend = "apache"
allow_http11 = true
priority = 3

[routes.headers_override]
Cache-Control = "no-store, no-cache, must-revalidate"
X-Accel-Buffering = "no"

# Sitemaps - allow HTTP/1.1 for search bots
[[routes]]
name = "main-sitemaps"
host = "pqcrypta.com"
path_regex = "^/sitemaps/.*\\.xml$"
webtransport = false
backend = "apache"
allow_http11 = true
priority = 5

[routes.headers_override]
Cross-Origin-Resource-Policy = "cross-origin"
Access-Control-Allow-Origin = "*"
X-Robots-Tag = "noindex, follow"
Cache-Control = "public, max-age=3600"

# robots.txt - allow HTTP/1.1 for search bots
[[routes]]
name = "main-robots"
host = "pqcrypta.com"
path_exact = "/robots.txt"
webtransport = false
backend = "apache"
allow_http11 = true
priority = 5

[routes.headers_override]
Cross-Origin-Resource-Policy = "cross-origin"
Access-Control-Allow-Origin = "*"
Cache-Control = "public, max-age=3600"

# Fonts - special handling
[[routes]]
name = "main-fonts"
allow_http11 = true
host = "pqcrypta.com"
path_regex = "\\.(woff|woff2|ttf|eot|otf)$"
webtransport = false
backend = "apache"
priority = 5

[routes.headers_override]
Cache-Control = "public, max-age=31536000"
Access-Control-Allow-Origin = "*"

# Audio files - mobile optimized
[[routes]]
name = "main-audio"
allow_http11 = true
host = "pqcrypta.com"
path_regex = "\\.(mp3|ogg|wav|m4a|aac|flac)$"
webtransport = false
backend = "apache"
priority = 5

[routes.headers_override]
Cross-Origin-Resource-Policy = "cross-origin"
Access-Control-Allow-Origin = "*"
Access-Control-Allow-Methods = "GET, HEAD, POST, OPTIONS"
Access-Control-Allow-Headers = "Range, Content-Range, Content-Type, Accept, User-Agent, X-API-Key, signature-agent"
Access-Control-Expose-Headers = "Content-Range, Content-Length, Accept-Ranges"

# Billing pages - Stripe.js compatibility (remove COEP/COOP)
[[routes]]
name = "main-billing"
allow_http11 = true
host = "pqcrypta.com"
path_prefix = "/dashboard/billing/"
webtransport = false
backend = "apache"
priority = 5
stripe_compatibility = true

# api_config.php - no bot blocking
[[routes]]
name = "main-api-config"
allow_http11 = true
host = "pqcrypta.com"
path_exact = "/api_config.php"
webtransport = false
backend = "apache"
priority = 5
skip_bot_blocking = true

# Prometheus UI (metrics dashboard) - all /prometheus paths go to Prometheus
# Priority 3 = higher than fonts route (5) so Prometheus serves its own assets
[[routes]]
name = "main-prometheus"
allow_http11 = true
host = "pqcrypta.com"
path_prefix = "/prometheus"
webtransport = false
backend = "prometheus"
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 3

[routes.headers_override]
cross_origin_embedder_policy = "unsafe-none"
cross_origin_opener_policy = "unsafe-none"
cross_origin_resource_policy = "cross-origin"

# Grafana dashboards - all /grafana paths go to Grafana
# Priority 3 = higher than fonts route (5) so Grafana serves its own assets
[[routes]]
name = "main-grafana"
allow_http11 = true
host = "pqcrypta.com"
path_prefix = "/grafana"
webtransport = false
backend = "grafana"
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 3

[routes.headers_override]
cross_origin_embedder_policy = "unsafe-none"
cross_origin_opener_policy = "unsafe-none"
cross_origin_resource_policy = "cross-origin"

# Main site catch-all (all pqcrypta.com traffic โ†’ Apache port 8080)
[[routes]]
name = "main-default"
host = "pqcrypta.com"
path_prefix = "/"
webtransport = false
backend = "apache"
allow_http11 = true
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 100

# www.pqcrypta.com - same as pqcrypta.com
[[routes]]
name = "www-default"
host = "www.pqcrypta.com"
path_prefix = "/"
webtransport = false
backend = "apache"
allow_http11 = true
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 100

# =============================================================================
# DOMAIN: pqpdf.com / www.pqpdf.com - Routes to Apache (PDF tools standalone)
# =============================================================================

# api.pqcrypta.com robots.txt โ€” served by Apache, not by the API.
#
# api.pqcrypta.com routes straight to the pqcrypta-api backend, so /robots.txt
# returned the API's JSON 404. Googlebot fetches it on every visit to the host
# and, getting no directives, was free to crawl the whole API surface โ€” crawl
# budget spent on a host with nothing indexable on it. priority = 1 puts this
# ahead of the API path routes; the file itself is a "Disallow: /" served from
# /var/www/api-static by the api vhost.
[[routes]]
name = "api-robots"
host = "api.pqcrypta.com"
path_exact = "/robots.txt"
webtransport = false
backend = "apache"
allow_http11 = true
priority = 1

# pqpdf.com sitemaps
[[routes]]
name = "pqpdf-sitemaps"
host = "pqpdf.com"
path_regex = "^/sitemaps/.*\\.xml$"
webtransport = false
backend = "apache"
allow_http11 = true
priority = 5

[routes.headers_override]
Cross-Origin-Resource-Policy = "cross-origin"
Access-Control-Allow-Origin = "*"
X-Robots-Tag = "noindex, follow"
Cache-Control = "public, max-age=3600"

# pqpdf.com sitemap.xml
[[routes]]
name = "pqpdf-sitemap"
host = "pqpdf.com"
path_exact = "/sitemap.xml"
webtransport = false
backend = "apache"
allow_http11 = true
priority = 200

[routes.headers_override]
Cross-Origin-Resource-Policy = "cross-origin"
Access-Control-Allow-Origin = "*"
Cache-Control = "public, max-age=3600"

# pqpdf.com robots.txt
[[routes]]
name = "pqpdf-robots"
host = "pqpdf.com"
path_exact = "/robots.txt"
webtransport = false
backend = "apache"
allow_http11 = true
priority = 200

[routes.headers_override]
Cross-Origin-Resource-Policy = "cross-origin"
Access-Control-Allow-Origin = "*"
Cache-Control = "public, max-age=3600"

# pqpdf.com WebTransport
[[routes]]
name = "pqpdf-webtransport"
host = "pqpdf.com"
path_prefix = "/webtransport"
webtransport = true
backend = "apache"
stream_to_method = "POST"
priority = 3

# pqpdf.com catch-all
[[routes]]
name = "pqpdf-default"
host = "pqpdf.com"
path_prefix = "/"
webtransport = false
backend = "apache"
allow_http11 = true
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 100

# www.pqpdf.com WebTransport
[[routes]]
name = "www-pqpdf-webtransport"
host = "www.pqpdf.com"
path_prefix = "/webtransport"
webtransport = true
backend = "apache"
stream_to_method = "POST"
priority = 3

# www.pqpdf.com โ€” same as pqpdf.com
[[routes]]
name = "www-pqpdf-default"
host = "www.pqpdf.com"
path_prefix = "/"
webtransport = false
backend = "apache"
allow_http11 = true
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 100

# DOMAIN: fated.org โ€” Company site (Fated LLC)
[[routes]]
name = "fated-webtransport"
host = "fated.org"
path_prefix = "/webtransport"
webtransport = true
backend = "apache"
stream_to_method = "POST"
priority = 3

[[routes]]
name = "fated-default"
host = "fated.org"
path_prefix = "/"
webtransport = false
backend = "apache"
allow_http11 = true
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 100

# www.fated.org โ€” same as fated.org
[[routes]]
name = "www-fated-webtransport"
host = "www.fated.org"
path_prefix = "/webtransport"
webtransport = true
backend = "apache"
stream_to_method = "POST"
priority = 3

[[routes]]
name = "www-fated-default"
host = "www.fated.org"
path_prefix = "/"
webtransport = false
backend = "apache"
allow_http11 = true
forward_client_identity = true
client_identity_header = "X-Client-IP"

# =============================================================================
# DOMAIN: api.pqpdf.com โ€” PQPDF External REST API (port 3004)
# =============================================================================

# All api.pqpdf.com traffic โ†’ pqpdf-api Rust binary
[[routes]]
name = "pqpdf-api-default"
allow_http11 = true
host = "api.pqpdf.com"
path_prefix = "/"
webtransport = false
backend = "pqpdf-api"
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 100

[routes.cors]
allow_origins = ["*"]
allow_methods = ["GET", "POST", "DELETE", "OPTIONS"]
allow_headers = ["Content-Type", "X-API-Key", "X-Session-Id", "Authorization"]
allow_credentials = "[REDACTED]"
max_age = 86400
priority = 100

# =============================================================================
# DOMAIN: stlweb.dev โ€” St. Louis Web Development Studio (Fated LLC)
# =============================================================================

# stlweb.dev WebTransport probe โ€” accepts WT CONNECT so scanner grades A++
[[routes]]
name = "stlweb-webtransport"
host = "stlweb.dev"
path_prefix = "/webtransport"
webtransport = true
backend = "apache"
stream_to_method = "POST"
priority = 3

# stlweb.dev robots.txt โ€” allow HTTP/1.1 for search bots
[[routes]]
name = "stlweb-robots"
host = "stlweb.dev"
path_exact = "/robots.txt"
webtransport = false
backend = "apache"
allow_http11 = true
priority = 5

[routes.headers_override]
Cross-Origin-Resource-Policy = "cross-origin"
Access-Control-Allow-Origin = "*"
Cache-Control = "public, max-age=3600"

# stlweb.dev sitemap.xml โ€” allow HTTP/1.1 for crawlers
[[routes]]
name = "stlweb-sitemap"
host = "stlweb.dev"
path_exact = "/sitemap.xml"
webtransport = false
backend = "apache"
allow_http11 = true
priority = 5

[routes.headers_override]
Cross-Origin-Resource-Policy = "cross-origin"
Access-Control-Allow-Origin = "*"
Cache-Control = "public, max-age=3600"

# stlweb.dev fonts โ€” long cache, CORS open
[[routes]]
name = "stlweb-fonts"
host = "stlweb.dev"
path_regex = "\\.(woff|woff2|ttf|eot|otf)$"
webtransport = false
backend = "apache"
allow_http11 = true
priority = 5

[routes.headers_override]
Cache-Control = "public, max-age=31536000"
Access-Control-Allow-Origin = "*"

# stlweb.dev CSS โ€” long cache, no Content-Type override (Apache sends correct text/css)
[[routes]]
name = "stlweb-css"
host = "stlweb.dev"
path_regex = "\\.css$"
webtransport = false
backend = "apache"
allow_http11 = true
priority = 4

[routes.headers_override]
Cache-Control = "public, max-age=2592000"

# stlweb.dev JS โ€” long cache, no Content-Type override (Apache sends correct application/javascript)
[[routes]]
name = "stlweb-js"
host = "stlweb.dev"
path_regex = "\\.js$"
webtransport = false
backend = "apache"
allow_http11 = true
priority = 4

[routes.headers_override]
Cache-Control = "public, max-age=2592000"

# stlweb.dev other static assets โ€” long cache
[[routes]]
name = "stlweb-static"
host = "stlweb.dev"
path_regex = "\\.(webp|avif|jpg|jpeg|png|gif|ico|svg)$"
webtransport = false
backend = "apache"
allow_http11 = true
priority = 5

[routes.headers_override]
Cache-Control = "public, max-age=2592000"

# stlweb.dev contact form โ€” no bot blocking to allow form submissions
[[routes]]
name = "stlweb-contact-submit"
host = "stlweb.dev"
path_exact = "/contact/submit.php"
webtransport = false
backend = "apache"
allow_http11 = true
priority = 3
skip_bot_blocking = true

# www.stlweb.dev โ†’ stlweb.dev (301 redirect, non-www canonical)
[[routes]]
name = "www-stlweb-default"
host = "www.stlweb.dev"
path_prefix = "/"
webtransport = false
backend = "apache"
allow_http11 = true
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 100

# stlweb.dev catch-all โ€” all traffic โ†’ Apache
[[routes]]
name = "stlweb-default"
host = "stlweb.dev"
path_prefix = "/"
webtransport = false
backend = "apache"
allow_http11 = true
forward_client_identity = true
client_identity_header = "X-Client-IP"
priority = 100

# =============================================================================
# DOMAIN: office.stlweb.dev โ€” ERPNext (stlweb.dev branded ERP)
# =============================================================================

# WebTransport โ€” speedtest, telemetry, and WT root (required for A++ grade)
[[routes]]
name = "office-wt-speedtest"
host = "office.stlweb.dev"
path_exact = "/speedtest"
webtransport = true
backend = "apache"
priority = 1

[[routes]]
name = "office-wt-telemetry"
host = "office.stlweb.dev"
path_exact = "/telemetry"
webtransport = true
backend = "apache"
priority = 1

[[routes]]
name = "office-wt-root"
host = "office.stlweb.dev"
path_prefix = "/webtransport"
webtransport = true
backend = "apache"
priority = 2

# Static assets โ€” long cache, immutable
[[routes]]
name = "office-assets"
host = "office.stlweb.dev"
path_prefix = "/assets/"
backend = "apache"
allow_http11 = true
priority = 20

[routes.headers_override]
Cache-Control = "public, max-age=31536000, immutable"
Cross-Origin-Resource-Policy = "cross-origin"

# Login endpoint โ€” strict rate limit to mitigate credential brute-force (H4)
[[routes]]
name = "office-login"
host = "office.stlweb.dev"
path_exact = "/api/method/login"
backend = "apache"
allow_http11 = true
priority = 15
enforce_cookie_security = true
strip_response_json_fields = ["exc"]

[routes.headers_override]
Cache-Control = "no-store"
Cross-Origin-Resource-Policy = "cross-origin"
Cross-Origin-Opener-Policy = "unsafe-none"

# API endpoints โ€” no cache
[[routes]]
name = "office-api"
host = "office.stlweb.dev"
path_prefix = "/api/"
backend = "apache"
allow_http11 = true
priority = 20
strip_response_json_fields = ["exc"]
enforce_cookie_security = true

[routes.headers_override]
Cache-Control = "no-store"
Cross-Origin-Resource-Policy = "cross-origin"
Cross-Origin-Opener-Policy = "unsafe-none"

# Socket.IO โ€” no cache
[[routes]]
name = "office-socketio"
host = "office.stlweb.dev"
path_prefix = "/socket.io/"
backend = "office-node"
allow_http11 = true
websocket = true
priority = 20

[routes.headers_override]
Cache-Control = "no-store"

# Uploaded files โ€” no cache
[[routes]]
name = "office-files"
host = "office.stlweb.dev"
path_prefix = "/files/"
backend = "apache"
allow_http11 = true
priority = 20

[routes.headers_override]
Cache-Control = "no-store, private"

# Catch-all โ€” ERPNext app (no-store, relaxed CSP for Frappe)
[[routes]]
name = "office-app"
host = "office.stlweb.dev"
path_prefix = "/"
backend = "apache"
allow_http11 = true
priority = 100
enforce_cookie_security = true

[routes.headers_override]
# CSP is set by Frappe itself per-request with a generated nonce (frappe.utils.csp).
# The proxy does NOT override it here โ€” the backend CSP passes through.
X-Frame-Options = "SAMEORIGIN"
Cache-Control = "no-store, no-cache"
Cross-Origin-Opener-Policy = "unsafe-none"
Cross-Origin-Resource-Policy = "cross-origin"