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 QUIC/UDP listener
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
Worker threads (0 = auto-detect CPU cores)
Graceful shutdown drain timeout after SIGTERM before forced exit
Enable IPv6 dual-stack binding
Additional ports to listen on (all support QUIC/HTTP3/WebTransport)
Allowed WebTransport origins โ browser Origin header validation. Empty = reject all browser connections.
Enable HTTP redirect server on port 80
HTTP port โ listens for plain HTTP and redirects to HTTPS
Redirect all HTTP traffic to HTTPS
Allowed hostnames for the HTTPโHTTPS redirect. Requests with a Host header not in this list receive 400 (prevents open-redirect abuse).
proxy-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 client certificates (mTLS mode)
Minimum TLS version โ only TLS 1.3 is supported for QUIC
- 1.3 required for QUIC
- 1.2
ALPN protocols advertised during TLS handshake
Enable OCSP stapling for revocation status
Certificate hot-reload interval (0 = disabled)
Enable 0-RTT early data for faster reconnections. โ ๏ธ Vulnerable to replay attacks.
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
Use ECDSA P-256 keys โ smaller keys, faster TLS handshakes than RSA
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)
Enable PQC signatures (ML-DSA) โ requires pqc-signatures feature
Require hybrid mode โ reject pure PQC or pure classical connections
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
Blocked IP addresses (manual blocklist)
No IPs blockedAllowed IP allowlist (empty = allow all)
Allowlist disabled โ all IPs permittedBlocked ISO 3166-1 alpha-2 country codes
No countries blocked4xx 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
Enable TLS fingerprint detection middleware
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 for X-Forwarded-For parsing
Priority 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
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 = off, 1.0 = maximum)
Automatically adjust limits based on detected anomalies
Minimum sample count before adaptive limiting activates
Standard deviation multiplier for anomaly threshold (requests > mean + N*stddev = anomaly)
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.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
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=() |
| Cross-Origin-Opener-Policy cross_origin_opener_policy |
unsafe-none |
| Cross-Origin-Embedder-Policy cross_origin_embedder_policy |
unsafe-none |
| Cross-Origin-Resource-Policy cross_origin_resource_policy |
cross-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,i=?0 |
| 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"}]} |
| 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
Load balancing algorithm for this pool
Skip unhealthy backends when routing
Session affinity mode (none, cookie, ip_hash, header)
Health check path for all servers in pool
Health check interval
Enable canary routing for this pool
Keep users on the same canary server once assigned
Cookie name for canary server assignment
Cookie lifetime for canary assignment
Automatically suspend canary on high error rate
Error rate threshold to trigger auto-rollback (0.0โ1.0)
Sliding window for canary error tracking
Minimum requests before rollback can trigger
| # | Address | Weight | Priority | Max Conn | Timeout | TLS Mode | Flags |
|---|---|---|---|---|---|---|---|
| 1 | [loopback]:3003 | 100 | 1 | 100 | 30000 ms | terminate | โ |
| 2 | [loopback]:3004 | 100 | 1 | 100 | 30000 ms | terminate | โ |
Load balancing algorithm for this pool
Skip unhealthy backends when routing
Session affinity mode (none, cookie, ip_hash, header)
Health check path for all servers in pool
Health check interval
| # | Address | Weight | Priority | Max Conn | Timeout | TLS Mode | Flags |
|---|---|---|---|---|---|---|---|
| 1 | [loopback]:8080 | 100 | 1 | 200 | โ | terminate | โ |
| 2 | [loopback]:8081 | 100 | 1 | 200 | โ | terminate | โ |
Load balancing algorithm for this pool
Skip unhealthy backends when routing
Session affinity mode (none, cookie, ip_hash, header)
Health check path for all servers in pool
Health check interval
| # | Address | Weight | Priority | Max Conn | Timeout | TLS Mode | Flags |
|---|---|---|---|---|---|---|---|
| 1 | [loopback]:8000 | 100 | 1 | 100 | โ | terminate | โ |
| 2 | [loopback]:8001 | 100 | 1 | 100 | โ | terminate | โ |
Load balancing algorithm for this pool
Skip unhealthy backends when routing
Session affinity mode (none, cookie, ip_hash, header)
Header name when affinity = header
Pool-specific queue max size (overrides global)
Pool-specific queue timeout in ms (overrides global)
Health check path for all servers in pool
Health check interval
| # | Address | Weight | Priority | Max Conn | Timeout | TLS Mode | Flags |
|---|---|---|---|---|---|---|---|
| 1 | [private]:443 | 100 | 1 | 50 | 30000 ms | reencrypt | sni:backend1.internal.com |
| 2 | [private]:443 | 100 | 1 | 50 | 30000 ms | reencrypt | sni:backend2.internal.com |
proxy-config.toml. No changes can be made from this interface.| Name | Host | Path | Backend | Type | Priority | |
|---|---|---|---|---|---|---|
| api-route | api.example.com | prefix:/ | api | HTTP | 100 | |
|
Routing
backend:api
type:http
priority:100
forward_client_identity:true
client_identity_header:X-Client-IP
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| main-site | example.com | prefix:/ | apache | HTTP | 100 | |
|
Routing
backend:apache
type:http
priority:100
forward_client_identity:true
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| www-redirect | www.example.com | prefix:/ | โ https://example.com | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
Redirect
redirect:https://example.com
permanent:301 (permanent)
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| internal-api-route | example.com | prefix:/internal-api | internal-api | HTTP | 50 | |
|
Routing
backend:internal-api
type:http
priority:50
forward_client_identity:true
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| seo-old-path | example.com | prefix:/old_path | โ /new-path | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
Redirect
redirect:/new-path
permanent:301 (permanent)
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| seo-legacy | example.com | prefix:/legacy_page | โ /modern-page | 301 REDIRECT | 1 | |
|
Routing
backend:โ
type:redirect
priority:1
Redirect
redirect:/modern-page
permanent:301 (permanent)
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| webtransport-encrypt | api.example.com | prefix:/encrypt | api | WebTransport | 10 | |
|
Routing
backend:api
type:webtransport
priority:10
stream_to_method:POST
forward_client_identity:true
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| webtransport-stream | api.example.com | prefix:/stream | api | WebTransport | 10 | |
|
Routing
backend:api
type:webtransport
priority:10
stream_to_method:POST
forward_client_identity:true
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| custom-headers | example.com | prefix:/embed | apache | HTTP | 50 | |
|
Routing
backend:apache
type:http
priority:50
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| php-route | example.com | prefix:/app | php | HTTP | 80 | |
|
Routing
backend:php
type:http
priority:80
forward_client_identity:true
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| api-versioned | api.example.com | regex:^/v[0-9]+/.* | api | HTTP | 20 | |
|
Routing
backend:api
type:http
priority:20
forward_client_identity:true
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| exact-health | api.example.com | exact:/health | api | HTTP | 5 | |
|
Routing
backend:api
type:http
priority:5
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| sanitized-api | api.example.com | prefix:/public | api | HTTP | 60 | |
|
Routing
backend:api
type:http
priority:60
forward_client_identity:true
Remove Headers
X-Internal-Token, X-Debug-Info
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| seo-friendly | example.com | prefix:/sitemap | apache | HTTP | 30 | |
|
Routing
backend:apache
type:http
priority:30
allow_http11:true
skip_bot_blocking:true
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| checkout-page | example.com | prefix:/checkout | apache | HTTP | 40 | |
|
Routing
backend:apache
type:http
priority:40
stripe_compatibility:true โ removes COEP/COOP headers
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| long-running-api | api.example.com | prefix:/export | api | HTTP | 45 | |
|
Routing
backend:api
type:http
priority:45
timeout_override_ms:120000
forward_client_identity:true
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| grafana | pqcrypta.com | prefix:/grafana | grafana | HTTP | 20 | |
|
Routing
backend:grafana
type:http
priority:20
forward_client_identity:true
client_identity_header:X-Real-IP
stripe_compatibility:true โ removes COEP/COOP headers
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| prometheus | pqcrypta.com | prefix:/prometheus | prometheus | HTTP | 20 | |
|
Routing
backend:prometheus
type:http
priority:20
forward_client_identity:true
client_identity_header:X-Real-IP
|
||||||
| (unnamed) | * | / | โ | HTTP | โ | |
|
Routing
backend:โ
type:http
priority:โ
|
||||||
| default | * | prefix:/ | apache | HTTP | 1000 | |
|
Routing
backend:apache
type:http
priority:1000
|
||||||
| Name | SNI Pattern | Backend | Proxy Protocol | Timeout |
|---|---|---|---|---|
| external-mail | mail.example.com | 192.0.2.10:443 | Disabled | 30000 ms |
| internal-wildcard | *.internal.example.com | 192.0.2.20:443 | Enabled | 30000 ms |
| legacy-service | legacy.example.com | 192.0.2.100:443 | Disabled | 60000 ms |
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)
[NOT SET โ REQUIRED in production]Allowed IP addresses for admin API access
Enable database sync for IP/fingerprint blocklists from PostgreSQL
PostgreSQL host
PostgreSQL port
Database name
Database user
Minimum connection pool size
Maximum connection pool size
Database connection timeout
Blocklist refresh interval
Maximum blocklist entries in memory
SQL query to fetch blocked IP addresses (returns: ip_address, reason, expires_at)
SQL query to fetch blocked TLS fingerprints (returns: fingerprint, type, reason)
SQL query to fetch blocked country codes (returns: country_code, reason)
Log proxy-level detections back to database for analytics
Log blocked requests to database
Log suspicious patterns detected at proxy level
Database table for proxy detection records
SQL template for inserting proxy detection records (uses $1, $2, โฆ for parameters)
proxy-config.toml. No changes can be made from this interface.# PQCrypta Proxy Configuration # ============================= # Production-ready HTTP/3/QUIC/WebTransport reverse proxy with hybrid PQC TLS. # # All values are configurable - no hardcoded ports, paths, or addresses. # CLI arguments and environment variables can override these settings. # ============================= # Server Configuration # ============================= [server] # Bind address for QUIC/UDP listener bind_address = "0.0.0.0" # Primary UDP port for QUIC/HTTP3/WebTransport (default: 443) udp_port = 443 # Additional ports to listen on (all support QUIC/HTTP3/WebTransport) # Note: port 4433 is omitted here โ the proxy binds it exclusively as the # dedicated WebTransport server (hardcoded in main.rs). Listing it here would # cause a second QUIC listener to attempt the same port and also advertise it # in Alt-Svc as a generic h3 endpoint, which is incorrect. additional_ports = ["[configured]"] # Allowed origins for browser WebTransport sessions (SR-02). # Browsers send an Origin header; sessions whose origin is not listed are # rejected with 403. Non-browser / native clients (no Origin header) are # always accepted. Empty list = reject ALL browser connections. webtransport_allowed_origins = ["https://pqcrypta.com", "https://www.pqcrypta.com", "https://api.pqcrypta.com"] # Maximum concurrent connections max_connections = 10000 # Maximum concurrent streams per connection max_streams_per_connection = 1000 # 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 = false # Worker threads (0 = auto-detect based on CPU cores) worker_threads = 0 # Graceful shutdown drain timeout in seconds (AUD-11). # After SIGTERM/SIGQUIT, the proxy waits up to this many seconds for in-flight # requests to complete before exiting. Must not exceed systemd TimeoutStopSec. graceful_shutdown_timeout_secs = 30 # ============================= # TLS Configuration # ============================= [tls] # Path to TLS certificate chain (PEM format) # Managed by built-in ACME service (see [acme] section below) cert_path = "/[system]/[key-material]" # Path to TLS private key (PEM format) key_path = "/[system]/[key-material]" # 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 (h3/webtransport for QUIC, h2/http1.1 for TCP) 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 # Enable 0-RTT (early data) for faster reconnections # SECURITY WARNING: 0-RTT is vulnerable to replay attacks! # Only enable if your application can handle replay protection, # or if the performance benefit outweighs the security risk. # Default: false (disabled for security) enable_0rtt = false # ============================= # HTTPโHTTPS Redirect Server # ============================= [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 (301 permanent redirect) redirect_to_https = true # AUD-02: Allowed hostnames for the HTTPโHTTPS redirect. # Requests whose Host header is not in this list receive 400 Bad Request, # preventing open-redirect abuse (attacker sends Host: evil.com and gets # a redirect to https://evil.com/). # Set to your canonical domain(s). Empty list disables the check. allowed_domains = ["pqcrypta.com", "api.pqcrypta.com", "www.pqcrypta.com"] # ============================= # ACME Certificate Automation (Let's Encrypt) # ============================= # Automatic TLS certificate provisioning and renewal via ACME protocol (RFC 8555). # Checks cert expiry once daily โ actual ACME protocol only runs ~once every 60 days # when renewal is needed (certs last 90 days, renewal triggers at 30 days remaining). # HTTP-01 challenges served on port 80 before HTTPS redirect. [acme] # Enable ACME certificate automation enabled = true # Let's Encrypt production CA directory_url = "https://acme-v02.api.letsencrypt.org/directory" # Contact email for account registration and expiry notifications # AUD-06: Do NOT commit a real email address here โ use a placeholder in version control. # Replace with your actual operator address in the private config on each host. email = "admin@example.com" # Domains to include in the SAN certificate (single cert covers all) domains = ["pqcrypta.com", "api.pqcrypta.com"] # Certificate and key storage path certs_path = "/[system]/certs" # ACME account credentials storage account_path = "/etc/pqcrypta/acme/account.json" # Renew when certificate expires within this many days # Let's Encrypt certs last 90 days; 30 days gives a comfortable renewal window renewal_days = 30 # How often to check certificate expiry (hours) # 24h = once daily. The check is local (reads cert file) โ zero network cost. # ACME protocol only runs when renewal is actually needed (~once every 60 days). check_interval_hours = 24 # Use ECDSA keys (P-256) โ smaller keys, faster TLS handshakes than RSA use_ecdsa = true # HTTP-01 challenge validation (served on port 80 redirect server) challenge_type = "http-01" # Accept CA terms of service accept_tos = true # ============================= # Response Cache (RFC 9111) # ============================= [cache] enabled = false max_size_mb = 128 default_ttl_secs = 60 max_body_size_bytes = 2097152 excluded_paths = ["/api/", "/ws", "/stream", "/auth", "/admin"] no_cache_set_cookie = true # ============================= # OpenTelemetry Distributed Tracing # ============================= # Propagates trace context across all HTTP transports: # - HTTP/1.1 and HTTP/2 (W3C traceparent/tracestate + B3 headers) # - HTTP/3 / QUIC # - WebTransport # # Compatible backends: Jaeger, Grafana Tempo, Honeycomb, Datadog, OTEL Collector # Default OTLP HTTP port: 4318 (JSON), 4317 (gRPC โ not supported by this build) [otel] # Set to true to enable OTLP span export (default: false) enabled = false # Service name shown in the tracing UI (default: pqcrypta-proxy) service_name = "pqcrypta-proxy" # OTLP HTTP/JSON endpoint โ use your collector or tracing backend # Jaeger: http://localhost:4318 # Grafana Tempo: http://localhost:4318 # OTEL Collector: http://otel-collector:4318 otlp_endpoint = "http://localhost:4318" # Sampling: 1.0 = record every trace, 0.1 = record 10%, 0.0 = off # Uses ParentBased(TraceIdRatio) so downstream services inherit the decision. sample_ratio = 1.0 # Optional resource attributes added to every span (arbitrary key/value pairs) # [otel.resource_attributes] # deployment.environment = "production" # k8s.namespace = "pqcrypta" # ============================= # Security Headers # ============================= # These headers are automatically injected into all responses. # The Server header is always replaced with "PQCProxy v0.1.0". [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=()" # Cross-origin isolation headers # NOTE: require-corp/same-origin can break apps like Grafana that use modern JS cross_origin_opener_policy = "unsafe-none" cross_origin_embedder_policy = "unsafe-none" cross_origin_resource_policy = "cross-origin" # Additional security headers x_permitted_cross_domain_policies = "none" x_download_options = "noopen" x_dns_prefetch_control = "off" # Custom branding headers (advertise PQC capabilities) 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" # 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=?0 (not incremental) # u=3 is default urgency, i=?0 means non-incremental delivery priority = "u=3,i=?0" # ============================= # Post-Quantum Cryptography # ============================= # Two TLS backends available: # - rustls (default): Pure Rust, memory-safe, QUIC support, uses aws-lc-rs # - OpenSSL 3.5+: Broader algorithm support, hardware acceleration # # Algorithm Support Matrix: # | Algorithm | rustls | OpenSSL 3.5+ | # |---------------------|--------|--------------| # | X25519MLKEM768 | โ | โ | # | SecP256r1MLKEM768 | โณ | โ | # | SecP384r1MLKEM1024 | โณ | โ | # | ML-KEM-512/768/1024 | โ | โ | # | ML-DSA-44/65/87 | ๐ง | โ | # โ = Available, โณ = Planned, ๐ง = Requires pqc-signatures feature [pqc] # Enable PQC hybrid key exchange enabled = true # PQC provider selection: # - "auto" (recommended): Use rustls for QUIC, OpenSSL for broader algorithms # - "rustls": Pure Rust via aws-lc-rs (memory-safe, QUIC support) # - "openssl3.5": OpenSSL 3.5+ with native ML-KEM (broader algorithms) provider = "auto" # Path to OpenSSL 3.5+ binary (for OpenSSL backend) openssl_path = "/[system]/openssl-pq/..." # OpenSSL library path openssl_lib_path = "/[system]/openssl-pq/..." # Preferred KEM algorithm for key exchange (IETF standard) # Recommended: X25519MLKEM768 (NIST Level 3, best compatibility) # Options: # Hybrid (classical + PQC): # - X25519MLKEM768 (recommended, IETF standard) # - SecP256r1MLKEM768 (NIST curve variant) # - SecP384r1MLKEM1024 (Level 5 security) # - X448MLKEM1024 (maximum security) # Pure ML-KEM (FIPS 203): # - ML-KEM-512 (Level 1) # - ML-KEM-768 (Level 3) # - ML-KEM-1024 (Level 5) preferred_kem = "X25519MLKEM768" # Fallback to classical TLS if PQC is unavailable fallback_to_classical = true # Minimum security level (NIST levels 1-5) # 1 = 128-bit, 3 = 192-bit (recommended), 5 = 256-bit min_security_level = 3 # Additional KEM algorithms to offer (in preference order after preferred_kem) additional_kems = ["SecP256r1MLKEM768", "SecP384r1MLKEM1024"] # Enable PQC signatures (ML-DSA) - requires pqc-signatures feature enable_signatures = false # Require hybrid mode (reject pure PQC or pure classical) require_hybrid = false # Security hardening options: # Verify OpenSSL provider integrity at startup verify_provider = true # Check TLS key file permissions (should be 0600 or 0400) check_key_permissions = true # Fail startup if key permissions are insecure (vs just warning) # AUD-09: Set to true so a misconfigured deployment with world-readable TLS key # fails fast rather than silently serving traffic with an insecure key. strict_key_permissions = true # ============================= # Admin API Configuration # ============================= [admin] # Enable admin HTTP API enabled = true # Admin API bind address (use 127.0.0.1 for local-only access) bind_address = "[loopback]" # Admin API port port = [configured] # Require mTLS for admin API require_mtls = false # AUD-01: Bearer token for admin API authentication. # REQUIRED: Generate a strong random token and set it here. # Without this, any process on the host can call destructive endpoints # (shutdown, reload) without credentials. # # Generate with: openssl rand -base64 32 # Then set: auth_token = "<your-generated-token>" # # IMPORTANT: Do NOT leave this commented out in production. # auth_token = "CHANGE_ME_run_openssl_rand_base64_32_and_paste_result_here" # Allowed IP addresses for admin API access allowed_ips = ["[loopback]", "::1"] # ============================= # 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 connections_per_second = [configured] # ============================= # Advanced Multi-Dimensional Rate Limiting # ============================= # Cutting-edge rate limiting inspired by Cloudflare, Envoy, HAProxy, and Traefik. # 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 = ["[RFC1918]", "[RFC1918]", "[RFC1918]", "[loopback]"] # Key resolution strategy [advanced_rate_limiting.key_strategy] # Priority order for key resolution (first found wins) # Options: source_ip, real_ip, api_key, jwt_subject, ja3_fingerprint, ja4_fingerprint order = ["api_key", "jwt_subject", "ja3_fingerprint", "real_ip", "source_ip"] # Fallback key if none found fallback = "source_ip" # Enable composite keys (combine multiple keys) 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 = "pqcp" 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 # Use fingerprint as primary key for NAT scenarios blocked_fingerprints = [] # Block known malicious fingerprints # Unknown fingerprint limits (more restrictive) [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 # Disabled by default, enable for production baseline_window_secs = 3600 # 1 hour baseline learning sensitivity = [configured] auto_adjust = false min_samples = 1000 std_dev_multiplier = [configured] # Requests > mean + 3*std_dev = anomaly # Per-route rate limits (uncomment to enable) # Each route can have its own rate limits with optional key override and exemptions # [advanced_rate_limiting.route_limits.api_heavy] # pattern = "/api/heavy" # Route pattern (path prefix or regex) # key_override = "api_key" # Override key resolution for this route # exempt_keys = ["internal-service-key"] # Keys exempt from rate limiting # [advanced_rate_limiting.route_limits.api_heavy.limits] # requests_per_second = 10 # burst_size = 5 # requests_per_minute = 300 # requests_per_hour = 5000 # [advanced_rate_limiting.route_limits.public_api] # pattern = "/api/public/.*" # [advanced_rate_limiting.route_limits.public_api.limits] # requests_per_second = 50 # burst_size = 25 # requests_per_minute = 1500 # Composite key configuration (combine multiple keys for rate limiting) # Useful for tenant-aware rate limiting or complex scenarios # [[advanced_rate_limiting.composite_keys]] # name = "tenant-user" # Name for logging/metrics # keys = ["header:X-Tenant-ID", "jwt_subject"] # Keys to combine # routes = ["/api/.*"] # Routes this applies to (empty = all) # [advanced_rate_limiting.composite_keys.limits] # requests_per_second = 500 # burst_size = 250 # requests_per_minute = 15000 # requests_per_hour = 250000 # [[advanced_rate_limiting.composite_keys]] # name = "ip_plus_path" # keys = ["source_ip", "path"] # routes = [] # Empty = all routes # [advanced_rate_limiting.composite_keys.limits] # requests_per_second = 50 # burst_size = 25 # ============================= # Security Settings # ============================= [security] # Maximum request body size in bytes (10MB default) max_request_size = 10485760 # Maximum header size in bytes (64KB default) max_header_size = 65536 # Connection timeout in seconds connection_timeout_secs = 30 # Enable DoS protection dos_protection = true # Blocked IP addresses blocked_ips = [] # Allowed IP addresses (whitelist mode - empty = allow all) allowed_ips = [] # GeoIP database path for country blocking (optional) # geoip_db_path = "/[data]/..." # Blocked country codes (ISO 3166-1 alpha-2) blocked_countries = [] # e.g., ["CN", "RU", "KP", "IR"] # 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) # ============================= # TLS Fingerprint Detection (JA3/JA4) # ============================= [fingerprint] # Enable TLS fingerprint detection enabled = 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 # AUD-12: Automatically block connections presenting known-malicious JA3/JA4 fingerprints. # true (default) โ block IPs whose JA3/JA4 hash matches a Malicious entry in the fingerprint DB. # false โ advisory-only; Malicious fingerprints are logged but not blocked. # Only effective when fingerprint_db_path is configured and contains Malicious entries. block_malicious = true # ============================= # 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 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 = 10 # Maximum total connections per backend host max_connections_per_host = 100 # Connection acquire timeout (milliseconds) acquire_timeout_ms = 5000 # 5 seconds # ============================= # Load Balancer Configuration # ============================= # Configure load balancing for backend pools with multiple servers. # Supports: least_connections (default), round_robin, weighted_round_robin, # random, ip_hash, least_response_time [load_balancer] # Enable load balancing (automatically enabled when backend_pools defined) enabled = true # Default algorithm for all pools (can be overridden per-pool) # 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] # Enable session affinity globally enabled = false # Cookie name for session tracking cookie_name = "PQCPROXY_BACKEND" # Cookie TTL in seconds (0 = session cookie) cookie_ttl_secs = 3600 # Use secure cookies (HTTPS only) cookie_secure = true # Use HttpOnly cookies (prevent XSS access) cookie_httponly = true # SameSite attribute: strict, lax, none 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 Pools (Load Balanced) # ============================= # Backend pools define multiple servers for load balancing. # Routes can reference pools by name just like single backends. # Pools take priority over single backends with the same name. # Example: API pool with 3 servers using least_connections [backend_pools.api-pool] name = "api-pool" algorithm = "least_connections" # Override default algorithm health_aware = true # Skip unhealthy backends affinity = "none" # Options: none, cookie, ip_hash, header # affinity_header = "X-Session-ID" # Header name when affinity = "header" # queue_max_size = 500 # Pool-specific queue size (overrides global) # queue_timeout_ms = 3000 # Pool-specific queue timeout (overrides global) health_check_path = "/health" health_check_interval_secs = 10 # Canary / percentage traffic splitting for api-pool # Set enabled = true and mark a server with canary = true to activate. [backend_pools.api-pool.canary] enabled = false # flip to true to enable canary routing sticky = true # keep users on the same canary server sticky_cookie_name = "PQCPROXY_CANARY" # cookie written on first canary assignment sticky_cookie_ttl_secs = 3600 # cookie lifetime in seconds (1 hour) # sticky_header = "X-Canary-Group" # optional: pre-assign group via request header auto_rollback = true # auto-suspend on high error rate rollback_error_rate = 0.05 # suspend when canary error rate exceeds 5 % rollback_window_secs = 60 # sliding window for error tracking (seconds) rollback_min_requests = 10 # require at least 10 requests before rollback triggers [[backend_pools.api-pool.servers]] address = "[loopback]" weight = 100 # For weighted algorithms priority = 1 # Lower = higher priority for failover max_connections = 100 timeout_ms = 30000 tls_mode = "terminate" [[backend_pools.api-pool.servers]] address = "[loopback]" weight = 100 priority = 1 max_connections = 100 timeout_ms = 30000 tls_mode = "terminate" # AUD-04: Example secondary/failover node โ replace with your real remote server address. # [[backend_pools.api-pool.servers]] # address = "[private]:3003" # weight = 50 # Half the traffic (weighted algorithms) # priority = 2 # Failover only (used when priority 1 servers down) # max_connections = 50 # timeout_ms = 30000 # tls_mode = "terminate" # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ # Canary / Percentage Traffic Splitting # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ # Enable canary routing in any backend pool by (a) adding a [backend_pools.NAME.canary] # section and (b) setting canary = true on one or more servers with a # canary_weight_percent value (0-100). # # How it works: # 1. Incoming requests that carry the sticky canary cookie are sent to the same # canary server they were assigned to before (consistent experience). # 2. New requests are probabilistically routed: if rand(0,100) < canary_weight_percent # the canary server is chosen; otherwise a normal stable server is used. # 3. If auto_rollback is enabled and the canary's error rate in the rolling window # exceeds rollback_error_rate, the canary is suspended automatically. # 4. Use the admin API to inspect, suspend, resume, or adjust weight at runtime: # GET /canary # POST /canary/suspend/:server_id # POST /canary/resume/:server_id # POST /canary/weight/:server_id (body: {"percent": 10}) # # [backend_pools.api-pool.canary] # enabled = true # sticky = true # Once assigned, keep same canary # sticky_cookie_name = "PQCPROXY_CANARY" # Cookie used for sticky routing # sticky_cookie_ttl_secs = 3600 # Cookie lifetime (1 hour) # sticky_header = "X-Canary-Group" # Optional: pre-assign via request header # auto_rollback = true # Suspend canary on high error rate # rollback_error_rate = 0.05 # 5 % errors โ auto-suspend # rollback_window_secs = 60 # Sliding window for error tracking # rollback_min_requests = 10 # Minimum requests before rollback triggers # # Canary server (mark with canary = true): # [[backend_pools.api-pool.servers]] # address = "[loopback]" # canary = true # Mark as canary server # canary_weight_percent = 5 # Route 5 % of new traffic here # weight = 100 # priority = 1 # max_connections = 100 # timeout_ms = 30000 # tls_mode = "terminate" # Example: Apache pool with IP hash for session stickiness [backend_pools.apache-pool] name = "apache-pool" algorithm = "ip_hash" # Consistent hashing by client IP health_aware = true affinity = "ip_hash" # Built-in IP-based stickiness health_check_path = "/health" health_check_interval_secs = 10 [[backend_pools.apache-pool.servers]] address = "[loopback]" weight = 100 priority = 1 max_connections = 200 [[backend_pools.apache-pool.servers]] address = "[loopback]" weight = 100 priority = 1 max_connections = 200 # Example: Session pool with cookie-based sticky sessions [backend_pools.session-pool] name = "session-pool" algorithm = "round_robin" health_aware = true affinity = "cookie" # Cookie-based sticky sessions health_check_path = "/health" health_check_interval_secs = 10 # AUD-04/AUD-13: Replace with your actual backend addresses. [[backend_pools.session-pool.servers]] address = "[loopback]" weight = 100 priority = 1 max_connections = 100 [[backend_pools.session-pool.servers]] address = "[loopback]" weight = 100 priority = 1 max_connections = 100 # Example: Pool with header-based affinity and TLS to backends [backend_pools.secure-pool] name = "secure-pool" algorithm = "round_robin" health_aware = true affinity = "header" # Header-based sticky sessions affinity_header = "X-Session-ID" # Required when affinity = "header" queue_max_size = 200 # Pool-specific queue max size queue_timeout_ms = 2000 # Pool-specific queue timeout health_check_path = "/health" health_check_interval_secs = 15 # Pool server with TLS re-encryption to backend [[backend_pools.secure-pool.servers]] address = "[private]:443" weight = 100 priority = 1 max_connections = 50 timeout_ms = 30000 tls_mode = "reencrypt" # TLS to backend tls_cert = "/[system]/[key-material]" # CA cert for verification tls_skip_verify = false # DANGEROUS if true - only for testing tls_sni = "backend1.internal.com" # Custom SNI hostname [[backend_pools.secure-pool.servers]] address = "[private]:443" weight = 100 priority = 1 max_connections = 50 timeout_ms = 30000 tls_mode = "reencrypt" tls_cert = "/[system]/[key-material]" tls_skip_verify = false tls_sni = "backend2.internal.com" # ============================= # Backend Definitions (Single) # ============================= # Backends support three TLS modes: # - terminate (default): TLS terminates at proxy, plain HTTP to backend # - reencrypt: TLS terminates at proxy, new HTTPS connection to backend # - passthrough: No TLS termination, SNI-based routing (see [[passthrough_routes]]) # Apache/PHP backend (TLS Terminate - default) [backends.apache] name = "apache" type = "http1" address = "[loopback]" tls_mode = "terminate" # Default - can be omitted timeout_ms = 30000 max_connections = 100 health_check = "/health" health_check_interval_secs = 30 # Rust API backend (TLS Terminate) [backends.api] name = "api" type = "http1" address = "[loopback]" tls_mode = "terminate" timeout_ms = 30000 max_connections = 100 health_check = "/health" health_check_interval_secs = 30 # Internal HTTPS API (TLS Re-encrypt with mTLS) [backends.internal-api] name = "internal-api" type = "http1" address = "internal.example.com:443" tls_mode = "reencrypt" # Custom CA for backend certificate verification tls_cert = "/[system]/[key-material]" # mTLS client certificate (optional) tls_client_cert = "/[system]/[key-material]" tls_client_key = "/[system]/[key-material]" # DANGEROUS: Only set true for testing tls_skip_verify = false # Custom SNI hostname (optional) tls_sni = "internal.example.com" timeout_ms = 30000 max_connections = 50 health_check = "/health" health_check_interval_secs = 30 # PHP-FPM backend via Unix socket [backends.php] name = "php" type = "unix" address = "unix:/run/php-fpm.sock" timeout_ms = 30000 max_connections = 100 health_check = "/health" health_check_interval_secs = 30 # HTTP/2 backend with TLS [backends.http2-backend] name = "http2-backend" type = "http2" address = "api.internal.example.com:443" tls_mode = "reencrypt" tls_skip_verify = false timeout_ms = 30000 max_connections = 50 health_check = "/health" health_check_interval_secs = 30 # HTTP/3 backend (QUIC to QUIC) [backends.quic-backend] name = "quic-backend" type = "http3" address = "backend.example.com:4433" tls_mode = "reencrypt" timeout_ms = 30000 max_connections = 50 # Raw TCP backend [backends.tcp-service] name = "tcp-service" type = "tcp" address = "[loopback]" timeout_ms = 10000 max_connections = 100 # Grafana monitoring dashboard [backends.grafana] name = "grafana" type = "http1" address = "[loopback]" tls_mode = "terminate" timeout_ms = 30000 max_connections = 50 health_check = "/api/health" health_check_interval_secs = 30 # Prometheus metrics server [backends.prometheus] name = "prometheus" type = "http1" address = "[loopback]" tls_mode = "terminate" timeout_ms = 30000 max_connections = 50 health_check = "/-/healthy" health_check_interval_secs = 30 # ============================= # TLS Passthrough Routes (SNI Routing) # ============================= # These routes bypass TLS termination entirely. # Traffic is routed based on SNI (Server Name Indication) without decryption. # Use for services that must handle their own TLS or require end-to-end encryption. # AUD-04/AUD-13: Passthrough routes contain actual backend addresses โ never commit real # infrastructure IPs here. The examples below use documentation-only addresses # (RFC 5737 TEST-NET-1 192.0.2.0/24) to avoid disclosing real topology. # Example: External service with SNI routing [[passthrough_routes]] name = "external-mail" sni = "mail.example.com" backend = "192.0.2.10:443" proxy_protocol = false # Enable for client IP preservation timeout_ms = 30000 # Example: Wildcard SNI routing (matches *.internal.example.com) [[passthrough_routes]] name = "internal-wildcard" sni = "*.internal.example.com" backend = "192.0.2.20:443" proxy_protocol = true timeout_ms = 30000 # Example: Legacy service requiring passthrough [[passthrough_routes]] name = "legacy-service" sni = "legacy.example.com" backend = "192.0.2.100:443" timeout_ms = 60000 # ============================= # Route Configuration # ============================= # Routes are matched by priority (lower = higher priority). # Routes support: domain matching, path matching, CORS, redirects, headers override. # API subdomain route with CORS [[routes]] name = "api-route" host = "api.example.com" path_prefix = "/" backend = "api" forward_client_identity = true client_identity_header = "X-Client-IP" priority = 100 [routes.cors] allow_origin = "https://example.com" allow_methods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"] allow_headers = ["Content-Type", "Authorization", "X-API-Key", "X-Request-ID"] allow_credentials = true max_age = 86400 [routes.add_headers] X-Forwarded-Proto = "https" X-API-Version = "v1" # Traffic shadowing example โ uncomment to mirror 10% of API traffic to a canary: # [routes.shadow] # backend = "api-canary" # Must be a key in [backends.*] # percent = 10 # 0โ100 % of requests to mirror # timeout_ms = 5000 # Shadow task abandoned after this many ms # shadow_header = "X-Shadow-Request" # shadow_header_value = "1" # log_responses = true # Main site route (Apache backend) [[routes]] name = "main-site" host = "example.com" path_prefix = "/" backend = "apache" forward_client_identity = true priority = 100 # www redirect to non-www [[routes]] name = "www-redirect" host = "www.example.com" path_prefix = "/" redirect = "https://example.com" redirect_permanent = true priority = 1 # Internal API route (TLS re-encrypt) [[routes]] name = "internal-api-route" host = "example.com" path_prefix = "/internal-api" backend = "internal-api" forward_client_identity = true priority = 50 # SEO redirect: underscore to hyphen [[routes]] name = "seo-old-path" host = "example.com" path_prefix = "/old_path" redirect = "/new-path" redirect_permanent = true priority = 1 # SEO redirect: legacy URL [[routes]] name = "seo-legacy" host = "example.com" path_prefix = "/legacy_page" redirect = "/modern-page" redirect_permanent = true priority = 1 # WebTransport route for real-time encryption [[routes]] name = "webtransport-encrypt" host = "api.example.com" path_prefix = "/encrypt" webtransport = true backend = "api" stream_to_method = "POST" forward_client_identity = true priority = 10 [routes.add_headers] X-WebTransport = "true" # WebTransport route for streaming [[routes]] name = "webtransport-stream" host = "api.example.com" path_prefix = "/stream" webtransport = true backend = "api" stream_to_method = "POST" forward_client_identity = true priority = 10 # Route with custom headers override [[routes]] name = "custom-headers" host = "example.com" path_prefix = "/embed" backend = "apache" priority = 50 # Override global security headers for this route [routes.headers_override] x_frame_options = "SAMEORIGIN" # Allow embedding from same origin cross_origin_embedder_policy = "unsafe-none" # Disable for legacy content # PHP-FPM route [[routes]] name = "php-route" host = "example.com" path_prefix = "/app" backend = "php" forward_client_identity = true priority = 80 # Regex path matching (requires path_regex instead of path_prefix) [[routes]] name = "api-versioned" host = "api.example.com" path_regex = "^/v[0-9]+/.*" backend = "api" forward_client_identity = true priority = 20 # Exact path match (stricter than path_prefix) [[routes]] name = "exact-health" host = "api.example.com" path_exact = "/health" # Only matches /health exactly, not /health/db backend = "api" priority = 5 # Route with headers removal [[routes]] name = "sanitized-api" host = "api.example.com" path_prefix = "/public" backend = "api" forward_client_identity = true priority = 60 remove_headers = ["X-Internal-Token", "X-Debug-Info"] # Headers to strip before sending to backend [routes.add_headers] X-Public-Request = "true" # Route allowing HTTP/1.1 (for search bots like Googlebot that don't support HTTP/3) [[routes]] name = "seo-friendly" host = "example.com" path_prefix = "/sitemap" backend = "apache" allow_http11 = true # Allow HTTP/1.1 for this route (search bots) skip_bot_blocking = true # Don't block bots on this route priority = 30 # Route with Stripe.js compatibility (removes COEP/COOP headers that break Stripe) [[routes]] name = "checkout-page" host = "example.com" path_prefix = "/checkout" backend = "apache" stripe_compatibility = true # Removes Cross-Origin-Embedder-Policy and Cross-Origin-Opener-Policy priority = 40 # Route with custom timeout override [[routes]] name = "long-running-api" host = "api.example.com" path_prefix = "/export" backend = "api" timeout_override_ms = 120000 # 2 minute timeout for long exports forward_client_identity = true priority = 45 # Grafana monitoring dashboard route [[routes]] name = "grafana" host = "pqcrypta.com" path_prefix = "/grafana" backend = "grafana" forward_client_identity = true client_identity_header = "X-Real-IP" stripe_compatibility = true # Removes COEP/COOP headers that can break Grafana login priority = 20 [routes.add_headers] X-Forwarded-Proto = "https" X-Forwarded-Host = "pqcrypta.com" [routes.headers_override] cross_origin_embedder_policy = "unsafe-none" cross_origin_opener_policy = "unsafe-none" # Prometheus metrics route [[routes]] name = "prometheus" host = "pqcrypta.com" path_prefix = "/prometheus" backend = "prometheus" forward_client_identity = true client_identity_header = "X-Real-IP" priority = 20 [routes.add_headers] X-Forwarded-Proto = "https" X-Forwarded-Host = "pqcrypta.com" # Wildcard catch-all route [[routes]] name = "default" path_prefix = "/" backend = "apache" priority = 1000 # ============================= # Database Blocklist Sync # ============================= # Synchronize blocklists from PostgreSQL database. # The API server's WAF/honeypot system stores detected threats in the database. # This enables the proxy to automatically block known malicious IPs/fingerprints. [database] # Enable database sync for blocklists enabled = true # PostgreSQL connection settings # Credentials are read from environment variables for security: # PQCRYPTA_DB_HOST, PQCRYPTA_DB_PORT, PQCRYPTA_DB_USER, # PQCRYPTA_DB_PASSWORD, PQCRYPTA_DB_NAME host = "localhost" port = 5432 database = "pqcrypta" user = "pqcrypta_user" # Password should be set via PQCRYPTA_DB_PASSWORD environment variable # password = "" # DO NOT hardcode - use env var # Connection pool settings min_connections = 2 max_connections = 10 connection_timeout_secs = 10 # Sync interval in seconds (how often to refresh blocklist from database) sync_interval_secs = 60 # Maximum blocklist size (memory protection) max_blocklist_size = 50000 # Blocklist queries - customize based on your schema [database.blocklist_queries] # Query for blocked IPs (should return: ip_address, reason, expires_at) blocked_ips = """ SELECT ip_address, reason, expires_at FROM bot_blocklist WHERE is_active = true AND (expires_at IS NULL OR expires_at > NOW()) """ # Query for blocked fingerprints (should return: fingerprint, type, reason) blocked_fingerprints = """ SELECT fingerprint, fingerprint_type, reason FROM fingerprint_blocklist WHERE is_active = true AND (expires_at IS NULL OR expires_at > NOW()) """ # Query for blocked countries (should return: country_code, reason) blocked_countries = """ SELECT country_code, reason FROM country_blocklist WHERE is_active = true """ # Query to log proxy-level detections back to database # This bridges the gap between proxy detection and API WAF detection [database.logging] enabled = true # Log blocked requests to database for analytics log_blocked_requests = true # Log suspicious patterns detected at proxy level log_suspicious_patterns = true # Table for proxy-level detections detection_table = "proxy_detections" # Insert query template (use $1, $2, etc. for parameters) insert_detection = """ INSERT INTO proxy_detections (ip_address, fingerprint, path, method, user_agent, detection_type, blocked, timestamp) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW()) """ # ============================= # Configuration Notes # ============================= # TLS Modes Explained: # -------------------- # 1. TERMINATE (default): TLS terminates at proxy # - Client โ(HTTPS)โ Proxy โ(HTTP)โ Backend # - Most common mode for internal backends # - Proxy can inspect/modify traffic # # 2. REENCRYPT: TLS terminates and re-establishes # - Client โ(HTTPS)โ Proxy โ(HTTPS)โ Backend # - Use for backends requiring end-to-end encryption # - Supports mTLS for mutual authentication # - Proxy can still inspect traffic # # 3. PASSTHROUGH: No TLS termination (SNI routing) # - Client โ(HTTPS)โ Proxy โ(HTTPS)โ Backend (same TLS session) # - Use for services handling their own TLS # - Proxy cannot inspect traffic # - Configured via [[passthrough_routes]], not backends # Security Headers: # ----------------- # The proxy automatically injects security headers from [headers] section. # The Server header is always replaced with "PQCProxy v0.1.0" to hide # backend identity (Apache, nginx, etc.). # CORS Handling: # -------------- # When [routes.cors] is configured: # - OPTIONS preflight requests are handled automatically # - CORS headers are added to all responses # - Credentials and custom headers supported # HTTP Redirect: # -------------- # When [http_redirect] is enabled: # - Port 80 redirects all requests to HTTPS # - Uses 301 (permanent) redirect # - Preserves path and query string # Alt-Svc Advertisement: # ---------------------- # All responses include Alt-Svc header advertising HTTP/3: # Alt-Svc: h3=":443"; ma=86400, h3=":4433"; ma=86400, h3=":4434"; ma=86400 # Hot Reload: # ----------- # Configuration can be reloaded without restart: # curl -X POST http://127.0.0.1:8081/reload # TLS certificates can be reloaded separately: # curl -X POST http://127.0.0.1:8081/reload -d '{"tls_only":true}' # Route Options Reference: # ------------------------ # Path Matching (use one): # - path_prefix: Matches if path starts with value (e.g., "/api" matches "/api/v1") # - path_exact: Matches only the exact path (e.g., "/health" won't match "/health/db") # - path_regex: Regex pattern for complex matching (e.g., "^/v[0-9]+/.*") # # Headers: # - add_headers: Headers to add to backend request # - remove_headers: Headers to strip before sending to backend # - headers_override: Override global security headers for this route # # Special Modes: # - allow_http11: Allow HTTP/1.1 for search bots (Googlebot doesn't support HTTP/3) # - skip_bot_blocking: Don't apply bot protection to this route # - stripe_compatibility: Removes COEP/COOP headers that break Stripe.js # - timeout_override_ms: Custom timeout for slow endpoints # # Backend Pool Options: # --------------------- # - affinity: Session stickiness mode (none, cookie, ip_hash, header) # - affinity_header: Header name when affinity = "header" # - queue_max_size: Pool-specific queue size (overrides [load_balancer.queue]) # - queue_timeout_ms: Pool-specific queue timeout # # Pool Server TLS Options: # ------------------------ # - tls_mode: terminate, reencrypt, or passthrough # - tls_cert: CA certificate for backend verification # - tls_skip_verify: Skip verification (DANGEROUS - testing only) # - tls_sni: Custom SNI hostname for TLS handshake