Configuration Reference
These are the global settings in exfilguard.toml. ExfilGuard reads them at
startup.
Core Settings
These settings are required.
| Field | Type | Required | Description |
|---|---|---|---|
listen |
String | Yes | Listen address and port (e.g., "127.0.0.1:3128") |
proxy_protocol |
String | "off" |
PROXY protocol mode: "off", "optional", or "required" |
proxy_protocol_allowed_cidrs |
Array | None | CIDR allowlist for peers allowed to send PROXY headers (required when proxy_protocol is "optional" or "required") |
ca_dir |
Path | Yes | Directory containing CA certificate and private key for TLS interception |
clients |
Path | Yes | Path to clients configuration file |
policies |
Path | Yes | Path to policies configuration file |
clients_dir |
Path | No | Directory containing additional client config files (*.toml) |
policies_dir |
Path | No | Directory containing additional policy config files (*.toml) |
Note
Relative paths are resolved from the directory containing the main config file.
Note
Use ExfilGuard as an explicit proxy. Clients should know they are talking to one.
Note
When proxy_protocol is "optional" or "required", ExfilGuard
auto-detects PROXY protocol v1 or v2 headers for peers in
proxy_protocol_allowed_cidrs.
In "optional" mode, peers outside that allowlist are treated as normal
client connections.
In "required" mode, peers outside that allowlist are rejected before
HTTP parsing, and allowlisted peers must send a valid PROXY header.
Note
Set proxy_protocol_allowed_cidrs when PROXY protocol is enabled.
Note
ExfilGuard reads exfilguard.toml only at startup. SIGHUP reloads only
the client and policy data read from the configured clients,
clients_dir, policies, and policies_dir paths. If you change fields
in exfilguard.toml itself, including listen, metrics, cache, TLS,
logging, and timeout settings, restart the server.
TLS / Certificate Settings
These settings control TLS interception and leaf certificate generation.
| Field | Type | Default | Description |
|---|---|---|---|
cert_cache_dir |
Path | None | Directory to cache dynamically generated TLS certificates |
leaf_ttl |
u64 | 86400 | TLS certificate leaf TTL in seconds (must be > 0) |
CA Directory Structure
ExfilGuard uses a root CA plus an intermediate CA. ca_dir must contain:
ca_dir/
├── root.crt # Root CA certificate
├── root.key # Root CA private key (optional when using external CA)
├── intermediate.crt # Intermediate CA certificate (signed by root)
└── intermediate.key # Intermediate CA private key
- The intermediate CA signs leaf certificates.
- ExfilGuard sends the chain
Leaf -> Intermediate -> Rootto clients. - Clients only need to trust the root CA.
- If you use an externally signed intermediate,
root.keymay be omitted.
If ca_dir is empty, ExfilGuard generates all four files automatically on first startup.
Note
If you change files under ca_dir, restart the server. If you replace the
signing CA and use cert_cache_dir, clear the cached leaf certificates
before restart if you need clients to see the new issuer right away.
Using Your Corporate CA
Use this flow if you want clients to trust ExfilGuard through your existing PKI:
-
Let ExfilGuard generate its keys:
Start with an empty# Creates root.crt, root.key, intermediate.crt, intermediate.key exfilguard --config exfilguard.tomlca_dir, then stop the process after it creates the files. -
Create a CSR from the generated intermediate key:
-
Get your corporate CA to sign the CSR:
-
Replace the certificates:
-
Restart ExfilGuard. Clients that trust your corporate CA will then trust intercepted connections.
Note
The private key (intermediate.key) stays the same. Only the certificate
changes.
Logging Settings
| Field | Type | Default | Description |
|---|---|---|---|
log |
String | "json" |
Log format: "json" or "text" |
log_queries |
Boolean | false | Whether to log each request query |
Timeout Settings
All timeout values are in seconds. Use 0 to disable request_total_timeout and
connect_tunnel_max_lifetime.
| Field | Type | Default | Description |
|---|---|---|---|
dns_resolve_timeout |
u64 | 2 | Maximum time to resolve DNS for upstream hosts |
upstream_connect_timeout |
u64 | 5 | Maximum time to establish upstream TCP connections |
tls_handshake_timeout |
u64 | 10 | Maximum time for TLS handshakes (client or upstream) |
request_header_timeout |
u64 | 10 | Maximum time to read an HTTP request line + headers |
request_body_idle_timeout |
u64 | 30 | Maximum idle time between request body reads/writes |
response_header_timeout |
u64 | 30 | Maximum time to receive upstream response headers |
response_body_idle_timeout |
u64 | 60 | Maximum idle time between response body reads/writes |
request_total_timeout |
u64 | 0 | Maximum total time from request start until the response has been fully forwarded (0 disables) |
client_keepalive_idle_timeout |
u64 | 30 | Idle time before closing an idle client keep-alive connection |
connect_tunnel_idle_timeout |
u64 | 60 | Maximum idle time for CONNECT tunnels |
connect_tunnel_max_lifetime |
u64 | 0 | Maximum lifetime for CONNECT tunnels (0 disables) |
Request Size Limits
All size values are in bytes. Header limits must be greater than 0.
Set max_request_body_size = 0 to disable the global request-body cap.
| Field | Type | Default | Description |
|---|---|---|---|
max_request_header_size |
usize | 32768 (32 KiB) | Maximum HTTP request header size, including bumped HTTP/2 header lists |
max_response_header_size |
usize | 32768 (32 KiB) | Maximum HTTP response header size, including upstream HTTP/2 header lists |
max_request_body_size |
usize | 0 (unlimited) | Maximum HTTP request body size during forwarding (0 disables the limit) |
Connection Pool
| Field | Type | Default | Description |
|---|---|---|---|
upstream_pool_capacity |
usize | 32 | Maximum number of upstream connections to pool (must be >= 1) |
HTTP/2
| Field | Type | Default | Description |
|---|---|---|---|
http2_max_concurrent_streams |
u32 | 100 | Maximum concurrent bumped downstream HTTP/2 streams per connection (must be >= 1) |
Metrics
| Field | Type | Default | Description |
|---|---|---|---|
metrics_listen |
String | None | Optional listen address (e.g., "127.0.0.1:9090") to serve Prometheus metrics at /metrics |
metrics_tls_cert |
Path | None | PEM certificate chain to enable HTTPS for /metrics |
metrics_tls_key |
Path | None | PEM private key matching metrics_tls_cert |
ExfilGuard exports counters and histograms for per-client and per-policy decisions, latency, cache activity, and upstream reuse, plus gauges for current downstream connections, in-flight requests, CONNECT tunnels, bumped TLS sessions, active HTTP/2 streams, upstream connections, cache usage, and the last successful policy reload time.
Cache Settings
Response caching is opt-in per rule. The settings here configure the shared cache storage.
| Field | Type | Default | Description |
|---|---|---|---|
cache_dir |
Path | None | Directory for response cache storage |
cache_max_entry_size |
u64 | 10485760 (10 MiB) | Maximum size of individual cache entries |
cache_max_entries |
usize | 10000 | Maximum number of cached responses (LRU) |
cache_total_capacity |
u64 | 1073741824 (1 GiB) | Total cache capacity |
cache_sweeper_interval |
u64 | 300 | Interval in seconds between cache sweeper runs |
cache_sweeper_batch_size |
usize | 1000 | Maximum metadata entries inspected per sweep |
Cache Behavior
The cache follows standard HTTP cache headers from upstream servers.
Scope
The cache is shared across all clients. Responses are keyed by method and
absolute URI. Vary headers decide which request headers are part of the cache
key. Enable caching only if cross-client sharing is acceptable in your
environment.
Supported Headers
- Cache-Control:
max-age,s-maxage,public,private,no-cache,no-store - Expires: HTTP date for cache expiration
- Vary: Cache keys include request headers specified by Vary
TTL Priority
Cache lifetime is chosen in this order:
s-maxage(shared cache max-age) - highest prioritymax-ageExpiresheaderforce_cache_durationfrom policy rule (fallback only)
What Gets Cached
- Methods: Only
GETandHEADrequests - Status codes: 200, 203, 204, 205, 206, 301, 302
- Bypass: Requests with
AuthorizationorCookieheaders are never served from cache and are not stored - Not cached: Responses with
no-store,no-cache, orprivatedirectives, or anySet-Cookieheader
Request Cache Directives
Request-side cache controls can force a bypass. If a request includes
Cache-Control: no-cache, Cache-Control: no-store, Cache-Control: max-age=0,
or Pragma: no-cache, the cache will not be used and the response will not be
stored. Otherwise, caching follows the upstream response headers plus
force_cache_duration from policy rules.
Eviction
The cache uses LRU eviction when capacity is reached. Expired entries are removed on lookup.
Layout and Sweeping
Cache entries live under a versioned subdirectory (v1 under the cache root).
When the layout changes, old version directories are deleted asynchronously. A
background sweeper runs every cache_sweeper_interval seconds and inspects up
to cache_sweeper_batch_size entries, removing expired entries and pruning
empty shard directories.
Note
The cache does not support conditional revalidation (ETag/If-None-Match, Last-Modified/If-Modified-Since). Stale entries are discarded and fetched fresh from upstream.
Environment Variables
You can override any setting with environment variables. Use the
EXFILGUARD__ prefix and double underscores for nesting.
# Override listen address
EXFILGUARD__LISTEN="0.0.0.0:3128"
# Override log format
EXFILGUARD__LOG="text"
# Override timeouts
EXFILGUARD__CLIENT_KEEPALIVE_IDLE_TIMEOUT=60
EXFILGUARD__UPSTREAM_CONNECT_TIMEOUT=120
Complete Example
# Core settings
listen = "127.0.0.1:3128"
ca_dir = "./ca"
cert_cache_dir = "./cert_cache"
clients = "clients.toml"
policies = "policies.toml"
clients_dir = "clients.d"
policies_dir = "policies.d"
# Logging
log = "text"
log_queries = false
# TLS
leaf_ttl = 86400
# Timeouts (seconds)
dns_resolve_timeout = 2
upstream_connect_timeout = 5
tls_handshake_timeout = 10
request_header_timeout = 10
request_body_idle_timeout = 30
response_header_timeout = 30
response_body_idle_timeout = 60
request_total_timeout = 0
client_keepalive_idle_timeout = 30
connect_tunnel_idle_timeout = 60
connect_tunnel_max_lifetime = 0
# Connection pool
upstream_pool_capacity = 32
# HTTP/2
http2_max_concurrent_streams = 100
# Size limits (bytes)
max_request_header_size = 32768
max_response_header_size = 32768
max_request_body_size = 0
# Cache (optional)
cache_dir = "./cache"
cache_max_entry_size = 10485760
cache_max_entries = 10000
cache_total_capacity = 1073741824
cache_sweeper_interval = 300
cache_sweeper_batch_size = 1000