proxy-config.toml. No changes can be made from this interface.proxy-config.toml. No changes can be made from this interface.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}".
Primary UDP port for QUIC/HTTP3/WebTransport
Maximum concurrent connections
Maximum concurrent streams per QUIC connection
Keep-alive interval
Maximum idle timeout before connection close
Tokio runtime worker threads, read before the runtime is built. 0 = one per CPU core.
Graceful shutdown drain timeout after SIGTERM before forced exit
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.
QUIC ACK Frequency extension (draft-ietf-quic-ack-frequency) โ lets the peer request fewer, batched acknowledgements. Negotiated, so clients without it are unaffected.
Ack-eliciting QUIC packets accepted before an ACK-only packet is sent. 1 (default) is RFC 9000's every-other-packet; below it an owed ACK rides on the next outgoing packet. 0 acknowledges every packet at once.
Carry owed QUIC ACKs on packets being sent anyway, so a response acknowledges the request it answers instead of the ACK waiting for max_ack_delay (25 ms).
Let responses finishing together on one QUIC connection leave in one transmit: the connection driver yields past the runtime's next I/O poll before sending. Measured 2.08 to about 1.3 datagrams per request at a hundred connections.
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.
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.
Lowercase request paths at the proxy. Disabled where backends enforce their own casing or use case-sensitive hashes.
Additional ports to listen on (all support QUIC/HTTP3/WebTransport)
Allowed WebTransport origins โ browser Origin header validation. Empty = reject all browser connections.
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.
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.
Proxy UDP over HTTP/3 Extended CONNECT. Off by default โ an open UDP relay is an amplification and SSRF risk.
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 permittedA session with no datagrams in either direction for this long is closed
Maximum concurrent CONNECT-UDP sessions per QUIC connection
Enable HTTP redirect server on port 80
HTTP port โ listens for plain HTTP and redirects 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 hostnames for the HTTPโHTTPS redirect. Requests with a Host header not in this list receive 400 (prevents open-redirect abuse).
None configuredproxy-config.toml. No changes can be made from this interface.Path to TLS certificate chain (PEM format)
Path to TLS private key (PEM format)
Optional CA certificate for client certificate verification (mTLS). Not set = mTLS disabled unless require_client_cert is true.
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.
Minimum TLS version โ only TLS 1.3 is supported for QUIC
- 1.3 required for QUIC
- 1.2
ALPN protocols advertised during TLS handshake
TLS-side stapling switch. Both this and [ocsp].enabled must be on for the proxy to fetch and staple revocation responses.
Certificate hot-reload interval (0 = disabled)
Enable 0-RTT early data for faster reconnections. โ ๏ธ Vulnerable to replay attacks.
Fetch and staple OCSP responses so clients need no separate revocation round trip
How long a fetched OCSP response is reused
Refresh lead time โ a new response is fetched this long before the cached one expires
Timeout for a request to the CA responder
Retry attempts before giving up on a responder
Delay between retry attempts
Enable ACME certificate automation (Let's Encrypt)
Domains to manage certificates for
ACME challenge type for domain validation
- http-01 port 80 required
- dns-01
- tls-alpn-01
ACME directory URL
Contact email for Let's Encrypt notifications
Directory to store managed certificates
ACME account credentials storage
Renew certificates this many days before expiry
Check interval for certificate expiry
Port the http-01 challenge responder listens on โ the CA always validates on 80
Use ECDSA keys โ smaller keys and faster TLS handshakes than RSA
ECDSA curve for issued certificates. P-384 = 192-bit, equivalent to 7680-bit RSA; P-256 = 128-bit.
Accept CA terms of service (required for Let's Encrypt)
proxy-config.toml. No changes can be made from this interface.Enable PQC hybrid key exchange in TLS handshake
PQC provider โ auto uses rustls for QUIC, OpenSSL for broader algorithms
- auto recommended
- rustls pure Rust, QUIC-native
- openssl3.5 ML-KEM native
Path to OpenSSL 3.5+ binary with PQC support
OpenSSL library path for dynamic linking
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
Fall back to classical TLS if PQC is unavailable
Minimum NIST security level (1=128-bit, 3=192-bit, 5=256-bit)
Additional KEM algorithms to offer (in preference order after preferred_kem)
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.
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 OpenSSL provider integrity at startup
Check TLS key file permissions (should be 0600 or 0400)
Fail startup if key permissions are insecure (vs just warning)
proxy-config.toml. No changes can be made from this interface.Maximum request body size
Maximum header size
Connection establishment timeout
Enable DoS protection layer
Maximum concurrent connections from a single IP
Static IP/CIDR blocklist applied before any other check. Entries withheld.
1 entry โ withheldAllowed IP allowlist (empty = allow all). Entries withheld.
1 entry โ withheldBlocked ISO 3166-1 alpha-2 country codes
4xx errors before triggering suspicious-pattern check
Minimum requests before error rate check activates
Error rate (0.0โ1.0) that triggers suspicious pattern (e.g. 0.7 = 70%)
Sliding window duration for error detection
Suspicious pattern count before auto-block triggers
Duration of automatic IP block after threshold exceeded
MaxMind GeoLite2 database backing country blocking
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 โ withheldEnable request inspection for OWASP Top 10 attack patterns
Action taken on a rule hit
- block reject with 403
- detect log only, for tuning
OWASP A03 โ SQL injection pattern matching
OWASP A03 โ cross-site scripting pattern matching
OWASP A01/A05 โ path traversal detection (../, %2e%2e and encodings)
OWASP A03 โ NoSQL injection detection ($where, $gt operator abuse)
OWASP A10 โ SSRF pattern detection. High false-positive rate on URL form fields.
Inspect JSON request bodies, not just the query string and headers
Maximum request body bytes inspected โ bodies beyond this are passed through unscanned
Block known reconnaissance probe paths (Nikto, sqlmap, directory scanners)
Block known scanner and bot user-agents (sqlmap, nikto, masscan, headless clients)
Operator-supplied regex rules layered on top of the built-in ruleset. Pattern bodies withheld.
None โ built-in ruleset onlyEnable TLS fingerprint detection middleware
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 fingerprints belonging to scanning tools (Nmap, Nikto, Burp Suite)
Attach the computed fingerprint to responses for debugging and monitoring
Automatically block connections presenting known-malicious JA3/JA4 fingerprints. false = advisory-only (log but not block).
Block duration for confirmed malicious fingerprints
Block duration for suspicious fingerprints with high request rate
Request count threshold to trigger suspicious fingerprint rate check
Time window for suspicious rate detection
Fingerprint cache max age before cleanup
proxy-config.toml. No changes can be made from this interface.Enable basic rate limiting (overridden by advanced when both enabled)
Maximum requests per second per IP address
Token bucket burst size โ allows short spikes above the rate limit
Enable connection rate limiting (new connections per second)
New connections allowed per second per IP
Enable advanced multi-dimensional rate limiting (overrides basic)
IPv6 subnet grouping โ /64 subnets treated as a single client (NAT-aware)
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 โ withheldPriority order for rate limit key resolution (first found wins)
Key resolution fallback when no other key is found
- source_ip default fallback
- real_ip
- ja3_fingerprint
Use composite key (combines multiple dimensions)
Header name for API key extraction
Header name for user ID extraction
Header name for tenant ID extraction
Header name for real IP extraction
Global DDoS protection layer req/s
Global burst bucket size
Per-IP requests per second
Per-IP burst size
Per-IP requests per minute
Per-IP requests per hour
Per JA3/JA4 fingerprint req/s. Base limits for a fingerprint key; fingerprint_limiting refines these when it is enabled
Per-fingerprint burst size
Per-fingerprint requests per minute
Per-fingerprint requests per hour
Per API key requests per second
Per API key burst size
Per API key requests per minute
Per API key requests per hour
Per composite key req/s
Per composite key burst size
Per composite key requests per minute
Per composite key requests per hour
Redis URL for distributed counter sharing across proxy instances
Redis key prefix for all rate limit counters
Redis connection timeout
Redis command timeout โ fast fail to prevent blocking
Use per-second distributed sliding window
Enable fingerprint-based rate limiting (NAT-friendly)
Prefer fingerprint key over IP address for limiting
Explicitly blocked JA3/JA4 fingerprint hashes
No fingerprints blockedRate limit for unknown/unclassified fingerprints
Burst size for unknown fingerprints
Per-minute limit for unknown fingerprints
Per-hour limit for unknown fingerprints
Enable ML-inspired adaptive rate limiting with anomaly detection
Baseline measurement window for normal traffic profiling
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
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
Minimum sample count before adaptive limiting activates
Standard deviation multiplier for anomaly threshold (requests > mean + N*stddev = anomaly)
No per-route overrides โ the global per-IP and per-fingerprint limits apply everywhere.
None configuredproxy-config.toml. No changes can be made from this interface.Enable circuit breaker for backend protection against cascading failures
Failure count to open the circuit
Time before circuit transitions from Open โ Half-Open state
Maximum test requests allowed in Half-Open state
Success count required to close circuit from Half-Open state
Stale request counter cleanup interval
How long idle connections remain in pool before being closed
Maximum idle connections kept per backend host
Maximum total connections per backend host
Connection acquire timeout from pool
proxy-config.toml. No changes can be made from this interface.Send a 103 Early Hints response so the browser starts fetching critical resources while the backend is still generating the page
Honour RFC 9218 extensible priorities (Priority header and PRIORITY_UPDATE frames) when scheduling streams
Deduplicate identical in-flight requests โ concurrent requests for the same resource share one backend fetch
Origins advertised for preconnect in the 103 response
None configuredMaximum time a coalesced request waits on the in-flight fetch before issuing its own
Maximum requests that may attach to a single in-flight fetch
HTTP methods eligible for coalescing
Paths never coalesced โ APIs, WebSocket upgrades and streaming endpoints
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 | style |
| stlweb.dev | / | exact | /js/main.js | script |
| stlweb.dev | /about.php | prefix | /css/style.css | style |
| stlweb.dev | /about.php | prefix | /js/main.js | script |
| stlweb.dev | /services.php | prefix | /css/style.css | style |
| stlweb.dev | /services.php | prefix | /js/main.js | script |
| stlweb.dev | /work.php | prefix | /css/style.css | style |
| stlweb.dev | /work.php | prefix | /js/main.js | script |
| stlweb.dev | /contact.php | prefix | /css/style.css | style |
| stlweb.dev | /contact.php | prefix | /js/main.js | script |
| stlweb.dev | /legal/ | prefix | /css/style.css | style |
| stlweb.dev | /legal/ | prefix | /js/main.js | script |
| stlweb.dev | /proposals/ofallonconcreteco.com/ | prefix | /proposals/ofallonconcreteco.com/css/style.css | style |
| stlweb.dev | /proposals/ofallonconcreteco.com/ | prefix | /proposals/ofallonconcreteco.com/js/head.js | script |
| pqcrypta.com | / | exact | /css/pqcrypta-loader.css | style |
| pqcrypta.com | / | exact | /css/homepage.css | style |
| pqpdf.com | / | prefix | /css/pdf.css | style |
| pqpdf.com | / | prefix | /css/site-nav.css | style |
| fated.org | / | prefix | /css/style.css | style |
| fated.org | / | prefix | /js/app.js | script |
No [otel] table is present in the loaded config, so the proxy falls back to the compiled defaults shown below โ tracing export is off.
Enable OTLP span export to tracing backend (Jaeger, Grafana Tempo, Honeycomb, etc.)
Service name shown in the tracing UI
OTLP HTTP/JSON endpoint โ use your collector or tracing backend
Sampling ratio: 1.0 = every trace, 0.1 = 10%, 0.0 = off. Uses ParentBased(TraceIdRatio).
Enable RFC 9111 compliant response cache
Maximum cache size in memory
Default TTL for cacheable responses without explicit Cache-Control
Maximum response body size to cache
Never cache responses that set cookies
Paths excluded from caching
Hosts whose responses are never cached, whatever the path
proxy-config.toml. No changes can be made from this interface.| 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 |
proxy-config.toml. No changes can be made from this interface.Enable load balancing (auto-enabled when backend_pools defined)
Default load balancing algorithm for all pools
- least_connections active
- round_robin
- weighted_round_robin
- random
- ip_hash
- least_response_time
Enable sticky sessions (session affinity via cookie)
Cookie name for sticky session tracking
Cookie TTL in seconds (0 = session cookie)
Use Secure cookies (HTTPS only)
Use HttpOnly cookies (prevent XSS access)
SameSite attribute for sticky session cookie
- lax cross-site allowed
- strict
- none requires Secure
Enable request queue for when all backends are saturated
Maximum queued requests before returning 503
Maximum wait time in queue before timeout
Gradually increase traffic to recovering backends
Duration of slow start ramp-up period
Initial traffic weight during slow start
Enable graceful connection draining on backend removal
Maximum drain time before forceful close
proxy-config.toml. No changes can be made from this interface.| 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-Ticket-Token, 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-Ticket-Token, 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-Ticket-Token, 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-Ticket-Token, 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-Ticket-Token, 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-Ticket-Token, 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-http3-scanner | api.pqcrypta.com | prefix:/http3-scanner | pqcrypta-api | HTTP | 20 | |
|
Routing
backend:pqcrypta-api
type:http
priority:20
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-Ticket-Token, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
|
||||||
| api-pqc-scanner | api.pqcrypta.com | prefix:/pqc-scanner | pqcrypta-api | HTTP | 20 | |
|
Routing
backend:pqcrypta-api
type:http
priority:20
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-Ticket-Token, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
|
||||||
| api-reports | api.pqcrypta.com | prefix:/reports | pqcrypta-api | HTTP | 10 | |
|
Routing
backend:pqcrypta-api
type:http
priority:10
allow_http11:true
CORS [routes.cors]
allow_origin:*
allow_methods:POST, OPTIONS
allow_headers:Content-Type
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-Ticket-Token, 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-Ticket-Token, 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-Ticket-Token, 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-Ticket-Token, 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)
|
||||||
| legacy-fun-cosmicfluidsimulation | pqcrypta.com | prefix:/encryption/fun/CosmicFluidSimulation | โ /fun/cosmicfluidsimulation | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/fun/cosmicfluidsimulation
permanent:301 (permanent)
|
||||||
| legacy-fun-cubeanimation | pqcrypta.com | prefix:/encryption/fun/CubeAnimation | โ /fun/cubeanimation | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/fun/cubeanimation
permanent:301 (permanent)
|
||||||
| legacy-fun-dynamicquantumgrid | pqcrypta.com | prefix:/encryption/fun/DynamicQuantumGrid | โ /fun/dynamicquantumgrid | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/fun/dynamicquantumgrid
permanent:301 (permanent)
|
||||||
| legacy-fun-fluidanimation | pqcrypta.com | prefix:/encryption/fun/FluidAnimation | โ /fun/fluidanimation | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/fun/fluidanimation
permanent:301 (permanent)
|
||||||
| legacy-fun-fractaltree | pqcrypta.com | prefix:/encryption/fun/FractalTree | โ /fun/fractaltree | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/fun/fractaltree
permanent:301 (permanent)
|
||||||
| legacy-fun-matrixrainanimation | pqcrypta.com | prefix:/encryption/fun/MatrixRainAnimation | โ /fun/matrixrainanimation | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/fun/matrixrainanimation
permanent:301 (permanent)
|
||||||
| legacy-fun-mysticgarden | pqcrypta.com | prefix:/encryption/fun/MysticGarden | โ /fun/backgrounds | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/fun/backgrounds
permanent:301 (permanent)
|
||||||
| legacy-fun-neuralchaosgrid | pqcrypta.com | prefix:/encryption/fun/NeuralChaosGrid | โ /fun/neuralchaosgrid | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/fun/neuralchaosgrid
permanent:301 (permanent)
|
||||||
| legacy-fun-neuralnetwork | pqcrypta.com | prefix:/encryption/fun/NeuralNetwork | โ /fun/neuralnetwork | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/fun/neuralnetwork
permanent:301 (permanent)
|
||||||
| legacy-fun-particlesystem | pqcrypta.com | prefix:/encryption/fun/ParticleSystem | โ /fun/particlesystem | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/fun/particlesystem
permanent:301 (permanent)
|
||||||
| legacy-fun | pqcrypta.com | prefix:/encryption/fun | โ /fun | 301 REDIRECT | 2 | |
|
Routing
backend:โ
type:redirect
priority:2
allow_http11:true
Redirect
redirect:/fun
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-cert-compression-tools | pqcrypta.com | prefix:/cert-compression/tools/ | apache | HTTP | 5 | |
|
Routing
backend:apache
type:http
priority:5
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
|
||||||
| main-http-smuggling-tools | pqcrypta.com | prefix:/http-smuggling/tools/ | apache | HTTP | 5 | |
|
Routing
backend:apache
type:http
priority:5
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
|
||||||
| 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
|
||||||
| pqc-host-chain-files | pqc.pqcrypta.com | prefix:/pqc/ | apache | HTTP | 5 | |
|
Routing
backend:apache
type:http
priority:5
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
|
||||||
| 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
|
||||||
proxy-config.toml. No changes can be made from this interface.Log verbosity level
- trace very verbose
- debug
- info active
- warn
- error errors only
Log output format
- json structured logging
- text human readable
Enable access logging for all requests
Access log file path (empty/commented = stdout)
Enable admin HTTP API for runtime management
Admin API bind address โ 127.0.0.1 for local-only access
Admin API listening port
Require mutual TLS for admin API connections
Bearer token for admin API authentication (AUD-01)
โโโโโโโโโโโโโโโโโโโโ [REDACTED]Allowed IP addresses for admin API access
proxy-config.toml. No changes can be made from this interface.# 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-10-22 09: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] udp_gro = false # 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://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 # The ML-DSA-87 chain t-cert-compression-pq serves, and its key. Verdicts on # that port quote the chain's size as loaded from here. pq_chain_cert = "/[system]/[key-material]" pq_chain_key = "[REDACTED]" # ============================================================================= # TLS Configuration # ============================================================================= [tls] # RFC 8879 certificate compression: "auto" (follow the TLS library) or "off". # Stated explicitly rather than left to the default so the knob is discoverable. certificate_compression = "auto" # 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", "conformance.pqcrypta.com", # ns1's DNS over TLS / QUIC certificate (Knot reads a copy: dns/tools/knot-cert-sync) "ns1.pqcrypta.com", # ECH's cover name (the configs' public_name): a client authenticates a # rejected ECH handshake with this certificate before it retries "ech.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" # Pure ML-KEM as well as the hybrids: CNSA 2.0 ends at ML-KEM-1024 alone. # Groups come in tuples by NIST level (MLKEM1024 with SecP384r1MLKEM1024, # MLKEM768 with the 768 hybrids), hybrids first within each. additional_kems = ["MLKEM1024", "MLKEM768"] # Groups the OQS provider adds without an IANA codepoint, offered over TCP after # standard ML-KEM and before P-384 (browsers offer none of them): FrodoKEM, the # conservative LWE KEM our frodokem-976/1344 engines implement, and HQC, the # code-based KEM NIST selected in 2025 (our hqc-128/192/256 engines). HQC was off # while liboqs carried the PQClean implementation CVE-2025-48946 concerned; # liboqs 0.16 replaced it with the official one (20250822 spec) and enables it. # Names are oqs-provider 0.12's (FrodoKEM at 0xFE31/0xFE2C, HQC at 0xFE39/0xFE36). experimental_groups = ["frodo1344shake", "hqc5", "frodo976shake", "hqc3"] # Fallback to classical TLS if PQC is unavailable fallback_to_classical = true # Refuse to start when PQC is enabled but its provider cannot be used, rather # than degrading to a classical-only listener with a warning. Split out of # fallback_to_classical, which conflated our own posture with client # compatibility: failing closed here used to require cutting off every # non-ML-KEM client, so nobody did it. require_pqc_provider = 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. Exceeding it calls block_ip() for 300s on # the FIRST offence. # # Until proxy 2026-10-03 this counted every REQUEST, not every connection, so it # was really a request limit: at 10 it banned Googlebot on every Search Console # render (269 rate-limit events in 24h against ordinary visitors), and at 50 a # visitor's first load of /encryption/ (53 requests on one HTTP/3 connection) # still earned a ban. A connection is now counted once, by its source port. # 50 real connections a second from one address is far beyond any browser. 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. # ============================================================================= # Security Settings # ============================================================================= [security] # Every backend here is on loopback by design (the proxy fronts services on # its own host); this acknowledges it instead of warning on each start. allow_internal_backends = true # Origins that may read the proxy's own 403/429 refusals cross-origin, on every # transport. Was compiled in (three disagreeing copies); now set here. refusal_cors_origins = ["https://pqcrypta.com", "https://www.pqcrypta.com", "https://pqpdf.com", "https://www.pqpdf.com"] # Exempt from IP/country/rate blocking so a blocked visitor can load the page # explaining the block. Was compiled in. error_pages_path_prefix = "/error_pages/" # Geo-blocked visitors are redirected here (served under error_pages_path_prefix). # Was compiled in; empty would refuse with a plain 403. geo_block_redirect_url = "https://pqcrypta.com/error_pages/pqcrypt_403.html" # How many distinct TLS fingerprints the observed corpus retains. # # This is the size limit on a published dataset, not just a memory guard: # observed.json is what /ja4/ serves, and that is the most-crawled path on the # site by a factor of eight. It was a hardcoded 50,000 until 2026-09-17, by # which point the corpus was sitting at exactly that number -- it had been # evicting its long tail, least-recently-seen first, which is precisely the # part a fingerprint directory exists to record. # # Still bounded, because anyone who can open a connection can feed it. At # roughly 500 bytes an entry this costs ~125 MB resident and the same on disk. # Eviction now logs at warn; it used to happen in silence. max_tracked_fingerprints = 250000 # 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] # Exempt from every scanner/bot User-Agent rule (regex over the lowercased # path): files every kind of crawler reads. Injection scanning still runs. # Setting this replaces the default, so the by-convention entries are listed # too. Downloads and APIs belong in programmatic_client_paths below. scanner_ua_exempt_paths = [ '^/robots\.txt$', '^/sitemap\.xml$', '^/llms\.txt$', '^/\.well-known/', '^/sitemaps/', ] # Downloads and APIs our pages tell people to fetch with a script: curl, Wget, # python-requests, Go, Java and an empty User-Agent pass, and scanners, # scrapers and headless browsers are still refused. In the list above until # 2026-10-18, which lifted every user-agent rule: pqc.pqcrypta.com/pqc/root-ca.crt # answered sqlmap with 200. The list above stays for what every kind of crawler # reads. programmatic_client_paths = [ '^/stream/downloads/', '^/ja4/api\.php$', '^/handshake/api\.php$', '^/pqc/.*\.(pem|crt|der|json)$', '^/masque/client/', ] # 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 over the raw request path; name rule # identifiers in `rules` and/or whole categories in `categories`. Anchor both # ends: an exclusion applies to the path, the query and the body. # Discovery-agent inventories. A certificate's CRL distribution points and AIA # entries are ldap://, ftp:// and http:// URLs, so every inventory of real # certificates carries them; PQW-SSRF-008 (ldap://) refused every batch from # fortress from 2026-09-01 and from api3 and mail from 2026-10-08, and the # agents reported it as a rejected API key. The API stores these URLs and never # fetches them (its only outbound request is the operator's SIEM webhook), so # SSRF is not a property this body can have. [[waf.exclusions]] path = "^/crypto-assets/scan/results$" categories = ["ssrf"] # Browser reports (NEL and the Reporting API, see the api-reports route). A CSP # violation report quotes the content it blocked, so a report about an inline # handler contains `onload=` and PQW-XSS-004 refused it: the reports describing # XSS-shaped violations were exactly the ones lost. They are stored as JSON and # shown on /monitor/ through textContent only. [[waf.exclusions]] path = "^/reports$" 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. The pages version their assets by # filemtime, so each versioned entry names its file as version_file and the proxy # adds ?v=<mtime> itself. Pinned values went stale with every edit: 10 of these 20 # named a version no page linked (2026-10-22), so each was fetched twice. # # 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. # Coalescing settings belong ABOVE the [[http3.preload_resources]] tables: after # them TOML files these keys under the last preload entry, where nothing reads # them. They sat there until 2026-09-23, so the exclusions below never applied. # 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-"] [[http3.preload_resources]] host = "stlweb.dev" path = "/" exact = true href = "/css/style.css" version_file = "/var/www/stlweb.dev/css/style.css" as_type = "style" [[http3.preload_resources]] host = "stlweb.dev" path = "/" exact = true href = "/js/main.js" version_file = "/var/www/stlweb.dev/js/main.js" as_type = "script" [[http3.preload_resources]] host = "stlweb.dev" path = "/about.php" href = "/css/style.css" version_file = "/var/www/stlweb.dev/css/style.css" as_type = "style" [[http3.preload_resources]] host = "stlweb.dev" path = "/about.php" href = "/js/main.js" version_file = "/var/www/stlweb.dev/js/main.js" as_type = "script" [[http3.preload_resources]] host = "stlweb.dev" path = "/services.php" href = "/css/style.css" version_file = "/var/www/stlweb.dev/css/style.css" as_type = "style" [[http3.preload_resources]] host = "stlweb.dev" path = "/services.php" href = "/js/main.js" version_file = "/var/www/stlweb.dev/js/main.js" as_type = "script" [[http3.preload_resources]] host = "stlweb.dev" path = "/work.php" href = "/css/style.css" version_file = "/var/www/stlweb.dev/css/style.css" as_type = "style" [[http3.preload_resources]] host = "stlweb.dev" path = "/work.php" href = "/js/main.js" version_file = "/var/www/stlweb.dev/js/main.js" as_type = "script" [[http3.preload_resources]] host = "stlweb.dev" path = "/contact.php" href = "/css/style.css" version_file = "/var/www/stlweb.dev/css/style.css" as_type = "style" [[http3.preload_resources]] host = "stlweb.dev" path = "/contact.php" href = "/js/main.js" version_file = "/var/www/stlweb.dev/js/main.js" as_type = "script" [[http3.preload_resources]] host = "stlweb.dev" path = "/legal/" href = "/css/style.css" version_file = "/var/www/stlweb.dev/css/style.css" as_type = "style" [[http3.preload_resources]] host = "stlweb.dev" path = "/legal/" href = "/js/main.js" version_file = "/var/www/stlweb.dev/js/main.js" as_type = "script" # O'Fallon Concrete proposal: its own render-blocking assets. version_file gives # the hrefs the same filemtime version that asset() emits in _head.php. # 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" version_file = "/var/www/stlweb.dev/proposals/ofallonconcreteco.com/css/style.css" as_type = "style" [[http3.preload_resources]] host = "stlweb.dev" path = "/proposals/ofallonconcreteco.com/" href = "/proposals/ofallonconcreteco.com/js/head.js" version_file = "/var/www/stlweb.dev/proposals/ofallonconcreteco.com/js/head.js" 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" version_file = "/[data]/..." as_type = "style" [[http3.preload_resources]] host = "pqcrypta.com" path = "/" exact = true href = "/css/homepage.css" version_file = "/[data]/..." as_type = "style" # pqpdf.com: main stylesheet + site nav (filemtime-versioned: version_file). [[http3.preload_resources]] host = "pqpdf.com" path = "/" href = "/css/pdf.css" version_file = "/var/www/pqpdf/css/pdf.css" as_type = "style" [[http3.preload_resources]] host = "pqpdf.com" path = "/" href = "/css/site-nav.css" version_file = "/var/www/pqpdf/css/site-nav.css" as_type = "style" # fated.org: stylesheet + app script (unversioned, always matches). [[http3.preload_resources]] host = "fated.org" path = "/" href = "/css/style.css" version_file = "/var/www/fated/css/style.css" as_type = "style" [[http3.preload_resources]] host = "fated.org" path = "/" href = "/js/app.js" version_file = "/var/www/fated/js/app.js" as_type = "script" # ============================================================================= # 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"] 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 # The chatbot's five-minute timeout is `timeout_override_ms` on the api-chatbot # route; a `chatbot_timeout_ms` here was not a setting and applied to nothing. # 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 # ============================================================================= # 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" allow_credentialed_clients = "[REDACTED]" 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-Ticket-Token", "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" allow_credentialed_clients = "[REDACTED]" 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-Ticket-Token", "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" allow_credentialed_clients = "[REDACTED]" 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-Ticket-Token", "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" allow_credentialed_clients = "[REDACTED]" 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-Ticket-Token", "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" allow_credentialed_clients = "[REDACTED]" 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-Ticket-Token", "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" allow_credentialed_clients = "[REDACTED]" 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-Ticket-Token", "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 # Documented scanner API, reachable by non-browser clients. # # /http3-scanner/{scan,corpus,stats,timeline} is an API: the browser page at # pqcrypta.com/http3-quic/ is one client of it, not the only sanctioned one. # Falling through to api-default meant the WAF's scanner/bot User-Agent rules # applied, so `curl`, python-requests and every SDK got 403 while a browser # User-Agent with the same API key got 200 โ the block was on the User-Agent, # not on auth. # # allow_programmatic_clients lifts only the generic-client rules (curl, Wget, # python-requests/urllib, Go, Java, no User-Agent), key or no key. Attack # tools, scrapers and headless browsers stay blocked: this route carried # skip_bot_blocking until 2026-10-18, which let sqlmap and nikto through with # curl. Anomaly detection, scanner-probe paths, payload, header and body # scanning, rate limiting and the API-key check on /scan all still run. # # The CORS block is copied from api-default rather than inherited: CORS is # per-route, and without it this higher-priority route would break the browser # page it was serving before. [[routes]] name = "api-http3-scanner" allow_http11 = true host = "api.pqcrypta.com" path_prefix = "/http3-scanner" webtransport = false backend = "pqcrypta-api" allow_credentialed_clients = "[REDACTED]" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 20 allow_programmatic_clients = true [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-Ticket-Token", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"] allow_credentials = "[REDACTED]" max_age = 86400 # The PQC scanner's API, as api-http3-scanner above is the HTTP/3 scanner's. # # /pqc-ready/ publishes /pqc-scanner/corpus as a CC BY 4.0 dataset (schema.org # DataDownload, "Every figure, as JSON") and its page reads /timeline from the # browser. Falling through to api-default, the bad-bot User-Agent rules 403'd # curl and python-requests on both from outside (checked from the mail host, # 2026-10-18), so the published dataset could be downloaded by nothing but a # browser. Same flag, same limits as the route above. [[routes]] name = "api-pqc-scanner" allow_http11 = true host = "api.pqcrypta.com" path_prefix = "/pqc-scanner" webtransport = false backend = "pqcrypta-api" allow_credentialed_clients = "[REDACTED]" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 20 allow_programmatic_clients = true [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-Ticket-Token", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"] allow_credentials = "[REDACTED]" max_age = 86400 # Browser reports (NEL and the Reporting API). Every site behind the proxy # sends Report-To naming this URL, so a delivery may come from any of their # origins: the endpoint is write-only and takes no credentials, so any origin # may post. api-default's origin list left out fated.org and the other nodes' # sites, whose reports a browser would have dropped at the preflight. [[routes]] name = "api-reports" allow_http11 = true host = "api.pqcrypta.com" path_prefix = "/reports" webtransport = false backend = "pqcrypta-api" priority = 10 [routes.cors] allow_origin = "*" allow_methods = ["POST", "OPTIONS"] allow_headers = ["Content-Type"] 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" allow_credentialed_clients = "[REDACTED]" 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-Ticket-Token", "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-Ticket-Token", "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-Ticket-Token", "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-Ticket-Token", "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 # The old /encryption/fun/ copy of the animations: each one lives on under # /fun/ (Mystic Garden as /fun/backgrounds/). Each page outranks the catch-all. [[routes]] name = "legacy-fun-cosmicfluidsimulation" allow_http11 = true host = "pqcrypta.com" path_prefix = "/encryption/fun/CosmicFluidSimulation" redirect = "/fun/cosmicfluidsimulation" redirect_permanent = true priority = 1 [[routes]] name = "legacy-fun-cubeanimation" allow_http11 = true host = "pqcrypta.com" path_prefix = "/encryption/fun/CubeAnimation" redirect = "/fun/cubeanimation" redirect_permanent = true priority = 1 [[routes]] name = "legacy-fun-dynamicquantumgrid" allow_http11 = true host = "pqcrypta.com" path_prefix = "/encryption/fun/DynamicQuantumGrid" redirect = "/fun/dynamicquantumgrid" redirect_permanent = true priority = 1 [[routes]] name = "legacy-fun-fluidanimation" allow_http11 = true host = "pqcrypta.com" path_prefix = "/encryption/fun/FluidAnimation" redirect = "/fun/fluidanimation" redirect_permanent = true priority = 1 [[routes]] name = "legacy-fun-fractaltree" allow_http11 = true host = "pqcrypta.com" path_prefix = "/encryption/fun/FractalTree" redirect = "/fun/fractaltree" redirect_permanent = true priority = 1 [[routes]] name = "legacy-fun-matrixrainanimation" allow_http11 = true host = "pqcrypta.com" path_prefix = "/encryption/fun/MatrixRainAnimation" redirect = "/fun/matrixrainanimation" redirect_permanent = true priority = 1 [[routes]] name = "legacy-fun-mysticgarden" allow_http11 = true host = "pqcrypta.com" path_prefix = "/encryption/fun/MysticGarden" redirect = "/fun/backgrounds" redirect_permanent = true priority = 1 [[routes]] name = "legacy-fun-neuralchaosgrid" allow_http11 = true host = "pqcrypta.com" path_prefix = "/encryption/fun/NeuralChaosGrid" redirect = "/fun/neuralchaosgrid" redirect_permanent = true priority = 1 [[routes]] name = "legacy-fun-neuralnetwork" allow_http11 = true host = "pqcrypta.com" path_prefix = "/encryption/fun/NeuralNetwork" redirect = "/fun/neuralnetwork" redirect_permanent = true priority = 1 [[routes]] name = "legacy-fun-particlesystem" allow_http11 = true host = "pqcrypta.com" path_prefix = "/encryption/fun/ParticleSystem" redirect = "/fun/particlesystem" redirect_permanent = true priority = 1 [[routes]] name = "legacy-fun" allow_http11 = true host = "pqcrypta.com" path_prefix = "/encryption/fun" redirect = "/fun" redirect_permanent = true priority = 2 # 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 # /cert-compression/tools/ publishes verify-cert-compression.py, which the page # tells readers to download and run against their own servers. That audience # reaches for curl and wget, and both were getting 403: the WAF's bad-bot # User-Agent family blocks non-browser agents site-wide, so the browser button # worked while `curl -O` on the same URL did not. A download nobody can script # is not a download. Same reasoning as api-http3-scanner and /stream/downloads/: # the traffic this path exists for is non-browser by definition. # # allow_programmatic_clients lifts only the generic-client User-Agent rules # (curl, Wget, python-requests/urllib, Go, Java, no User-Agent); attack tools, # scrapers and headless browsers stay blocked, which skip_bot_blocking (here # until 2026-10-18) let through. Anomaly scoring, scanner-probe paths, # payload/header/body scanning and rate limiting all still run, and the path # serves one static read-only file. [[routes]] name = "main-cert-compression-tools" allow_http11 = true host = "pqcrypta.com" path_prefix = "/cert-compression/tools/" webtransport = false backend = "apache" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 5 allow_programmatic_clients = true # /http-smuggling/tools/ publishes smuggle-probe.sh, which the article tells # readers to download and run against their own proxies. Same reasoning as the # cert-compression tools route above: a download the bad-bot User-Agent rule # blocks for curl and wget is not a download. allow_programmatic_clients lifts # only the generic-client UA rules; scanners stay blocked and every other WAF # check still applies. [[routes]] name = "main-http-smuggling-tools" allow_http11 = true host = "pqcrypta.com" path_prefix = "/http-smuggling/tools/" webtransport = false backend = "apache" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 5 allow_programmatic_clients = true [[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" # Grafana's cookies were rewritten with this Domain by code that matched the # /grafana path on HTTP/3 only. Now a route setting, applied on every transport. set_cookie_domain = "pqcrypta.com" 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" # pqc.pqcrypta.com serves the /pqc/ hierarchy files over its ML-DSA-87-only # TLS: the /pqc/ page tells readers to fetch them with curl over a post-quantum # handshake (--sigalgs mldsa87 --curves MLKEM1024), and the host had no route # at all, so every request was a 404. Only /pqc/; allow_programmatic_clients # for the same reason as the tools routes above (a download curl cannot fetch # is not one) -- it lifts only the generic-client User-Agent rules. [[routes]] name = "pqc-host-chain-files" allow_http11 = true host = "pqc.pqcrypta.com" path_prefix = "/pqc/" webtransport = false backend = "apache" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 5 allow_programmatic_clients = true # 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 # ============================================================================= # 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