TLS and reverse proxy¶
The Kneo Agent Platform service speaks plain HTTP. It does not terminate TLS, parse
X-Forwarded-* headers itself, or rate-limit by IP. Any deployment that
faces a network beyond 127.0.0.1 must run behind a reverse proxy that
terminates TLS and shields the service.
For deployment shapes (Container, Compose, Embedded) and the choice of
persistence backend, see deployment.md. For the
hardening checklist that includes TLS, see
security_hardening.md.
Topology¶
client ──HTTPS──► reverse proxy ──HTTP──► kneo-serv
(TLS termination,
request size limits,
rate limiting,
client-IP injection)
The proxy is responsible for:
- TLS termination and certificate management
- Request body size limits at the edge (defense in depth above
KNEO_SERV_MAX_BODY_BYTES) - IP-based rate limiting if you need it (the service has no built-in per-IP limiter)
- Forwarding the client IP for the service's structured logs
Run the proxy and kneo-serv on the same host (or in the same private
network) so the unencrypted hop is not exposed.
Bind address¶
| Topology | --host value |
Rationale |
|---|---|---|
| Proxy + service on the same host | 127.0.0.1 |
Service is unreachable except through the proxy. |
| Proxy + service in a shared private network | 0.0.0.0 |
The network boundary is the proxy; firewall the service port. |
Compose (compose.yaml) |
0.0.0.0 inside the container; only the proxy's port is published on the host. |
The Compose stack's internal network already isolates the API service. |
The Dockerfile defaults to --host 0.0.0.0 --port 8000. Override with
KNEO_SERV_PORT for the published host-side port; the container port is
fixed at 8000.
Trusted-proxy headers¶
The service logs the immediate TCP peer as client_ip in its structured
request logs (observability.md). When a proxy
fronts the service, the immediate peer is the proxy, not the original
client. To capture the real client IP in logs and traces, configure the
proxy to write X-Forwarded-For upstream and ingest it at your log
aggregator — the service itself does not rewrite client_ip from
X-Forwarded-For (no implicit trust).
The service does honor X-Request-ID and echoes it back on the response.
Proxies that already inject a request ID should pass it through; the
service generates a UUID per request otherwise.
Reverse-proxy snippets¶
These are minimal examples. Production configurations should add timeouts, buffer sizing, and rate-limit zones; consult your proxy's docs.
nginx¶
server {
listen 443 ssl http2;
server_name kneo.example.com;
ssl_certificate /etc/letsencrypt/live/kneo.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/kneo.example.com/privkey.pem;
client_max_body_size 2m; # match or exceed KNEO_SERV_MAX_BODY_BYTES
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Request-ID $request_id;
proxy_read_timeout 130s; # exceed KNEO_SERV_CLIENT_TIMEOUT
}
}
Caddy¶
kneo.example.com {
reverse_proxy 127.0.0.1:8000 {
header_up X-Forwarded-For {remote_host}
header_up X-Request-ID {http.request.uuid}
}
request_body {
max_size 2MB
}
}
AWS ALB / generic L7 load balancer¶
- Listener: HTTPS 443 with an ACM certificate; redirect 80 → 443.
- Target group: HTTP, port 8000, healthcheck
GET /readyz(interval 30s, unhealthy threshold 3, success codes200)./readyzis unauthenticated by design. - Idle timeout: ≥
KNEO_SERV_CLIENT_TIMEOUT(default 120s); set 130s for a safety margin. - Body size: ALBs cap at 1 MiB by default — if you accept larger inline
specs (
KNEO_SERV_MAX_INLINE_SPEC_BYTESis 256 KiB by default), use a CloudFront or nginx tier in front and bypass the ALB cap accordingly.
Health-check endpoints behind the proxy¶
Expose /livez and /readyz directly to the proxy or load balancer.
Both are unauthenticated to keep probe integration simple. Do not
expose /readyz to the public internet — its 503 not_ready payload
includes internal check names and registry contents that should stay
inside the operational perimeter.
For most setups: bind the proxy's probe routes to internal listeners only, or restrict the source IP range to your load-balancer subnet.
Verifying TLS is actually in front¶
# TLS terminates at the proxy, service is unreachable directly.
curl -sf https://kneo.example.com/readyz | jq '.metadata.ready' # → true
curl -sf http://kneo.example.com/readyz # → connection refused / 301
curl -sf http://127.0.0.1:8000/readyz # → only succeeds from the proxy host
If the third command succeeds from outside the proxy host, the service port is reachable from the public network and the bind address or firewall is misconfigured.
What kneo-serv does not provide¶
- No built-in TLS. Terminate at the proxy.
- No
X-Forwarded-Forrewriting. Capture client IPs at the proxy or in your log aggregator. - No per-IP rate limiting. Use the proxy's rate-limit zone.
- No mTLS to upstream providers. Provider connections go out from the service host; lock down egress at the network layer.
See security_hardening.md for the full
pre-launch checklist.