Configuration Reference

June 1, 2026 · View on GitHub

Configuration is read from a single file in TOML or YAML. The format is chosen from the path’s extension: .yaml / .yml → YAML; .toml and anything else (including a missing extension) → TOML. Pass the file path as the positional CONFIG argument or via HUGINN_CONFIG_PATH:

huginn-proxy config.toml
huginn-proxy config.yaml
HUGINN_CONFIG_PATH=config.yaml huginn-proxy

Validate a config file without starting the proxy (like nginx -t):

huginn-proxy --validate config.toml
huginn-proxy --validate config.yaml

Hot reload: dynamic sections update on SIGHUP or file-watcher trigger without dropping connections. Static sections require a process restart — changes are logged as a warning and ignored. See DEPLOYMENT.md for the full static/dynamic split.


Top-level keys

In TOML, these bare keys must appear before any [table] header. In YAML, use a normal mapping; key order does not matter.

KeyTypeDefaultDescription
preserve_hostboolfalseForward the original Host header from the client to the backend. When false, the proxy substitutes its own Host with the backend address.
TOML YAML
preserve_host = false
preserve_host: false

[listen]

Network interfaces and socket options. Static — requires restart to change.

KeyTypeDefaultDescription
addrsarray of stringsOne or more host:port addresses to bind. IPv6 addresses must be wrapped in brackets.
tcp_backloginteger4096Kernel listen(2) backlog per socket. Increase under heavy connection bursts.
TOML YAML
[listen]
addrs = ["0.0.0.0:7000", "[::]:7000"]
# tcp_backlog = 4096
listen:
  addrs:
    - "0.0.0.0:7000"
    - "[::]:7000"
  # tcp_backlog: 4096

[[backends]]

Backend servers for forwarding. Repeat the header for each backend. Dynamic (hot-reloadable).

KeyTypeDefaultDescription
addressstringhost:port of the backend. Used as the pool key — must match exactly what routes reference.
http_versionstringnull (preserve)Protocol to use when connecting to this backend. "http11", "http2", or "preserve" (negotiate based on what the client used).
health_checktablenull (off)Optional active health probe. When set, the proxy tracks per-upstream health and returns 502 to clients when the backend is marked unhealthy. If you omit this key (or leave the backend without a health_check table), that backend is not probed and the health gate does not apply to it. See [backends.health_check] below.
TOML YAML
[[backends]]
address = "backend-a:9000"
http_version = "preserve"

[[backends]]
address = "backend-b:9000"
http_version = "http11"
backends:
  - address: "backend-a:9000"
    http_version: preserve
  - address: "backend-b:9000"
    http_version: http11

[backends.health_check]

Optional. Dynamic (hot-reloadable). If absent, the backend is always treated as healthy for the gate (no background task).

KeyTypeDefaultDescription
typestringtcptcp (TCP 3-way handshake to address) or http (HTTP/1.1 GET to http://{address}{path}; plain HTTP only, no TLS to upstream).
pathstringRequired when type = "http": must start with / (e.g. / or /ready). Ignored for tcp.
expected_statusint200For http only: response status must match (e.g. 200, 204).
interval_secsint10Time between probes.
timeout_secsint5Per-probe budget (must be ≤ interval_secs). Encompasses connect, request, and body drain for http.
unhealthy_thresholdint3Consecutive failed probes before marking upstream unhealthy.
healthy_thresholdint2Consecutive successful probes before marking upstream healthy again.
TOML YAML
[[backends]]
address = "app:8080"
http_version = "http11"
# HTTP GET http://app:8080/ready must return 200
health_check = { type = "http", path = "/ready", expected_status = 200 }
backends:
  - address: "app:8080"
    http_version: http11
    health_check:
      type: http
      path: /ready
      expected_status: 200

[[routes]]

Path-prefix routing rules. First match wins — order matters. Dynamic (hot-reloadable).

KeyTypeDefaultDescription
prefixstringURL path prefix to match. Use "/" as a catch-all default route.
backendstringBackend address to forward to. Must match a [[backends]].address exactly.
fingerprintingbooltrueInject TLS/HTTP fingerprint headers (x-tls-ja4*, x-http2-akamai, x-tcp-p0f) for this route.
force_new_connectionboolfalseBypass the connection pool — opens a fresh TCP+TLS connection per request. Required when you need a fresh TLS handshake for each request (e.g. JA4 extraction on every request). Adds latency.
replace_pathstringnullPath prefix replacement. Empty string ("") strips the prefix. Any other value replaces the matched prefix. Absent = forward as-is.
rate_limittablePer-route rate limit overrides. See [routes.rate_limit] below.
headerstablePer-route header manipulation. Same shape as [headers].
TOML YAML
# Forward /api to backend-a, with fingerprinting
[[routes]]
prefix = "/api"
backend = "backend-a:9000"
fingerprinting = true

# Strip /strip prefix: /strip/users → /users
[[routes]]
prefix = "/strip"
backend = "backend-a:9000"
replace_path = ""

# Rewrite /old prefix: /old/data → /new/data
[[routes]]
prefix = "/old"
backend = "backend-b:9000"
replace_path = "/new"

# Catch-all
[[routes]]
prefix = "/"
backend = "backend-b:9000"
routes:
  # Forward /api to backend-a, with fingerprinting
  - prefix: "/api"
    backend: "backend-a:9000"
    fingerprinting: true

  # Strip /strip prefix: /strip/users → /users
  - prefix: "/strip"
    backend: "backend-a:9000"
    replace_path: ""

  # Rewrite /old prefix: /old/data → /new/data
  - prefix: "/old"
    backend: "backend-b:9000"
    replace_path: "/new"

  # Catch-all
  - prefix: "/"
    backend: "backend-b:9000"

[routes.rate_limit]

Overrides the global [security.rate_limit] for this specific route. Only the keys you set override the global; unset keys fall back to the global config.

KeyTypeDefaultDescription
enabledboolnullOverride whether rate limiting is active for this route.
requests_per_secondintegernullOverride RPS limit.
burstintegernullOverride burst size.
limit_bystringnullOverride limit key strategy: "ip", "header", "route", "combined".
limit_by_headerstringnullOverride header name when limit_by = "header".
TOML YAML
[[routes]]
prefix = "/api"
backend = "backend-a:9000"

[routes.rate_limit]
enabled = true
requests_per_second = 50
burst = 100
limit_by = "combined"

[[routes]]
prefix = "/public"
backend = "backend-b:9000"

[routes.rate_limit]
enabled = false   # disable rate limiting for this route
routes:
  - prefix: "/api"
    backend: "backend-a:9000"
    rate_limit:
      enabled: true
      requests_per_second: 50
      burst: 100
      limit_by: combined
  - prefix: "/public"
    backend: "backend-b:9000"
    rate_limit:
      enabled: false # disable rate limiting for this route

[routes.headers]

Per-route header manipulation. Same shape as [headers]. Applied after global headers.

TOML YAML
[[routes]]
prefix = "/api"
backend = "backend-a:9000"

[routes.headers.request]
add = [{ name = "X-Internal-Route", value = "api" }]

[routes.headers.response]
remove = ["X-Backend-Id"]
routes:
  - prefix: "/api"
    backend: "backend-a:9000"
    headers:
      request:
        add:
          - name: "X-Internal-Route"
            value: "api"
      response:
        remove:
          - "X-Backend-Id"

[headers]

Global header manipulation applied to every request/response. Dynamic (hot-reloadable).

[headers.request]

KeyTypeDefaultDescription
addarray of {name, value}[]Headers to add to the upstream request. Overwrites if already present.
removearray of strings[]Header names to remove from the upstream request.

[headers.response]

KeyTypeDefaultDescription
addarray of {name, value}[]Headers to add to the client response.
removearray of strings[]Header names to remove from the client response.
TOML YAML
[headers.request]
remove = ["X-Forwarded-Server"]
add = [
    { name = "X-Proxy-Name", value = "huginn-proxy" },
]

[headers.response]
remove = ["Server", "X-Powered-By"]
add = [
    { name = "X-Proxy", value = "huginn-proxy" },
]
headers:
  request:
    remove:
      - "X-Forwarded-Server"
    add:
      - name: "X-Proxy-Name"
        value: "huginn-proxy"
  response:
    remove:
      - "Server"
      - "X-Powered-By"
    add:
      - name: "X-Proxy"
        value: "huginn-proxy"

[tls]

TLS termination. Omit the entire section to run as plain HTTP. Static — requires restart to change (cert/key file contents are hot-reloaded separately via file watcher).

KeyTypeDefaultDescription
cert_pathstringPath to the server certificate PEM file.
key_pathstringPath to the private key PEM file.
alpnarray of strings[]ALPN protocols to advertise. Use ["h2", "http/1.1"] to support both HTTP/2 and HTTP/1.1 with negotiation.
TOML YAML
[tls]
cert_path = "/config/certs/server.crt"
key_path = "/config/certs/server.key"
alpn = ["h2", "http/1.1"]
tls:
  cert_path: "/config/certs/server.crt"
  key_path: "/config/certs/server.key"
  alpn:
    - "h2"
    - "http/1.1"

[tls.options]

KeyTypeDefaultDescription
versionsarray of strings["1.2", "1.3"]Allowed TLS versions. Values: "1.2", "1.3".
cipher_suitesarray of stringsall supportedNamed cipher suites. Restrict to tighten security posture.
curve_preferencesarray of stringsall supportedNamed elliptic curves for key exchange.
TOML YAML
[tls.options]
versions = ["1.2", "1.3"]
cipher_suites = [
    "TLS13_AES_128_GCM_SHA256",
    "TLS13_AES_256_GCM_SHA384",
    "TLS13_CHACHA20_POLY1305_SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
]
curve_preferences = ["X25519", "secp256r1", "secp384r1"]
tls:
  options:
    versions:
      - "1.2"
      - "1.3"
    cipher_suites:
      - "TLS13_AES_128_GCM_SHA256"
      - "TLS13_AES_256_GCM_SHA384"
      - "TLS13_CHACHA20_POLY1305_SHA256"
      - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
      - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
    curve_preferences:
      - "X25519"
      - "secp256r1"
      - "secp384r1"

[tls.client_auth]

Mutual TLS (mTLS). Omit to disable. Static.

TOML YAML
# Require client certificates signed by this CA
[tls.client_auth]
required = { ca_cert_path = "/config/certs/ca.crt" }
# Require client certificates signed by this CA
tls:
  client_auth:
    required:
      ca_cert_path: "/config/certs/ca.crt"

[tls.session_resumption]

KeyTypeDefaultDescription
enabledbooltrueEnable TLS session resumption (TLS 1.2 session IDs + TLS 1.3 session tickets).
max_sessionsinteger256TLS 1.2 server-side session cache size.
TOML YAML
[tls.session_resumption]
enabled = true
max_sessions = 256
tls:
  session_resumption:
    enabled: true
    max_sessions: 256

[fingerprint]

Feature flags for passive fingerprinting. Static — eBPF programs are loaded at startup.

KeyTypeDefaultDescription
tls_enabledbooltrueExtract TLS (JA4) fingerprints and inject x-tls-ja4* headers.
http_enabledbooltrueExtract HTTP/2 (Akamai) fingerprints and inject x-http2-akamai header.
tcp_enabledboolfalseExtract TCP SYN (p0f-style) fingerprints via eBPF/XDP and inject x-tcp-p0f header. Requires the ebpf-tcp build feature and Linux kernel ≥ 5.11.
max_captureinteger65536Maximum bytes captured per HTTP/2 connection for fingerprinting.
TOML YAML
[fingerprint]
tls_enabled = true
http_enabled = true
tcp_enabled = false
max_capture = 65536
fingerprint:
  tls_enabled: true
  http_enabled: true
  tcp_enabled: false
  max_capture: 65536

[logging]

Static — logger is initialized once at startup.

KeyTypeDefaultDescription
levelstring"info"Log level: "trace", "debug", "info", "warn", "error". Overridable with the RUST_LOG environment variable.
show_targetboolfalseInclude the Rust module path in log lines (useful for debugging).
TOML YAML
[logging]
level = "info"
show_target = false
logging:
  level: "info"
  show_target: false

[telemetry]

Metrics server and OpenTelemetry settings. Static — the metrics listener binds at startup.

KeyTypeDefaultDescription
metrics_portintegernullPort for the Prometheus metrics + health-check HTTP server. Omit to disable. Endpoints: /metrics, /health, /ready, /live.
otel_log_levelstring"warn"OpenTelemetry SDK internal log level. Does not affect application logs.
TOML YAML
[telemetry]
metrics_port = 9090
otel_log_level = "warn"
telemetry:
  metrics_port: 9090
  otel_log_level: "warn"

[timeout]

Connection timeout controls. Static — applied once at startup; the connection pool and acceptor are built with these values.

KeyTypeDefaultDescription
upstream_connect_msintegerabsent (no timeout)TCP connect timeout to backend in milliseconds. Absent or omitted = no timeout.
proxy_idle_msinteger60000Inbound idle timeout in milliseconds. Applied as HTTP/1.1 header_read_timeout and HTTP/2 keep-alive interval.
tls_handshake_secsinteger15Maximum seconds to complete the client TLS handshake. Slow/malicious clients that stall the handshake are disconnected.
connection_handling_secsinteger300Maximum total seconds for a full connection lifecycle (read request + proxy + write response). Guards against extremely slow clients.
shutdown_secsinteger30Graceful shutdown window. In-flight requests have this many seconds to complete before the process exits.
TOML YAML
[timeout]
upstream_connect_ms = 5000
proxy_idle_ms = 60000
tls_handshake_secs = 15
connection_handling_secs = 300
shutdown_secs = 30
timeout:
  upstream_connect_ms: 5000
  proxy_idle_ms: 60000
  tls_handshake_secs: 15
  connection_handling_secs: 300
  shutdown_secs: 30

[timeout.keep_alive]

HTTP/1.1 keep-alive and upstream TCP keepalive. Applies only to HTTP/1.1; HTTP/2 connections are always persistent.

KeyTypeDefaultDescription
enabledbooltrueEnable HTTP/1.1 persistent connections (Connection: keep-alive).
upstream_idle_timeoutinteger60TCP keepalive interval in seconds for proxy → backend connections. Sets how often keepalive packets are sent to detect dead backend connections. Aligned with rpxy's upstream_idle_timeout.
TOML YAML
[timeout.keep_alive]
enabled = true
upstream_idle_timeout = 60
timeout:
  keep_alive:
    enabled: true
    upstream_idle_timeout: 60

[backend_pool]

HTTP connection pool for proxy → backend connections. Dynamic (hot-reloadable). Changing this triggers pool recreation and draining of old connections.

KeyTypeDefaultDescription
enabledbooltrueEnable connection pooling. Set to false to open a new connection for every request (not recommended for production).
idle_timeoutinteger90Seconds before an idle pooled connection is closed and removed.
pool_max_idle_per_hostinteger0Maximum idle connections kept per backend host. 0 = unlimited.
TOML YAML
[backend_pool]
enabled = true
idle_timeout = 90
pool_max_idle_per_host = 0
backend_pool:
  enabled: true
  idle_timeout: 90
  pool_max_idle_per_host: 0

[security]

Top-level security keys

KeyTypeDefaultDescription
max_connectionsinteger512Maximum concurrent client connections. Static — enforced at the acceptor level.
TOML YAML
[security]
max_connections = 512
security:
  max_connections: 512

[security.ip_filter]

IP-based access control. Dynamic (hot-reloadable).

KeyTypeDefaultDescription
modestring"disabled"Filter mode: "disabled", "allowlist" (only listed IPs pass), or "denylist" (listed IPs are blocked).
allowlistarray of strings[]CIDR ranges allowed when mode = "allowlist". Supports IPv4 and IPv6. Empty allowlist blocks all traffic.
denylistarray of strings[]CIDR ranges blocked when mode = "denylist". Supports IPv4 and IPv6. Empty denylist allows all traffic.
TOML YAML
# Disabled (default)
[security.ip_filter]
mode = "disabled"

# Allowlist: only these IPs can connect
[security.ip_filter]
mode = "allowlist"
allowlist = ["10.0.0.0/8", "192.168.1.0/24", "::1/128"]

# Denylist: block these IPs
[security.ip_filter]
mode = "denylist"
denylist = ["192.168.1.100/32", "10.99.0.0/16"]
# Disabled (default)
security:
  ip_filter:
    mode: "disabled"

# Allowlist: only these IPs can connect
security:
  ip_filter:
    mode: "allowlist"
    allowlist:
      - "10.0.0.0/8"
      - "192.168.1.0/24"
      - "::1/128"

# Denylist: block these IPs
security:
  ip_filter:
    mode: "denylist"
    denylist:
      - "192.168.1.100/32"
      - "10.99.0.0/16"

[security.rate_limit]

Global rate limiting. Dynamic (hot-reloadable). Per-route overrides via [routes.rate_limit].

KeyTypeDefaultDescription
enabledboolfalseEnable global rate limiting.
requests_per_secondinteger1000Sustained request rate allowed.
burstinteger2000Maximum burst size above the sustained rate.
window_secondsinteger1Sliding window in seconds for the token bucket refill.
limit_bystring"ip"Key used to track limits: "ip", "header", "route", "combined" (IP + route).
limit_by_headerstringnullHeader name to use as the rate limit key when limit_by = "header".
trusted_proxiesstring array[]Trusted reverse-proxy CIDRs. When empty (default), the TCP peer IP is always used as the rate-limit key — this is the secure default and cannot be spoofed. When set, X-Forwarded-For is walked right-to-left and the first IP not in this list is used, recovering the real client IP behind a trusted load balancer. Accepts CIDR notation.
TOML YAML
[security.rate_limit]
enabled = true
requests_per_second = 1000
burst = 2000
window_seconds = 1
limit_by = "ip"
security:
  rate_limit:
    enabled: true
    requests_per_second: 1000
    burst: 2000
    window_seconds: 1
    limit_by: "ip"
TOML YAML
# Rate limit by API key header
[security.rate_limit]
enabled = true
requests_per_second = 200
burst = 400
limit_by = "header"
limit_by_header = "X-API-Key"
# Rate limit by API key header
security:
  rate_limit:
    enabled: true
    requests_per_second: 200
    burst: 400
    limit_by: "header"
    limit_by_header: "X-API-Key"
TOML YAML
# Rate limit by real client IP behind a trusted load balancer.
# Without trusted_proxies the TCP peer IP is used (secure default).
# With trusted_proxies, XFF is walked right-to-left to find the
# first non-trusted IP, which is treated as the client address.
[security.rate_limit]
enabled = true
requests_per_second = 500
burst = 1000
limit_by = "ip"
trusted_proxies = ["10.0.0.0/8", "172.16.0.0/12"]
# Rate limit by real client IP behind a trusted load balancer.
security:
  rate_limit:
    enabled: true
    requests_per_second: 500
    burst: 1000
    limit_by: "ip"
    trusted_proxies:
      - "10.0.0.0/8"
      - "172.16.0.0/12"

[security.headers]

Security headers added to every response. Dynamic (hot-reloadable).

KeyTypeDefaultDescription
customarray of {name, value}[]Arbitrary headers added to all responses.
TOML YAML
[security.headers]
custom = [
    { name = "X-Frame-Options", value = "DENY" },
    { name = "X-Content-Type-Options", value = "nosniff" },
    { name = "Referrer-Policy", value = "strict-origin-when-cross-origin" },
]
security:
  headers:
    custom:
      - name: "X-Frame-Options"
        value: "DENY"
      - name: "X-Content-Type-Options"
        value: "nosniff"
      - name: "Referrer-Policy"
        value: "strict-origin-when-cross-origin"

[security.headers.hsts]

HTTP Strict Transport Security. Only meaningful when TLS is enabled.

KeyTypeDefaultDescription
enabledboolfalseAdd Strict-Transport-Security header to responses.
max_ageinteger31536000max-age in seconds (default = 1 year).
include_subdomainsboolfalseAdd includeSubDomains directive.
preloadboolfalseAdd preload directive (for HSTS preload list submission).
TOML YAML
[security.headers.hsts]
enabled = true
max_age = 31536000
include_subdomains = true
preload = false
security:
  headers:
    hsts:
      enabled: true
      max_age: 31536000
      include_subdomains: true
      preload: false

[security.headers.csp]

Content Security Policy.

KeyTypeDefaultDescription
enabledboolfalseAdd Content-Security-Policy header to responses.
policystring"default-src 'self'"Full CSP policy string.
TOML YAML
[security.headers.csp]
enabled = true
policy = "default-src 'self'; script-src 'self' 'unsafe-inline'"
security:
  headers:
    csp:
      enabled: true
      policy: "default-src 'self'; script-src 'self' 'unsafe-inline'"

Complete minimal example

TOML YAML
preserve_host = false

[listen]
addrs = ["0.0.0.0:8080"]

[[backends]]
address = "localhost:3000"

[[routes]]
prefix = "/"
backend = "localhost:3000"
preserve_host: false

listen:
  addrs:
    - "0.0.0.0:8080"

backends:
  - address: "localhost:3000"

routes:
  - prefix: "/"
    backend: "localhost:3000"

Complete production example

See examples/config/compose.toml and examples/config/compose.yaml.