{
# Disable admin API — no runtime config changes allowed
admin off
# Let's Encrypt contact
email {$ACME_EMAIL}
# Prefer HTTP/2 and HTTP/3; disable insecure protocols globally
servers {
protocols h1 h2 h3
}
}
{$DOMAIN} {
# TLS 1.3 only — no legacy protocol negotiation
tls {
protocols tls1.3 tls1.3
}
# Security headers — applied to every response
header {
# HSTS: 2 years, include subdomains, eligible for preload list
Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
# Prevent MIME sniffing
X-Content-Type-Options "nosniff"
# No framing
X-Frame-Options "DENY"
# No referrer information leaked
Referrer-Policy "no-referrer"
# CSP: this is an API — no content to render
Content-Security-Policy "default-src 'none'"
# Permissions policy: deny all browser features
Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()"
# Remove server fingerprinting headers
-Server
-X-Powered-By
}
# Rate limiting: 60 requests/minute per source IP
# Protects against token brute-force and job flooding
rate_limit {remote_host} 60r/m
# Health endpoint — lower rate limit not needed, it's just a ping
handle /health {
reverse_proxy kx:8080
}
# All other routes — proxied to kx serve
handle {
reverse_proxy kx:8080 {
# Pass real client IP to kx (for logging/audit)
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
# Never forward Authorization header downstream (it's consumed here)
# kx validates its own bearer token independently
transport http {
# How long to wait for kx to accept the connection
dial_timeout 5s
# Agent jobs can run for minutes — give them room
response_header_timeout 600s
keepalive 30s
keepalive_idle_conns 10
}
}
}
# Structured access log
log {
output file /data/logs/access.log {
roll_size 20mb
roll_keep 10
}
format json
}
}