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.
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)
None configuredServe 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)
Applied as a secondary check inside the request handler after route matching, in addition to the global limits above — on HTTP/1.1, HTTP/2 and HTTP/3 alike. Each route gets its own bucket, keyed by route name, so other traffic from the same client cannot drain a route's budget. exempt_keys lists key values this route does not limit at all.
| Route | Pattern | Req/s | Burst | Req/min | Req/hr | Exempt keys |
|---|---|---|---|---|---|---|
| office-login | /api/method/login | โ | โ | โ | โ | โ |
proxy-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
None configuredPaths never coalesced โ APIs, WebSocket upgrades and streaming endpoints
None configuredAn href is only used by the browser if it byte-matches the URL in the page HTML, cache-buster included. Versioned entries are pinned to the file's current mtime and must be re-pinned when that file changes.
| Host | Path | Match | Preloaded href | as |
|---|---|---|---|---|
| stlweb.dev | / | exact | /css/style.css?v=1788490295 | style |
| stlweb.dev | / | exact | /js/main.js?v=1778453331 | script |
| stlweb.dev | /about.php | prefix | /css/style.css?v=1788490295 | style |
| stlweb.dev | /about.php | prefix | /js/main.js?v=1778453331 | script |
| stlweb.dev | /services.php | prefix | /css/style.css?v=1788490295 | style |
| stlweb.dev | /services.php | prefix | /js/main.js?v=1778453331 | script |
| stlweb.dev | /work.php | prefix | /css/style.css?v=1788490295 | style |
| stlweb.dev | /work.php | prefix | /js/main.js?v=1778453331 | script |
| stlweb.dev | /contact.php | prefix | /css/style.css?v=1788490295 | style |
| stlweb.dev | /contact.php | prefix | /js/main.js?v=1778453331 | script |
| stlweb.dev | /legal/ | prefix | /css/style.css?v=1788490295 | style |
| stlweb.dev | /legal/ | prefix | /js/main.js?v=1778453331 | script |
| stlweb.dev | /proposals/ofallonconcreteco.com/ | prefix | /proposals/ofallonconcreteco.com/css/style.css?v=1788489646 | style |
| stlweb.dev | /proposals/ofallonconcreteco.com/ | prefix | /proposals/ofallonconcreteco.com/js/head.js?v=1788405277 | script |
| pqcrypta.com | / | exact | /css/pqcrypta-loader.css?v=1767900410 | style |
| pqcrypta.com | / | exact | /css/homepage.css?v=1783392618 | style |
| pqpdf.com | / | prefix | /css/pdf.css?v=1779328120 | style |
| pqpdf.com | / | prefix | /css/site-nav.css?v=1780707281 | style |
| fated.org | / | prefix | /css/style.css?v=1788490295 | style |
| fated.org | / | prefix | /js/app.js | script |
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-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
|
||||||
| webtransport-encrypt | api.pqcrypta.com | prefix:/encrypt | pqcrypta-api | WebTransport | 10 | |
|
Routing
backend:pqcrypta-api
type:webtransport
priority:10
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
Add Headers [routes.add_headers]
X-Forwarded-Proto:https
X-WebTransport:true
|
||||||
| webtransport-decrypt | api.pqcrypta.com | prefix:/decrypt | pqcrypta-api | WebTransport | 10 | |
|
Routing
backend:pqcrypta-api
type:webtransport
priority:10
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
|
||||||
| webtransport-keys | api.pqcrypta.com | prefix:/keys | pqcrypta-api | WebTransport | 10 | |
|
Routing
backend:pqcrypta-api
type:webtransport
priority:10
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
|
||||||
| webtransport-stream | api.pqcrypta.com | prefix:/stream | pqcrypta-api | WebTransport | 10 | |
|
Routing
backend:pqcrypta-api
type:webtransport
priority:10
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
|
||||||
| webtransport-default | api.pqcrypta.com | prefix:/webtransport | pqcrypta-api | WebTransport | 50 | |
|
Routing
backend:pqcrypta-api
type:webtransport
priority:50
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
|
||||||
| api-prometheus | api.pqcrypta.com | prefix:/prometheus | prometheus | HTTP | 15 | |
|
Routing
backend:prometheus
type:http
priority:15
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, OPTIONS
allow_headers:Content-Type, Authorization, X-API-Key
allow_credentials:true
max_age:86400s
|
||||||
| api-default | api.pqcrypta.com | prefix:/ | pqcrypta-api | HTTP | 100 | |
|
Routing
backend:pqcrypta-api
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
CORS [routes.cors]
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
|
||||||
| main-webtransport | pqcrypta.com | prefix:/webtransport | pqcrypta-api | WebTransport | 5 | |
|
Routing
backend:pqcrypta-api
type:webtransport
priority:5
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
|
||||||
| main-webtransport-root | pqcrypta.com | exact:/ | pqcrypta-api | WebTransport | 5 | |
|
Routing
backend:pqcrypta-api
type:webtransport
priority:5
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
|
||||||
| www-webtransport | www.pqcrypta.com | prefix:/webtransport | pqcrypta-api | WebTransport | 5 | |
|
Routing
backend:pqcrypta-api
type:webtransport
priority:5
stream_to_method:POST
forward_client_identity:true
client_identity_header:X-Client-IP
CORS [routes.cors]
allow_origin:https://pqcrypta.com
allow_methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
allow_headers:Content-Type, Authorization, X-Requested-With, X-API-Key, X-Forwarded-For, X-Verification-Version, X-Analysis-Type, Cache-Control, signature-agent, signature-input, signature
allow_credentials:true
max_age:86400s
|
||||||
| seo-redirect-http3_quic | pqcrypta.com | prefix:/http3_quic | โ /http3-quic | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/http3-quic
permanent:301 (permanent)
|
||||||
| seo-redirect-pqc_ready | pqcrypta.com | prefix:/pqc_ready | โ /pqc-ready | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/pqc-ready
permanent:301 (permanent)
|
||||||
| seo-redirect-key_vault | pqcrypta.com | prefix:/key_vault | โ /key-vault | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/key-vault
permanent:301 (permanent)
|
||||||
| seo-redirect-security_systems | pqcrypta.com | prefix:/security_systems | โ /security-systems | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/security-systems
permanent:301 (permanent)
|
||||||
| seo-redirect-bot_threat_remediation | pqcrypta.com | prefix:/bot_threat_remediation | โ /bot-threat-remediation | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/bot-threat-remediation
permanent:301 (permanent)
|
||||||
| seo-redirect-threat_bot | pqcrypta.com | prefix:/threat_bot | โ /threat-bot | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
allow_http11:true
Redirect
redirect:/threat-bot
permanent:301 (permanent)
|
||||||
| speedtest-tcp | pqcrypta.com | regex:^/speedtest/tcp-(ping|download|upload)\.php | apache | HTTP | 3 | |
|
Routing
backend:apache
type:http
priority:3
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:no-store, no-cache, must-revalidate
X-Accel-Buffering:no
|
||||||
| main-sitemaps | pqcrypta.com | regex:^/sitemaps/.*\.xml$ | apache | HTTP | 5 | |
|
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
X-Robots-Tag:noindex, follow
Cache-Control:public, max-age=3600
|
||||||
| main-robots | pqcrypta.com | exact:/robots.txt | apache | HTTP | 5 | |
|
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
Cache-Control:public, max-age=3600
|
||||||
| main-fonts | pqcrypta.com | regex:\.(woff|woff2|ttf|eot|otf)$ | apache | HTTP | 5 | |
|
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:public, max-age=31536000
Access-Control-Allow-Origin:*
|
||||||
| main-audio | pqcrypta.com | regex:\.(mp3|ogg|wav|m4a|aac|flac)$ | apache | HTTP | 5 | |
|
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
Access-Control-Allow-Methods:GET, HEAD, POST, OPTIONS
Access-Control-Allow-Headers:Range, Content-Range, Content-Type, Accept, User-Agent, X-API-Key, signature-agent
Access-Control-Expose-Headers:Content-Range, Content-Length, Accept-Ranges
|
||||||
| main-billing | pqcrypta.com | prefix:/dashboard/billing/ | apache | HTTP | 5 | |
|
Routing
backend:apache
type:http
priority:5
allow_http11:true
stripe_compatibility:true โ removes COEP/COOP headers
|
||||||
| main-api-config | pqcrypta.com | exact:/api_config.php | apache | HTTP | 5 | |
|
Routing
backend:apache
type:http
priority:5
allow_http11:true
skip_bot_blocking:true
Security [routes.security]
skip_bot_blocking:yes โ scanner/bot user-agent checks bypassed for this route
|
||||||
| main-prometheus | pqcrypta.com | prefix:/prometheus | prometheus | HTTP | 3 | |
|
Routing
backend:prometheus
type:http
priority:3
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
Headers Override [routes.headers_override]
cross_origin_embedder_policy:unsafe-none
cross_origin_opener_policy:unsafe-none
cross_origin_resource_policy:cross-origin
|
||||||
| main-grafana | pqcrypta.com | prefix:/grafana | grafana | HTTP | 3 | |
|
Routing
backend:grafana
type:http
priority:3
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
Headers Override [routes.headers_override]
cross_origin_embedder_policy:unsafe-none
cross_origin_opener_policy:unsafe-none
cross_origin_resource_policy:cross-origin
|
||||||
| main-default | pqcrypta.com | prefix:/ | apache | HTTP | 100 | |
|
Routing
backend:apache
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
|
||||||
| www-default | www.pqcrypta.com | prefix:/ | apache | HTTP | 100 | |
|
Routing
backend:apache
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
|
||||||
| api-robots | api.pqcrypta.com | exact:/robots.txt | apache | HTTP | 1 | |
|
Routing
backend:apache
type:http
priority:1
allow_http11:true
|
||||||
| pqpdf-sitemaps | pqpdf.com | regex:^/sitemaps/.*\.xml$ | apache | HTTP | 5 | |
|
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
X-Robots-Tag:noindex, follow
Cache-Control:public, max-age=3600
|
||||||
| pqpdf-sitemap | pqpdf.com | exact:/sitemap.xml | apache | HTTP | 200 | |
|
Routing
backend:apache
type:http
priority:200
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
Cache-Control:public, max-age=3600
|
||||||
| pqpdf-robots | pqpdf.com | exact:/robots.txt | apache | HTTP | 200 | |
|
Routing
backend:apache
type:http
priority:200
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
Cache-Control:public, max-age=3600
|
||||||
| pqpdf-webtransport | pqpdf.com | prefix:/webtransport | apache | WebTransport | 3 | |
|
Routing
backend:apache
type:webtransport
priority:3
stream_to_method:POST
|
||||||
| pqpdf-default | pqpdf.com | prefix:/ | apache | HTTP | 100 | |
|
Routing
backend:apache
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
|
||||||
| www-pqpdf-webtransport | www.pqpdf.com | prefix:/webtransport | apache | WebTransport | 3 | |
|
Routing
backend:apache
type:webtransport
priority:3
stream_to_method:POST
|
||||||
| www-pqpdf-default | www.pqpdf.com | prefix:/ | apache | HTTP | 100 | |
|
Routing
backend:apache
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
|
||||||
| fated-webtransport | fated.org | prefix:/webtransport | apache | WebTransport | 3 | |
|
Routing
backend:apache
type:webtransport
priority:3
stream_to_method:POST
|
||||||
| fated-default | fated.org | prefix:/ | apache | HTTP | 100 | |
|
Routing
backend:apache
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
|
||||||
| www-fated-webtransport | www.fated.org | prefix:/webtransport | apache | WebTransport | 3 | |
|
Routing
backend:apache
type:webtransport
priority:3
stream_to_method:POST
|
||||||
| www-fated-default | www.fated.org | prefix:/ | apache | HTTP | โ | |
|
Routing
backend:apache
type:http
priority:โ
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
|
||||||
| pqpdf-api-default | api.pqpdf.com | prefix:/ | pqpdf-api | HTTP | 100 | |
|
Routing
backend:pqpdf-api
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
CORS [routes.cors]
allow_methods:GET, POST, DELETE, OPTIONS
allow_headers:Content-Type, X-API-Key, X-Session-Id, Authorization
allow_credentials:true
max_age:86400s
|
||||||
| stlweb-webtransport | stlweb.dev | prefix:/webtransport | apache | WebTransport | 3 | |
|
Routing
backend:apache
type:webtransport
priority:3
stream_to_method:POST
|
||||||
| stlweb-robots | stlweb.dev | exact:/robots.txt | apache | HTTP | 5 | |
|
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
Cache-Control:public, max-age=3600
|
||||||
| stlweb-sitemap | stlweb.dev | exact:/sitemap.xml | apache | HTTP | 5 | |
|
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cross-Origin-Resource-Policy:cross-origin
Access-Control-Allow-Origin:*
Cache-Control:public, max-age=3600
|
||||||
| stlweb-fonts | stlweb.dev | regex:\.(woff|woff2|ttf|eot|otf)$ | apache | HTTP | 5 | |
|
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:public, max-age=31536000
Access-Control-Allow-Origin:*
|
||||||
| stlweb-css | stlweb.dev | regex:\.css$ | apache | HTTP | 4 | |
|
Routing
backend:apache
type:http
priority:4
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:public, max-age=2592000
|
||||||
| stlweb-js | stlweb.dev | regex:\.js$ | apache | HTTP | 4 | |
|
Routing
backend:apache
type:http
priority:4
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:public, max-age=2592000
|
||||||
| stlweb-static | stlweb.dev | regex:\.(webp|avif|jpg|jpeg|png|gif|ico|svg)$ | apache | HTTP | 5 | |
|
Routing
backend:apache
type:http
priority:5
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:public, max-age=2592000
|
||||||
| stlweb-contact-submit | stlweb.dev | exact:/contact/submit.php | apache | HTTP | 3 | |
|
Routing
backend:apache
type:http
priority:3
allow_http11:true
skip_bot_blocking:true
Security [routes.security]
skip_bot_blocking:yes โ scanner/bot user-agent checks bypassed for this route
|
||||||
| www-stlweb-default | www.stlweb.dev | prefix:/ | apache | HTTP | 100 | |
|
Routing
backend:apache
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
|
||||||
| stlweb-default | stlweb.dev | prefix:/ | apache | HTTP | 100 | |
|
Routing
backend:apache
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
allow_http11:true
|
||||||
| office-wt-speedtest | office.stlweb.dev | exact:/speedtest | apache | WebTransport | 1 | |
|
Routing
backend:apache
type:webtransport
priority:1
stream_to_method:โ
|
||||||
| office-wt-telemetry | office.stlweb.dev | exact:/telemetry | apache | WebTransport | 1 | |
|
Routing
backend:apache
type:webtransport
priority:1
stream_to_method:โ
|
||||||
| office-wt-root | office.stlweb.dev | prefix:/webtransport | apache | WebTransport | 2 | |
|
Routing
backend:apache
type:webtransport
priority:2
stream_to_method:โ
|
||||||
| office-assets | office.stlweb.dev | prefix:/assets/ | apache | HTTP | 20 | |
|
Routing
backend:apache
type:http
priority:20
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:public, max-age=31536000, immutable
Cross-Origin-Resource-Policy:cross-origin
|
||||||
| office-login | office.stlweb.dev | exact:/api/method/login | apache | HTTP | 15 | |
|
Routing
backend:apache
type:http
priority:15
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:no-store
Cross-Origin-Resource-Policy:cross-origin
Cross-Origin-Opener-Policy:unsafe-none
|
||||||
| office-api | office.stlweb.dev | prefix:/api/ | apache | HTTP | 20 | |
|
Routing
backend:apache
type:http
priority:20
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:no-store
Cross-Origin-Resource-Policy:cross-origin
Cross-Origin-Opener-Policy:unsafe-none
|
||||||
| office-socketio | office.stlweb.dev | prefix:/socket.io/ | office-node | HTTP | 20 | |
|
Routing
backend:office-node
type:http
priority:20
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:no-store
|
||||||
| office-files | office.stlweb.dev | prefix:/files/ | apache | HTTP | 20 | |
|
Routing
backend:apache
type:http
priority:20
allow_http11:true
Headers Override [routes.headers_override]
Cache-Control:no-store, private
|
||||||
| office-app | office.stlweb.dev | prefix:/ | apache | HTTP | 100 | |
|
Routing
backend:apache
type:http
priority:100
allow_http11:true
Headers Override [routes.headers_override]
X-Frame-Options:SAMEORIGIN
Cache-Control:no-store, no-cache
Cross-Origin-Opener-Policy:unsafe-none
Cross-Origin-Resource-Policy:cross-origin
|
||||||
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-09-07 07:17:01 UTC # PQCrypta Proxy Configuration - Production # ========================================== # Complete configuration with all available options. # Handles all HTTP/HTTPS/HTTP3/WebTransport traffic on ports 443, 4433, 4434 # ============================================================================= # Server Configuration # ============================================================================= [server] # Bind address for QUIC/UDP and TCP listeners. # "[::]" is dual-stack: with net.ipv6.bindv6only=0 it accepts BOTH IPv6 and # IPv4 (as v4-mapped). Brackets are required โ the listeners build addresses # as format!("{bind_address}:{port}"), so a bare "::" would yield the # unparseable ":::443". # Previously "[redacted-ip]" (IPv4 only) while DNS published an AAAA record, so # IPv6-preferring clients hit a refused port: TCP fell back to IPv4 via Happy # Eyeballs (HTTP still worked) but QUIC/UDP simply failed, which is why # external HTTP/3 checkers reported "QUIC connection could not be established". bind_address = "[::]" # Primary UDP port for QUIC/HTTP3/WebTransport udp_port = 443 # Additional ports to listen on (all support QUIC/HTTP3/WebTransport) additional_ports = ["[configured]"] # Allowed WebTransport origins (browser Origin header validation) # Empty list = reject all browser connections. Must list every domain that # opens a WebTransport session from a browser context. webtransport_allowed_origins = ["https://pqcrypta.com", "https://api.pqcrypta.com", "https://pqpdf.com", "https://www.pqpdf.com", "https://stlweb.dev", "https://office.stlweb.dev", "https://fated.org", "https://www.fated.org"] # Hosts that must never advertise HTTP/3 upgrade. # Responses to these hosts include "Alt-Svc: clear" to evict any cached QUIC # upgrade in the browser, ensuring TCP/TLS is always used. tcp_only_hosts = ["tcp.pqcrypta.com"] # Hosts that must negotiate HTTP/1.1 only (no h2 in ALPN). # Prevents browser HTTP/2 connection coalescing so each parallel fetch() stream # opens an independent TCP connection. Required for accurate TCP speed tests โ # all 6 streams would otherwise share one HTTP/2 pipe and stall together. http11_only_hosts = ["tcp.pqcrypta.com", "api.pqpdf.com"] # Maximum concurrent connections max_connections = 10000 # Maximum concurrent BIDIRECTIONAL streams per connection (HTTP/3 requests) max_streams_per_connection = 1000 # Maximum concurrent UNIDIRECTIONAL streams per connection. HTTP/3 needs exactly # three (control + the two QPACK streams); WebTransport sessions open a few more. # This used to inherit the bidirectional figure, advertising 1000. max_uni_streams_per_connection = 100 # Keep-alive interval in seconds keepalive_interval_secs = 15 # Maximum idle timeout in seconds max_idle_timeout_secs = 120 # Enable IPv6 dual-stack binding enable_ipv6 = true # Worker threads (0 = auto-detect based on CPU cores) worker_threads = 0 # Enable the QUIC ACK Frequency extension (draft-ietf-quic-ack-frequency). # Fewer, batched ACKs cut overhead on high-throughput connections (speedtest, # bulk transfer). Negotiated, so clients that lack it are unaffected. enable_ack_frequency = true # Enable QUIC Retry for explicit source-address validation (RFC 9000 ยง8.1.2). # A new, unvalidated connection is answered with a Retry token the client must # echo before the handshake proceeds - hardening against spoofed-source # amplification/DDoS at the cost of one extra round trip per new connection. # When false, the server uses RFC 9000 implicit validation (the 3x # anti-amplification limit) with no per-connection latency. enable_quic_retry = true # Maximum concurrent data-carrying paths per connection # (draft-ietf-quic-multipath), served by the noq QUIC stack. Paths are created, # validated and torn down automatically once the peer negotiates multipath; # peers that do not advertise the extension are unaffected by this value. # 1 keeps every connection single-path, 0 stops multipath being negotiated. max_concurrent_multipath_paths = 4 # Disable proxy-level path lowercasing โ stlweb.dev Apache handles its own # lowercase enforcement via mod_rewrite 301; ERPNext uses case-sensitive hashes normalize_paths = false # ============================================================================= # MASQUE / CONNECT-UDP (RFC 9298) # ============================================================================= # UDP proxying over HTTP/3 Extended CONNECT. Disabled by default; an open UDP # relay is an amplification/SSRF risk, so targets must be explicitly allowlisted. # # A public RFC 9298 relay, documented at https://pqcrypta.com/masque/. There is # effectively no free public MASQUE target on the internet, so people writing # CONNECT-UDP clients have nothing to test against; this is that target. # # The allowlist is the entire safety model, and it is why this can be public at # all: a caller cannot aim the relay at a victim of their choosing, because the # destination has to appear below. Entries are pinned to well-known public # resolvers on their DNS ports only. Never add "*" to either half of an entry. [masque] enabled = true allowed_targets = [ # Local stub resolver (systemd-resolved) โ the original loopback test target, # still used by the HTTP/3 scanner's DNS-over-CONNECT-UDP round trip check. "[loopback]:53", # Cloudflare: Do53 and DNS-over-QUIC (RFC 9250). "[redacted-ip]:53", "[redacted-ip]:53", "[redacted-ip]:853", # Google Public DNS. "[redacted-ip]:53", "[redacted-ip]:53", # Quad9: Do53 and DNS-over-QUIC. "[redacted-ip]:53", "[redacted-ip]:853", ] session_idle_timeout_secs = 60 max_sessions_per_connection = 8 # ============================================================================= # HTTP/3 + QUIC client conformance suite # ============================================================================= # Serves deliberately awkward-but-legal protocol output so a client library can # find out how it copes: reserved frame types, a duplicated SETTINGS identifier, # a black-holed path MTU. The server records what the client did and scores it. # # Every test owns one UDP port from `port_range`; the listener knows which test # it is serving from its own local_addr(). `host` serves the catalogue, the # reports and the badge over ordinary HTTPS โ it does not select tests. # # `port_range` must not overlap server.udp_port (443) or additional_ports # (4434); startup refuses rather than warns, because an overlap would hand # ordinary visitors malformed protocol output. It must also be wide enough for # the whole catalogue, or the suite would silently serve a truncated one. [conformance] enabled = true host = "conformance.pqcrypta.com" port_range = [4460, 4600] session_ttl_secs = 3600 max_sessions = 512 liveness_timeout_ms = 5000 # ============================================================================= # TLS Configuration # ============================================================================= [tls] # Path to TLS certificate chain (PEM format) cert_path = "/[system]/[key-material]" # Path to TLS private key (PEM format) key_path = "[REDACTED]" # Optional: CA certificate for client verification (mTLS) # ca_cert_path = "/[system]/[key-material]" # Require client certificates (mTLS mode) require_client_cert = false # ALPN protocols to advertise alpn_protocols = ["h3", "h2", "http/1.1", "webtransport"] # Minimum TLS version (only "1.3" supported for QUIC) min_version = "1.3" # Enable OCSP stapling ocsp_stapling = true # Certificate reload interval in seconds (0 = disabled) cert_reload_interval_secs = 3600 # Issue TLS 1.3 session tickets sealed with ML-KEM-1024 (FIPS 203). Each ticket # carries its own encapsulation; the keypair rolls every # session_ticket_lifetime_secs with one generation of overlap. pqc_session_tickets = true session_ticket_lifetime_secs = 43200 # Enable 0-RTT (early data) for faster reconnections # SECURITY WARNING: 0-RTT is vulnerable to replay attacks! enable_0rtt = false # ============================================================================= # OCSP Stapling Configuration # ============================================================================= [ocsp] # Enable OCSP stapling service enabled = true # Cache duration for OCSP responses (seconds) cache_duration_secs = 3600 # Refresh OCSP response before expiry (seconds) refresh_before_expiry_secs = 300 # OCSP request timeout (seconds) timeout_secs = 10 # Maximum retries for OCSP requests max_retries = 3 # Retry delay between attempts (milliseconds) retry_delay_ms = 1000 # ============================================================================= # ACME Certificate Automation (Let's Encrypt) # ============================================================================= [acme] # Enable ACME certificate automation enabled = true # Domains to manage certificates for domains = ["pqcrypta.com", "www.pqcrypta.com", "api.pqcrypta.com", "pqpdf.com", "www.pqpdf.com", "api.pqpdf.com", "fated.org", "www.fated.org", "tcp.pqcrypta.com", "stlweb.dev", "www.stlweb.dev", "office.stlweb.dev", "conformance.pqcrypta.com"] # Contact email for Let's Encrypt notifications email = "[email withheld]" # ACME directory URL (Let's Encrypt production) directory_url = "https://acme-v02.api.letsencrypt.org/directory" # Challenge type: "http-01" or "dns-01" challenge_type = "http-01" # Directory to store certificates certs_path = "/[system]/certs" use_ecdsa = true # P-384 = secp384r1 = 192-bit = 7680-bit RSA equivalent = 100% SSL Labs key exchange score # P-256 = secp256r1 = 128-bit = 3072-bit RSA equivalent = 90% SSL Labs key exchange score ecdsa_curve = "P-384" # Renew certificates this many days before expiry renewal_days = 30 # Check interval for certificate expiry (hours) check_interval_hours = 12 # HTTP port for HTTP-01 challenge (must be 80) http_port = 80 # ============================================================================= # Post-Quantum Cryptography # ============================================================================= [pqc] # Enable PQC hybrid key exchange enabled = true # PQC provider: "openssl3.5" (requires OpenSSL 3.5+ with native ML-KEM) provider = "openssl3.5" # Path to OpenSSL 3.5 binary openssl_path = "/[system]/openssl-pq/..." # OpenSSL library path openssl_lib_path = "/[system]/openssl-pq/..." # Preferred KEM algorithm for key exchange (FIPS 203 / IETF standard names). # Options: X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, X448MLKEM1024, # ML-KEM-512, ML-KEM-768, ML-KEM-1024 # Legacy pre-NIST Kyber names require the `legacy-pqc` build feature (off by default) # and are NOT interoperable with ML-KEM peers โ do not use in new deployments. preferred_kem = "X25519MLKEM768" # Fallback to classical TLS if PQC is unavailable fallback_to_classical = true # Refuse to start if the TLS private key is group- or world-readable, rather # than warning and serving anyway. All keys under certs_path are 0600 root:root # and ACME writes them that way, so this changes nothing today - it exists so a # key that BECOMES readable is caught at startup instead of in the journal. strict_key_permissions = "[REDACTED]" # ============================================================================= # Admin API Configuration # ============================================================================= [admin] # Enable admin HTTP API enabled = true # Admin API bind address (use [loopback] for local-only access) bind_address = "[loopback]" # Admin API port port = [configured] # Require mTLS for admin API require_mtls = false # Bearer token for admin API authentication (persistent โ prevents cooldown spam on restart) auth_token = "[REDACTED]" # Allowed IP addresses for admin API access allowed_ips = ["[REDACTED]"] # ============================================================================= # Logging Configuration # ============================================================================= [logging] # Log level: trace, debug, info, warn, error level = "info" # Log format: "json" or "text" format = "json" # Log file path (empty = stdout) # file = "/[logs]/..." # Enable access logs access_log = true # Access log file path access_log_file = "/[logs]/..." # ============================================================================= # Rate Limiting (Basic) # ============================================================================= [rate_limiting] # Enable basic rate limiting enabled = true # Requests per second per IP requests_per_second = [configured] # Burst size for rate limiter burst_size = [configured] # Enable connection rate limiting connection_rate_limit = true # New connections per second per IP. # # Was 10, which a single page load can exceed on its own: a browser (or a # rendering crawler) opens a connection per subresource, and exceeding this # calls block_ip() for 300s on the FIRST offence, with no counter to cross # first. That is what banned Googlebot on every Search Console render and # produced 269 rate-limit events in 24h against ordinary visitors. # # 50 matches web2.pqcrypta.com (philibertsecurity.com), which has run at that # value with no Googlebot ban in 14 days of logs and no abuse problem โ the # scanners it does block are caught by the 4xx error-rate rule instead, which # is the check actually suited to that job. connections_per_second = [configured] # ============================================================================= # Advanced Multi-Dimensional Rate Limiting # ============================================================================= # Features: composite keys, JA3 fingerprinting, X-Forwarded-For trust, adaptive ML [advanced_rate_limiting] # Enable advanced rate limiting (overrides basic when enabled) enabled = true # IPv6 subnet grouping (64 = /64 subnets treated as single client) ipv6_subnet_bits = 64 # Trusted proxies for X-Forwarded-For parsing (CIDR or IP) trusted_proxies = ["[REDACTED]"] # Key resolution strategy [advanced_rate_limiting.key_strategy] # Priority order for key resolution (first found wins) order = ["api_key", "jwt_subject", "ja3_fingerprint", "real_ip", "source_ip"] fallback = "source_ip" use_composite = false # Header names for key extraction [advanced_rate_limiting.headers] api_key = "X-API-Key" user_id = "X-User-ID" tenant_id = "X-Tenant-ID" real_ip = "X-Real-IP" # Global limits (DDoS protection layer) [advanced_rate_limiting.global_limits] requests_per_second = [configured] burst_size = [configured] [advanced_rate_limiting.global_limits.per_ip] requests_per_second = [configured] burst_size = [configured] requests_per_minute = [configured] requests_per_hour = [configured] [advanced_rate_limiting.global_limits.per_fingerprint] requests_per_second = [configured] burst_size = [configured] requests_per_minute = [configured] requests_per_hour = [configured] [advanced_rate_limiting.global_limits.per_api_key] requests_per_second = [configured] burst_size = [configured] requests_per_minute = [configured] requests_per_hour = [configured] [advanced_rate_limiting.global_limits.per_composite] requests_per_second = [configured] burst_size = [configured] requests_per_minute = [configured] requests_per_hour = [configured] # Distributed rate limiting via Redis (all proxy instances share counters) [advanced_rate_limiting.redis] url = "redis://[loopback]" key_prefix = "[REDACTED]" connect_timeout_ms = 2000 command_timeout_ms = 50 distribute_per_second = true # JA3/JA4 fingerprint-based limiting (NAT-friendly) [advanced_rate_limiting.fingerprint_limiting] enabled = true prefer_over_ip = false blocked_fingerprints = [] [advanced_rate_limiting.fingerprint_limiting.unknown_limits] requests_per_second = [configured] burst_size = [configured] requests_per_minute = [configured] requests_per_hour = [configured] # Adaptive rate limiting (ML-inspired anomaly detection) [advanced_rate_limiting.adaptive] enabled = false baseline_window_secs = 3600 sensitivity = [configured] auto_adjust = false min_samples = 1000 std_dev_multiplier = [configured] # Per-route rate limits โ keyed by route name. # Applied as a secondary check inside the proxy handler after route matching, # in addition to the global per-IP and per-fingerprint limits above. [advanced_rate_limiting.route_limits.office-login] pattern = "/api/method/login" [advanced_rate_limiting.route_limits.office-login.limits] requests_per_second = [configured] burst_size = [configured] requests_per_minute = [configured] requests_per_hour = [configured] # ============================================================================= # Security Settings # ============================================================================= [security] # Maximum request body size in bytes (200MB - matches MAX_TOTAL_SIZE in pdf/api.php) max_request_size = 209715200 # Maximum header size in bytes (64KB) max_header_size = 65536 # Connection timeout in seconds connection_timeout_secs = 30 # Enable DoS protection dos_protection = true # Blocked IP addresses blocked_ips = ["[REDACTED]"] # Allowed IP addresses (whitelist mode - empty = allow all) allowed_ips = ["[REDACTED]"] # GeoIP database path for country blocking (optional) geoip_db_path = "/[data]/..." # Blocked country codes (ISO 3166-1 alpha-2) blocked_countries = ["CN", "RU", "KP", "IR", "NZ"] # How long a GeoIP block lasts, in seconds. 0 = never expires. # Set to 0 deliberately: a geo-blocked address stays on the blocklist rather # than ageing out after a day. Applies to every entry in blocked_countries. geoip_block_duration_secs = 0 # Maximum connections per IP max_connections_per_ip = [configured] # Error-based blocking thresholds (prevents false positives from scanners) error_4xx_threshold = [configured] # 4xx errors before checking rate min_requests_for_error_check = [configured] # Minimum requests before error check error_rate_threshold = [configured] # 70% error rate triggers suspicious pattern error_window_secs = 60 # Sliding window duration (seconds) auto_block_threshold = [configured] # Suspicious patterns before auto-block auto_block_duration_secs = [configured] # Block duration (seconds) # Authorized pentest IPs โ skip rate-limiting and auto-block but still WAF-checked. # Each attack still returns 403; IP is never auto-banned mid-run. # REMOVE after pentest engagement ends. # IPv6 matters here: this box egresses over IPv6 when it is available, so an # IPv4-only list exempted none of its own requests. Matching is by parsed # address (see config::ip_list_contains), so any valid spelling works. pentest_bypass_ips = ["[REDACTED]"] # [redacted-ip] + [loopback] are this server's own egress/loopback: server-side # jobs (test suite, news refresh, wizard, health tooling) must never auto-block # the box's own API access. Still WAF-inspected; only rate/error auto-ban is skipped. # ============================================================================= # Web Application Firewall (WAF) # ============================================================================= [waf] # Enable WAF โ injection, traversal, inclusion, scanner probes, request anomalies enabled = true # "block" rejects the request with 403; "detect" logs only (for tuning) mode = "block" # Anomaly score at which a request is blocked. Severities contribute # Low 3, Medium 5, High 8, Critical 10. At 5 any single Medium-or-higher rule # blocks on its own, while two Low-severity signals must agree. Raise it on a # route that produces false positives; lower it to act on single weak signals. anomaly_threshold = 5 # OWASP A03: SQL injection pattern matching sqli = true # OWASP A03: Cross-site scripting pattern matching xss = true # OWASP A01/A05: Path traversal and path confusion (../, ..;/, overlong UTF-8) path_traversal = true # OWASP A03: NoSQL injection detection ($where, $gt operator abuse, etc.) nosqli = true # OWASP A10: SSRF detection. Metadata IPs are Critical; loopback spellings are # Low and need corroboration, so a URL field naming localhost no longer 403s alone. ssrf = true # OWASP A03: OS command injection (shell metacharacters, ${IFS}, reverse shells) cmd_injection = true # OWASP A08: XML external entity injection xxe = true # OWASP A08: insecure deserialization (Java/PHP/Python, incl. byte signatures) deserialization = true # OWASP A06: JNDI/Log4Shell and expression-language injection (OGNL, SpEL) jndi = true # OWASP A03: server-side template injection (Jinja, Twig, ERB) ssti = true # Local/remote file inclusion via URL stream wrappers (php://, expect://, phar://) file_inclusion = true # CRLF injection / HTTP response splitting. Applied to URL and headers only โ # request bodies use CRLF line endings legitimately. crlf_injection = true # JavaScript prototype pollution (__proto__, constructor.prototype) proto_pollution = true # GraphQL schema introspection โ off, legitimate for a public schema graphql = false # Structural anomalies: request smuggling (CL+TE, conflicting Content-Length), # control characters in headers, diagnostic methods, malformed Host request_anomaly = true # Block known scanner/reconnaissance probe paths (.git, .env, wp-login, etc.) scanner_probe = true # Block known malicious scanner/bot user-agents (sqlmap, nikto, masscan, curl, etc.) block_scanner_uas = true # Scan request bodies, decoding JSON string escapes scan_json_body = true # Maximum body bytes to scan (64KB โ covers all form submissions) max_body_scan_bytes = 65536 # Scan every request header except content negotiation, cache validators, # client hints and credentials scan_all_headers = true # Maximum bytes of any single header value to scan max_header_scan_bytes = 8192 # Header count above which a request is flagged as anomalous max_header_count = 80 # Percent-decode passes before matching. One pass is a bypass: %253Cscript%253E # decodes once to %3Cscript%3E, matches nothing, and the origin decodes it again. max_decode_passes = 3 # Custom extra regex patterns โ empty, built-in ruleset covers OWASP Top 10 custom_patterns = [] # Per-path rule exclusions, for tuning a false positive without switching a # category off site-wide. `path` is a regex; name rule identifiers in `rules` # and/or whole categories in `categories`. # [[waf.exclusions]] # path = "^/regex/" # categories = ["xss"] # ============================================================================= # TLS Fingerprint Detection (JA3/JA4) # ============================================================================= [fingerprint] # Enable TLS fingerprint detection middleware enabled = true # Use TLS-layer capture with custom accept loop (captures raw ClientHello) # When enabled, uses FingerprintingTlsAcceptor for full JA3/JA4 capture # before TLS handshake, allowing early blocking of malicious clients. tls_layer_capture = true # Block duration for malicious fingerprints (seconds) malicious_block_duration_secs = [configured] # 1 hour # Block duration for suspicious fingerprints with high request rate (seconds) suspicious_block_duration_secs = [configured] # 5 minutes # Request count threshold to trigger suspicious fingerprint rate check suspicious_rate_threshold = [configured] # Time window for suspicious rate detection (seconds) suspicious_rate_window_secs = 60 # 1 minute # Fingerprint cache max age before cleanup (seconds) cache_max_age_secs = 3600 # 1 hour # Block security scanner fingerprints (Nmap, Nikto, Burp Suite, etc.) # When false, scanners are logged but not blocked block_scanners = true # Add fingerprint info headers to responses (x-client-fingerprint, x-client-type) # Enable for debugging/monitoring, disable in production for security add_response_headers = false # ============================================================================= # Circuit Breaker Configuration # ============================================================================= [circuit_breaker] # Enable circuit breaker for backend protection enabled = true # Time before circuit breaker transitions from Open to Half-Open (seconds) half_open_delay_secs = 30 # Maximum test requests allowed in Half-Open state half_open_max_requests = 3 # Failure threshold to open the circuit (only 5xx and timeouts count as failures) # High threshold to prevent health check cron from tripping circuit breaker # during intensive crypto endpoint testing (keygen + encrypt + decrypt per algo) failure_threshold = [configured] # Success threshold to close the circuit from Half-Open success_threshold = 2 # Stale request counter cleanup interval (seconds) stale_counter_cleanup_secs = 300 # 5 minutes # ============================================================================= # HTTP Connection Pool Configuration # ============================================================================= [connection_pool] # Pool idle timeout - how long idle connections stay in pool (seconds) idle_timeout_secs = 90 # Maximum idle connections per backend host max_idle_per_host = 100 # Maximum total connections per backend host max_connections_per_host = 100 # Connection acquire timeout (milliseconds) acquire_timeout_ms = 30000 # 30 seconds # ============================================================================= # HTTP/3 Advanced Features Configuration # ============================================================================= [http3] # Enable Early Hints (103 status code) - preload critical resources early_hints_enabled = true # Enable Priority Hints (RFC 9218) - resource scheduling optimization priority_hints_enabled = true # Enable Request Coalescing - deduplicate identical in-flight requests coalescing_enabled = true # Default preconnect origins for Early Hints (none โ stlweb.dev self-hosts all assets) preconnect_origins = [] # Resources to preload via 103 Early Hints, scoped per host. # NOTE: an href is only USED by the browser if it byte-matches the URL in the # page HTML, including any ?v= cache-buster. Versioned files below are pinned to # their current filemtime โ re-pin these if the underlying file changes. # # stlweb.dev: the main site's stylesheet (loaded in _head.php) and script (loaded # in _footer.php). "/" MUST be exact here โ every proposal under /proposals/<name>/ # ships its own css/style.css and js/main.js, so a "/" prefix pushed the main # site's two files on every proposal page, where they are never used. That tripped # the browser's "preloaded but not used" warning on all six proposals. Interior # pages of the main site are listed individually below so they keep the preload. [[http3.preload_resources]] host = "stlweb.dev" path = "/" exact = true href = "/css/style.css?v=1788490295" as_type = "style" [[http3.preload_resources]] host = "stlweb.dev" path = "/" exact = true href = "/js/main.js?v=1778453331" as_type = "script" [[http3.preload_resources]] host = "stlweb.dev" path = "/about.php" href = "/css/style.css?v=1788490295" as_type = "style" [[http3.preload_resources]] host = "stlweb.dev" path = "/about.php" href = "/js/main.js?v=1778453331" as_type = "script" [[http3.preload_resources]] host = "stlweb.dev" path = "/services.php" href = "/css/style.css?v=1788490295" as_type = "style" [[http3.preload_resources]] host = "stlweb.dev" path = "/services.php" href = "/js/main.js?v=1778453331" as_type = "script" [[http3.preload_resources]] host = "stlweb.dev" path = "/work.php" href = "/css/style.css?v=1788490295" as_type = "style" [[http3.preload_resources]] host = "stlweb.dev" path = "/work.php" href = "/js/main.js?v=1778453331" as_type = "script" [[http3.preload_resources]] host = "stlweb.dev" path = "/contact.php" href = "/css/style.css?v=1788490295" as_type = "style" [[http3.preload_resources]] host = "stlweb.dev" path = "/contact.php" href = "/js/main.js?v=1778453331" as_type = "script" [[http3.preload_resources]] host = "stlweb.dev" path = "/legal/" href = "/css/style.css?v=1788490295" as_type = "style" [[http3.preload_resources]] host = "stlweb.dev" path = "/legal/" href = "/js/main.js?v=1778453331" as_type = "script" # O'Fallon Concrete proposal: its own render-blocking assets. The hrefs carry the # same filemtime version that asset() emits in _head.php โ re-pin both whenever # style.css or head.js changes, or the hint pushes a URL the page never requests. # A prefix is correct # here โ all 30 routes under this path share the same stylesheet and head script. [[http3.preload_resources]] host = "stlweb.dev" path = "/proposals/ofallonconcreteco.com/" href = "/proposals/ofallonconcreteco.com/css/style.css?v=1788489646" as_type = "style" [[http3.preload_resources]] host = "stlweb.dev" path = "/proposals/ofallonconcreteco.com/" href = "/proposals/ofallonconcreteco.com/js/head.js?v=1788405277" as_type = "script" # pqcrypta.com: homepage-only render-blocking stylesheets (versions are # filemtime-stable). These two files are referenced only by the homepage # (index.php), so match "/" EXACTLY โ otherwise the "/" prefix preloads them on # every page (e.g. /http3-quic/, which uses a different stylesheet), wasting the # push and tripping the browser's "preloaded but not used" warning. [[http3.preload_resources]] host = "pqcrypta.com" path = "/" exact = true href = "/css/pqcrypta-loader.css?v=1767900410" as_type = "style" [[http3.preload_resources]] host = "pqcrypta.com" path = "/" exact = true href = "/css/homepage.css?v=1783392618" as_type = "style" # pqpdf.com: main stylesheet + site nav (filemtime-versioned โ re-pin if changed). [[http3.preload_resources]] host = "pqpdf.com" path = "/" href = "/css/pdf.css?v=1779328120" as_type = "style" [[http3.preload_resources]] host = "pqpdf.com" path = "/" href = "/css/site-nav.css?v=1780707281" as_type = "style" # fated.org: stylesheet + app script (unversioned, always matches). [[http3.preload_resources]] host = "fated.org" path = "/" href = "/css/style.css?v=1788490295" as_type = "style" [[http3.preload_resources]] host = "fated.org" path = "/" href = "/js/app.js" as_type = "script" # Maximum wait time for coalesced requests (milliseconds) coalescing_max_wait_ms = 100 # Maximum subscribers per coalesced request coalescing_max_subscribers = 100 # HTTP methods to coalesce (typically only GET and HEAD) coalescing_methods = ["GET", "HEAD"] # Paths to exclude from coalescing (API endpoints, WebSocket, streams) coalescing_exclude_paths = ["/api/", "/ws", "/stream", "/speedtest/tcp-"] # ============================================================================= # Response Cache (RFC 9111) # ============================================================================= [cache] enabled = true max_size_mb = 128 default_ttl_secs = 60 max_body_size_bytes = 2097152 excluded_paths = ["/api/", "/ws", "/stream", "/auth", "/admin"] excluded_hosts = ["api.pqcrypta.com", "office.stlweb.dev"] no_cache_set_cookie = true # ============================================================================= # Security Headers Configuration # ============================================================================= [headers] # HTTP Strict Transport Security (2 years, preload-ready) hsts = "max-age=63072000; includeSubDomains; preload" # Prevent clickjacking x_frame_options = "DENY" # Prevent MIME-type sniffing x_content_type_options = "nosniff" # Control referrer information referrer_policy = "strict-origin-when-cross-origin" # Disable browser features (FLoC, camera, microphone, geolocation) permissions_policy = "camera=(), microphone=(), geolocation=(), interest-cohort=(), fullscreen=(self), payment=()" # Cross-origin isolation headers # COOP/CORP set to safe defaults; per-route headers_override takes precedence (proxy # injects these conditionally โ skips if the route handler already set the header). # Grafana/Prometheus/Frappe routes override to "unsafe-none" / "cross-origin" as needed. cross_origin_opener_policy = "same-origin" cross_origin_embedder_policy = "unsafe-none" cross_origin_resource_policy = "same-origin" # Additional security headers x_permitted_cross_domain_policies = "none" x_download_options = "noopen" x_dns_prefetch_control = "off" # PQC branding headers x_quantum_resistant = "ML-KEM-1024, ML-DSA-87, X25519MLKEM768" x_security_level = "Post-Quantum Ready" # HTTP/3 Performance & Monitoring Headers # Enable Server-Timing header for performance metrics server_timing_enabled = true # Accept-CH header for Client Hints (responsive content delivery) accept_ch = "DPR, Viewport-Width, Width, ECT, RTT, Downlink, Sec-CH-UA-Platform, Sec-CH-UA-Mobile" # Outlook add-in surface exception. # Office hosts the PQ PDF task pane in a cross-origin iframe, so this path must NOT # receive X-Frame-Options and instead gets an add-in CSP (frame-ancestors for the # Office hosts + the office.js CDN + api.pqpdf.com). Set addin_csp = "" to disable. addin_hosts = ["pqpdf.com", "www.pqpdf.com"] addin_path_prefix = "/outlook/" addin_csp = "default-src 'self'; script-src 'self' 'unsafe-inline' https://appsforoffice.microsoft.com https://*.officeapps.live.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.pqpdf.com https://*.office.com https://*.officeapps.live.com; frame-ancestors https://outlook.office.com https://outlook.office365.com https://outlook.live.com https://*.office.com https://*.officeapps.live.com 'self'" # NEL (Network Error Logging) for client-side error reporting nel = '{"report_to":"default","max_age":86400,"include_subdomains":true}' # Report-To endpoint configuration for NEL and other reports report_to = '{"group":"default","max_age":86400,"endpoints":[{"url":"https://api.pqcrypta.com/reports"}]}' # HTTP/3 Priority (RFC 9218) - u=0-7 (urgency), i (incremental) priority = "u=3" # ============================================================================= # HTTP Redirect Configuration # ============================================================================= [http_redirect] # Enable HTTP redirect server on port 80 enabled = true # HTTP port (redirects to HTTPS on primary port) port = 80 # Redirect all HTTP to HTTPS redirect_to_https = true # ============================================================================= # Load Balancer Configuration # ============================================================================= [load_balancer] # Enable load balancing (automatically enabled when backend_pools defined) enabled = true # Default algorithm for all pools # Options: least_connections, round_robin, weighted_round_robin, random, ip_hash, least_response_time default_algorithm = "least_connections" # Session affinity (sticky sessions) configuration [load_balancer.session_affinity] enabled = false cookie_name = "PQCPROXY_BACKEND" cookie_ttl_secs = 3600 cookie_secure = true cookie_httponly = true cookie_samesite = "lax" # Request queue for when all backends are saturated [load_balancer.queue] enabled = true max_size = 1000 timeout_ms = 5000 # Slow start for recovering backends (gradual traffic increase) [load_balancer.slow_start] enabled = true duration_secs = 30 initial_weight_percent = 10 # Connection draining for graceful backend removal [load_balancer.connection_draining] enabled = true timeout_secs = 30 # ============================================================================= # Backend Definitions # ============================================================================= # Rust Enterprise API server (port 3003) [backends.pqcrypta-api] name = "pqcrypta-api" type = "http1" address = "[loopback]" tls = false timeout_ms = 120000 max_connections = 200 # Pooled HTTP/1.1 connections to this backend intermittently hang or (with h2c) # get reset under sustained concurrent load โ root cause not yet isolated # further upstream. Disabling pooling forces a fresh connection per request, # which sidesteps it; cheap since this is a loopback backend. disable_pooling = true health_check = "/health" health_check_interval_secs = 30 chatbot_timeout_ms = 300000 # Apache (port 8080) - serves main website [backends.apache] name = "apache" type = "http1" address = "[loopback]" tls = false timeout_ms = 120000 max_connections = 100 health_check = "/" health_check_interval_secs = 30 # Prometheus metrics server (port 9090) [backends.prometheus] name = "prometheus" type = "http1" address = "[loopback]" tls = false timeout_ms = 30000 max_connections = 50 health_check = "/prometheus/-/healthy" health_check_interval_secs = 30 # Grafana dashboard server (port 3000) [backends.grafana] name = "grafana" type = "http1" address = "[loopback]" tls = false timeout_ms = 30000 max_connections = 50 health_check = "/grafana/api/health" health_check_interval_secs = 30 # PQPDF external REST API (port 3004) [backends.pqpdf-api] name = "pqpdf-api" type = "http1" address = "[loopback]" tls = false timeout_ms = 180000 max_connections = 200 health_check = "/v1/health" health_check_interval_secs = 30 # Frappe socket.io Node server (direct โ bypasses Apache for WebSocket support) [backends.office-node] name = "office-node" type = "http1" address = "[loopback]" tls = false timeout_ms = 120000 max_connections = 500 health_check_interval_secs = 30 # ============================================================================= # DOMAIN: api.pqcrypta.com - Routes to Rust Enterprise API # ============================================================================= # # Traffic Shadowing / Mirroring # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ # Add a [routes.shadow] block to any route to mirror traffic to a second backend. # The client only sees the primary response; the shadow response is discarded. # Example (all values configurable): # # [routes.shadow] # backend = "pqcrypta-api-canary" # Must be a key in [backends.*] # percent = 10 # 0โ100 % of requests to mirror # timeout_ms = 5000 # Abandon shadow task after this many ms # shadow_header = "X-Shadow-Request" # Header injected on shadow requests # shadow_header_value = "1" # Value for that header # log_responses = true # Log shadow status + latency at INFO # # To activate: uncomment the block, add a [backends.pqcrypta-api-canary] entry # pointing to the canary instance (e.g. [loopback]:3004), then reload config. # Chatbot endpoint - extended timeouts for ML/FAISS model loading [[routes]] name = "api-chatbot" # Public REST API: HTTP/1.1 clients (python-requests, node fetch, SDKs) # must not get 426; h2/h3 still preferred via ALPN. allow_http11 = true host = "api.pqcrypta.com" path_prefix = "/chatbot" webtransport = false backend = "pqcrypta-api" priority = 5 timeout_override_ms = 300000 [routes.cors] allow_origin = "https://pqcrypta.com" allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"] allow_credentials = "[REDACTED]" max_age = 86400 # WebTransport route for encryption streams [[routes]] name = "webtransport-encrypt" # Public REST API: HTTP/1.1 clients (python-requests, node fetch, SDKs) # must not get 426; h2/h3 still preferred via ALPN. allow_http11 = true host = "api.pqcrypta.com" path_prefix = "/encrypt" webtransport = true backend = "pqcrypta-api" stream_to_method = "POST" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 10 [routes.add_headers] X-Forwarded-Proto = "https" X-WebTransport = "true" [routes.cors] allow_origin = "https://pqcrypta.com" allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"] allow_credentials = "[REDACTED]" max_age = 86400 # WebTransport route for decryption streams [[routes]] name = "webtransport-decrypt" # Public REST API: HTTP/1.1 clients (python-requests, node fetch, SDKs) # must not get 426; h2/h3 still preferred via ALPN. allow_http11 = true host = "api.pqcrypta.com" path_prefix = "/decrypt" webtransport = true backend = "pqcrypta-api" stream_to_method = "POST" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 10 [routes.cors] allow_origin = "https://pqcrypta.com" allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"] allow_credentials = "[REDACTED]" max_age = 86400 # WebTransport route for key generation [[routes]] name = "webtransport-keys" # Public REST API: HTTP/1.1 clients (python-requests, node fetch, SDKs) # must not get 426; h2/h3 still preferred via ALPN. allow_http11 = true host = "api.pqcrypta.com" path_prefix = "/keys" webtransport = true backend = "pqcrypta-api" stream_to_method = "POST" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 10 [routes.cors] allow_origin = "https://pqcrypta.com" allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"] allow_credentials = "[REDACTED]" max_age = 86400 # WebTransport route for streaming [[routes]] name = "webtransport-stream" # Public REST API: HTTP/1.1 clients (python-requests, node fetch, SDKs) # must not get 426; h2/h3 still preferred via ALPN. allow_http11 = true host = "api.pqcrypta.com" path_prefix = "/stream" webtransport = true backend = "pqcrypta-api" stream_to_method = "POST" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 10 [routes.cors] allow_origin = "https://pqcrypta.com" allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"] allow_credentials = "[REDACTED]" max_age = 86400 # General WebTransport catch-all [[routes]] name = "webtransport-default" host = "api.pqcrypta.com" path_prefix = "/webtransport" webtransport = true backend = "pqcrypta-api" stream_to_method = "POST" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 50 [routes.cors] allow_origin = "https://pqcrypta.com" allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"] allow_credentials = "[REDACTED]" max_age = 86400 # Prometheus API (metrics query endpoints) [[routes]] name = "api-prometheus" # Public REST API: HTTP/1.1 clients (python-requests, node fetch, SDKs) # must not get 426; h2/h3 still preferred via ALPN. allow_http11 = true host = "api.pqcrypta.com" path_prefix = "/prometheus" webtransport = false backend = "prometheus" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 15 [routes.cors] allow_origin = "https://pqcrypta.com" allow_methods = ["GET", "POST", "OPTIONS"] allow_headers = ["Content-Type", "Authorization", "X-API-Key"] allow_credentials = "[REDACTED]" max_age = 86400 # API catch-all (all api.pqcrypta.com traffic โ port 3003) [[routes]] name = "api-default" # Public REST API: HTTP/1.1 clients (python-requests, node fetch, SDKs) # must not get 426; h2/h3 still preferred via ALPN. allow_http11 = true host = "api.pqcrypta.com" path_prefix = "/" webtransport = false backend = "pqcrypta-api" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 100 [routes.cors] allow_origins = ["https://pqcrypta.com", "https://www.pqcrypta.com", "https://pqpdf.com", "https://www.pqpdf.com", "https://stlweb.dev", "https://conformance.pqcrypta.com"] allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"] allow_credentials = "[REDACTED]" max_age = 86400 # ============================================================================= # DOMAIN: pqcrypta.com / www.pqcrypta.com - Routes to Apache # ============================================================================= # WebTransport for main site - forward to API backend [[routes]] name = "main-webtransport" host = "pqcrypta.com" path_prefix = "/webtransport" webtransport = true backend = "pqcrypta-api" stream_to_method = "POST" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 5 [routes.cors] allow_origin = "https://pqcrypta.com" allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"] allow_credentials = "[REDACTED]" max_age = 86400 # WebTransport root path for main site [[routes]] name = "main-webtransport-root" host = "pqcrypta.com" path_exact = "/" webtransport = true backend = "pqcrypta-api" stream_to_method = "POST" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 5 [routes.cors] allow_origin = "https://pqcrypta.com" allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"] allow_credentials = "[REDACTED]" max_age = 86400 # www.pqcrypta.com WebTransport [[routes]] name = "www-webtransport" host = "www.pqcrypta.com" path_prefix = "/webtransport" webtransport = true backend = "pqcrypta-api" stream_to_method = "POST" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 5 [routes.cors] allow_origin = "https://pqcrypta.com" allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] allow_headers = ["Content-Type", "Authorization", "X-Requested-With", "X-API-Key", "X-Forwarded-For", "X-Verification-Version", "X-Analysis-Type", "Cache-Control", "signature-agent", "signature-input", "signature"] allow_credentials = "[REDACTED]" max_age = 86400 # SEO redirects - underscore to hyphen [[routes]] name = "seo-redirect-http3_quic" allow_http11 = true host = "pqcrypta.com" path_prefix = "/http3_quic" redirect = "/http3-quic" redirect_permanent = true priority = 1 [[routes]] name = "seo-redirect-pqc_ready" allow_http11 = true host = "pqcrypta.com" path_prefix = "/pqc_ready" redirect = "/pqc-ready" redirect_permanent = true priority = 1 [[routes]] name = "seo-redirect-key_vault" allow_http11 = true host = "pqcrypta.com" path_prefix = "/key_vault" redirect = "/key-vault" redirect_permanent = true priority = 1 [[routes]] name = "seo-redirect-security_systems" allow_http11 = true host = "pqcrypta.com" path_prefix = "/security_systems" redirect = "/security-systems" redirect_permanent = true priority = 1 [[routes]] name = "seo-redirect-bot_threat_remediation" allow_http11 = true host = "pqcrypta.com" path_prefix = "/bot_threat_remediation" redirect = "/bot-threat-remediation" redirect_permanent = true priority = 1 [[routes]] name = "seo-redirect-threat_bot" allow_http11 = true host = "pqcrypta.com" path_prefix = "/threat_bot" redirect = "/threat-bot" redirect_permanent = true priority = 1 # Speed test TCP endpoints - allow HTTP/1.1 so each parallel stream uses a # separate TCP connection, giving independent congestion windows for accurate # throughput measurement. HTTP/2 multiplexes over one connection which defeats # the purpose of parallel streams. [[routes]] name = "speedtest-tcp" host = "pqcrypta.com" path_regex = "^/speedtest/tcp-(ping|download|upload)\\.php" webtransport = false backend = "apache" allow_http11 = true priority = 3 [routes.headers_override] Cache-Control = "no-store, no-cache, must-revalidate" X-Accel-Buffering = "no" # Sitemaps - allow HTTP/1.1 for search bots [[routes]] name = "main-sitemaps" host = "pqcrypta.com" path_regex = "^/sitemaps/.*\\.xml$" webtransport = false backend = "apache" allow_http11 = true priority = 5 [routes.headers_override] Cross-Origin-Resource-Policy = "cross-origin" Access-Control-Allow-Origin = "*" X-Robots-Tag = "noindex, follow" Cache-Control = "public, max-age=3600" # robots.txt - allow HTTP/1.1 for search bots [[routes]] name = "main-robots" host = "pqcrypta.com" path_exact = "/robots.txt" webtransport = false backend = "apache" allow_http11 = true priority = 5 [routes.headers_override] Cross-Origin-Resource-Policy = "cross-origin" Access-Control-Allow-Origin = "*" Cache-Control = "public, max-age=3600" # Fonts - special handling [[routes]] name = "main-fonts" allow_http11 = true host = "pqcrypta.com" path_regex = "\\.(woff|woff2|ttf|eot|otf)$" webtransport = false backend = "apache" priority = 5 [routes.headers_override] Cache-Control = "public, max-age=31536000" Access-Control-Allow-Origin = "*" # Audio files - mobile optimized [[routes]] name = "main-audio" allow_http11 = true host = "pqcrypta.com" path_regex = "\\.(mp3|ogg|wav|m4a|aac|flac)$" webtransport = false backend = "apache" priority = 5 [routes.headers_override] Cross-Origin-Resource-Policy = "cross-origin" Access-Control-Allow-Origin = "*" Access-Control-Allow-Methods = "GET, HEAD, POST, OPTIONS" Access-Control-Allow-Headers = "Range, Content-Range, Content-Type, Accept, User-Agent, X-API-Key, signature-agent" Access-Control-Expose-Headers = "Content-Range, Content-Length, Accept-Ranges" # Billing pages - Stripe.js compatibility (remove COEP/COOP) [[routes]] name = "main-billing" allow_http11 = true host = "pqcrypta.com" path_prefix = "/dashboard/billing/" webtransport = false backend = "apache" priority = 5 stripe_compatibility = true # api_config.php - no bot blocking [[routes]] name = "main-api-config" allow_http11 = true host = "pqcrypta.com" path_exact = "/api_config.php" webtransport = false backend = "apache" priority = 5 skip_bot_blocking = true # Prometheus UI (metrics dashboard) - all /prometheus paths go to Prometheus # Priority 3 = higher than fonts route (5) so Prometheus serves its own assets [[routes]] name = "main-prometheus" allow_http11 = true host = "pqcrypta.com" path_prefix = "/prometheus" webtransport = false backend = "prometheus" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 3 [routes.headers_override] cross_origin_embedder_policy = "unsafe-none" cross_origin_opener_policy = "unsafe-none" cross_origin_resource_policy = "cross-origin" # Grafana dashboards - all /grafana paths go to Grafana # Priority 3 = higher than fonts route (5) so Grafana serves its own assets [[routes]] name = "main-grafana" allow_http11 = true host = "pqcrypta.com" path_prefix = "/grafana" webtransport = false backend = "grafana" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 3 [routes.headers_override] cross_origin_embedder_policy = "unsafe-none" cross_origin_opener_policy = "unsafe-none" cross_origin_resource_policy = "cross-origin" # Main site catch-all (all pqcrypta.com traffic โ Apache port 8080) [[routes]] name = "main-default" host = "pqcrypta.com" path_prefix = "/" webtransport = false backend = "apache" allow_http11 = true forward_client_identity = true client_identity_header = "X-Client-IP" priority = 100 # www.pqcrypta.com - same as pqcrypta.com [[routes]] name = "www-default" host = "www.pqcrypta.com" path_prefix = "/" webtransport = false backend = "apache" allow_http11 = true forward_client_identity = true client_identity_header = "X-Client-IP" priority = 100 # ============================================================================= # DOMAIN: pqpdf.com / www.pqpdf.com - Routes to Apache (PDF tools standalone) # ============================================================================= # api.pqcrypta.com robots.txt โ served by Apache, not by the API. # # api.pqcrypta.com routes straight to the pqcrypta-api backend, so /robots.txt # returned the API's JSON 404. Googlebot fetches it on every visit to the host # and, getting no directives, was free to crawl the whole API surface โ crawl # budget spent on a host with nothing indexable on it. priority = 1 puts this # ahead of the API path routes; the file itself is a "Disallow: /" served from # /var/www/api-static by the api vhost. [[routes]] name = "api-robots" host = "api.pqcrypta.com" path_exact = "/robots.txt" webtransport = false backend = "apache" allow_http11 = true priority = 1 # pqpdf.com sitemaps [[routes]] name = "pqpdf-sitemaps" host = "pqpdf.com" path_regex = "^/sitemaps/.*\\.xml$" webtransport = false backend = "apache" allow_http11 = true priority = 5 [routes.headers_override] Cross-Origin-Resource-Policy = "cross-origin" Access-Control-Allow-Origin = "*" X-Robots-Tag = "noindex, follow" Cache-Control = "public, max-age=3600" # pqpdf.com sitemap.xml [[routes]] name = "pqpdf-sitemap" host = "pqpdf.com" path_exact = "/sitemap.xml" webtransport = false backend = "apache" allow_http11 = true priority = 200 [routes.headers_override] Cross-Origin-Resource-Policy = "cross-origin" Access-Control-Allow-Origin = "*" Cache-Control = "public, max-age=3600" # pqpdf.com robots.txt [[routes]] name = "pqpdf-robots" host = "pqpdf.com" path_exact = "/robots.txt" webtransport = false backend = "apache" allow_http11 = true priority = 200 [routes.headers_override] Cross-Origin-Resource-Policy = "cross-origin" Access-Control-Allow-Origin = "*" Cache-Control = "public, max-age=3600" # pqpdf.com WebTransport [[routes]] name = "pqpdf-webtransport" host = "pqpdf.com" path_prefix = "/webtransport" webtransport = true backend = "apache" stream_to_method = "POST" priority = 3 # pqpdf.com catch-all [[routes]] name = "pqpdf-default" host = "pqpdf.com" path_prefix = "/" webtransport = false backend = "apache" allow_http11 = true forward_client_identity = true client_identity_header = "X-Client-IP" priority = 100 # www.pqpdf.com WebTransport [[routes]] name = "www-pqpdf-webtransport" host = "www.pqpdf.com" path_prefix = "/webtransport" webtransport = true backend = "apache" stream_to_method = "POST" priority = 3 # www.pqpdf.com โ same as pqpdf.com [[routes]] name = "www-pqpdf-default" host = "www.pqpdf.com" path_prefix = "/" webtransport = false backend = "apache" allow_http11 = true forward_client_identity = true client_identity_header = "X-Client-IP" priority = 100 # DOMAIN: fated.org โ Company site (Fated LLC) [[routes]] name = "fated-webtransport" host = "fated.org" path_prefix = "/webtransport" webtransport = true backend = "apache" stream_to_method = "POST" priority = 3 [[routes]] name = "fated-default" host = "fated.org" path_prefix = "/" webtransport = false backend = "apache" allow_http11 = true forward_client_identity = true client_identity_header = "X-Client-IP" priority = 100 # www.fated.org โ same as fated.org [[routes]] name = "www-fated-webtransport" host = "www.fated.org" path_prefix = "/webtransport" webtransport = true backend = "apache" stream_to_method = "POST" priority = 3 [[routes]] name = "www-fated-default" host = "www.fated.org" path_prefix = "/" webtransport = false backend = "apache" allow_http11 = true forward_client_identity = true client_identity_header = "X-Client-IP" # ============================================================================= # DOMAIN: api.pqpdf.com โ PQPDF External REST API (port 3004) # ============================================================================= # All api.pqpdf.com traffic โ pqpdf-api Rust binary [[routes]] name = "pqpdf-api-default" allow_http11 = true host = "api.pqpdf.com" path_prefix = "/" webtransport = false backend = "pqpdf-api" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 100 [routes.cors] allow_origins = ["*"] allow_methods = ["GET", "POST", "DELETE", "OPTIONS"] allow_headers = ["Content-Type", "X-API-Key", "X-Session-Id", "Authorization"] allow_credentials = "[REDACTED]" max_age = 86400 priority = 100 # ============================================================================= # DOMAIN: stlweb.dev โ St. Louis Web Development Studio (Fated LLC) # ============================================================================= # stlweb.dev WebTransport probe โ accepts WT CONNECT so scanner grades A++ [[routes]] name = "stlweb-webtransport" host = "stlweb.dev" path_prefix = "/webtransport" webtransport = true backend = "apache" stream_to_method = "POST" priority = 3 # stlweb.dev robots.txt โ allow HTTP/1.1 for search bots [[routes]] name = "stlweb-robots" host = "stlweb.dev" path_exact = "/robots.txt" webtransport = false backend = "apache" allow_http11 = true priority = 5 [routes.headers_override] Cross-Origin-Resource-Policy = "cross-origin" Access-Control-Allow-Origin = "*" Cache-Control = "public, max-age=3600" # stlweb.dev sitemap.xml โ allow HTTP/1.1 for crawlers [[routes]] name = "stlweb-sitemap" host = "stlweb.dev" path_exact = "/sitemap.xml" webtransport = false backend = "apache" allow_http11 = true priority = 5 [routes.headers_override] Cross-Origin-Resource-Policy = "cross-origin" Access-Control-Allow-Origin = "*" Cache-Control = "public, max-age=3600" # stlweb.dev fonts โ long cache, CORS open [[routes]] name = "stlweb-fonts" host = "stlweb.dev" path_regex = "\\.(woff|woff2|ttf|eot|otf)$" webtransport = false backend = "apache" allow_http11 = true priority = 5 [routes.headers_override] Cache-Control = "public, max-age=31536000" Access-Control-Allow-Origin = "*" # stlweb.dev CSS โ long cache, no Content-Type override (Apache sends correct text/css) [[routes]] name = "stlweb-css" host = "stlweb.dev" path_regex = "\\.css$" webtransport = false backend = "apache" allow_http11 = true priority = 4 [routes.headers_override] Cache-Control = "public, max-age=2592000" # stlweb.dev JS โ long cache, no Content-Type override (Apache sends correct application/javascript) [[routes]] name = "stlweb-js" host = "stlweb.dev" path_regex = "\\.js$" webtransport = false backend = "apache" allow_http11 = true priority = 4 [routes.headers_override] Cache-Control = "public, max-age=2592000" # stlweb.dev other static assets โ long cache [[routes]] name = "stlweb-static" host = "stlweb.dev" path_regex = "\\.(webp|avif|jpg|jpeg|png|gif|ico|svg)$" webtransport = false backend = "apache" allow_http11 = true priority = 5 [routes.headers_override] Cache-Control = "public, max-age=2592000" # stlweb.dev contact form โ no bot blocking to allow form submissions [[routes]] name = "stlweb-contact-submit" host = "stlweb.dev" path_exact = "/contact/submit.php" webtransport = false backend = "apache" allow_http11 = true priority = 3 skip_bot_blocking = true # www.stlweb.dev โ stlweb.dev (301 redirect, non-www canonical) [[routes]] name = "www-stlweb-default" host = "www.stlweb.dev" path_prefix = "/" webtransport = false backend = "apache" allow_http11 = true forward_client_identity = true client_identity_header = "X-Client-IP" priority = 100 # stlweb.dev catch-all โ all traffic โ Apache [[routes]] name = "stlweb-default" host = "stlweb.dev" path_prefix = "/" webtransport = false backend = "apache" allow_http11 = true forward_client_identity = true client_identity_header = "X-Client-IP" priority = 100 # ============================================================================= # DOMAIN: office.stlweb.dev โ ERPNext (stlweb.dev branded ERP) # ============================================================================= # WebTransport โ speedtest, telemetry, and WT root (required for A++ grade) [[routes]] name = "office-wt-speedtest" host = "office.stlweb.dev" path_exact = "/speedtest" webtransport = true backend = "apache" priority = 1 [[routes]] name = "office-wt-telemetry" host = "office.stlweb.dev" path_exact = "/telemetry" webtransport = true backend = "apache" priority = 1 [[routes]] name = "office-wt-root" host = "office.stlweb.dev" path_prefix = "/webtransport" webtransport = true backend = "apache" priority = 2 # Static assets โ long cache, immutable [[routes]] name = "office-assets" host = "office.stlweb.dev" path_prefix = "/assets/" backend = "apache" allow_http11 = true priority = 20 [routes.headers_override] Cache-Control = "public, max-age=31536000, immutable" Cross-Origin-Resource-Policy = "cross-origin" # Login endpoint โ strict rate limit to mitigate credential brute-force (H4) [[routes]] name = "office-login" host = "office.stlweb.dev" path_exact = "/api/method/login" backend = "apache" allow_http11 = true priority = 15 enforce_cookie_security = true strip_response_json_fields = ["exc"] [routes.headers_override] Cache-Control = "no-store" Cross-Origin-Resource-Policy = "cross-origin" Cross-Origin-Opener-Policy = "unsafe-none" # API endpoints โ no cache [[routes]] name = "office-api" host = "office.stlweb.dev" path_prefix = "/api/" backend = "apache" allow_http11 = true priority = 20 strip_response_json_fields = ["exc"] enforce_cookie_security = true [routes.headers_override] Cache-Control = "no-store" Cross-Origin-Resource-Policy = "cross-origin" Cross-Origin-Opener-Policy = "unsafe-none" # Socket.IO โ no cache [[routes]] name = "office-socketio" host = "office.stlweb.dev" path_prefix = "/socket.io/" backend = "office-node" allow_http11 = true websocket = true priority = 20 [routes.headers_override] Cache-Control = "no-store" # Uploaded files โ no cache [[routes]] name = "office-files" host = "office.stlweb.dev" path_prefix = "/files/" backend = "apache" allow_http11 = true priority = 20 [routes.headers_override] Cache-Control = "no-store, private" # Catch-all โ ERPNext app (no-store, relaxed CSP for Frappe) [[routes]] name = "office-app" host = "office.stlweb.dev" path_prefix = "/" backend = "apache" allow_http11 = true priority = 100 enforce_cookie_security = true [routes.headers_override] # CSP is set by Frappe itself per-request with a generated nonce (frappe.utils.csp). # The proxy does NOT override it here โ the backend CSP passes through. X-Frame-Options = "SAMEORIGIN" Cache-Control = "no-store, no-cache" Cross-Origin-Opener-Policy = "unsafe-none" Cross-Origin-Resource-Policy = "cross-origin"