Configuration
June 25, 2026 · View on GitHub
Priority order
Settings are resolved highest to lowest:
- Command-line flags —
synapse --log-level debug - YAML config file —
synapse -c /etc/synapse/config.yaml - Environment variables —
export LOG_LEVEL=debug
CLI options
| Flag | Description | Default |
|---|---|---|
-c, --config <PATH> | Path to configuration file (YAML) | — |
--security-rules-config <PATH> | Security rules file (fallback without API key) | security_rules.yaml |
-i, --iface <NAME> | Network interface for XDP | eth0 |
--ifaces <LIST> | Additional interfaces, comma-separated (overrides --iface) | — |
--log-level <LEVEL> | Log level: error, warn, info, debug, trace | info |
--disable-xdp | Disable XDP packet filtering | false |
--redis-url <URL> | Redis connection URL | redis://127.0.0.1/0 |
--redis-prefix <PREFIX> | Redis namespace prefix | ax:synapse |
--captcha-site-key <KEY> | CAPTCHA site key | — |
--captcha-secret-key <KEY> | CAPTCHA secret key | — |
--captcha-jwt-secret <KEY> | JWT secret for CAPTCHA tokens | — |
--captcha-provider <P> | hcaptcha, recaptcha, or turnstile | — |
--captcha-token-ttl <S> | CAPTCHA token TTL (seconds) | 7200 |
--captcha-cache-ttl <S> | CAPTCHA cache TTL (seconds) | 300 |
--proxy-protocol-enabled | Enable PROXY protocol | false |
--proxy-protocol-timeout <MS> | PROXY protocol timeout (ms) | 1000 |
-d, --daemon | Run as daemon (Linux) | false |
--service <CMD> | Windows SCM: install or uninstall | — |
--terminal | Enable ratatui TUI | false |
--daemon-pid-file <PATH> | PID file path | /var/run/synapse.pid |
--daemon-working-dir <PATH> | Daemon working directory | / |
--daemon-user <USER> | Run daemon as user (Linux) | — |
--daemon-group <GROUP> | Run daemon as group (Linux) | — |
--clear-certificate <DOMAIN> | Clear certificate from filesystem and Redis | — |
Feature toggles
| Feature | YAML Path | Environment Variable | Default |
|---|---|---|---|
| BPF Statistics | logging.bpf_stats.enabled | BPF_STATS_ENABLED | true |
| TCP Fingerprinting | logging.tcp_fingerprint.enabled | TCP_FINGERPRINT_ENABLED | true |
| Content Scanning | proxy.content_scanning.enabled | CONTENT_SCANNING_ENABLED | false |
| CAPTCHA Protection | (enabled when keys are set) | CAPTCHA_SITE_KEY, CAPTCHA_SECRET_KEY | disabled |
| ACME (Auto TLS) | proxy.acme.enabled | ACME_ENABLED | false |
| Internal Services | proxy.internal_services.enabled | INTERNAL_SERVICES_ENABLED | true |
| Config-Push SSE | proxy.config_stream.enabled | — | false |
| PROXY Protocol | proxy.protocol.enabled | PROXY_PROTOCOL_ENABLED | false |
| File Logging | logging.file_logging_enabled | LOGGING_FILE_ENABLED | false |
| Syslog (Linux) | logging.syslog.enabled | LOGGING_SYSLOG_ENABLED | false |
| ETW (Windows) | logging.etw.enabled | LOGGING_ETW_ENABLED | false |
| Log Sending (API) | platform.log_sending_enabled | LOG_SENDING_ENABLED | true |
| XDP Firewall | firewall.mode | FIREWALL_MODE | auto |
Full environment variable reference: ENV_VARS.md
Per-feature configuration
Firewall & network
firewall:
mode: "auto" # auto | xdp | nftables | iptables | none
network:
iface: "auto" # "auto" | <name> | "" → auto
ifaces: ["eth0", "eth1"] # explicit list, overrides iface
ip_version: "both" # ipv4 | ipv6 | both
network.iface selection
| Value | Behaviour |
|---|---|
"auto" / "" | Attach only to physical UP uplink interface(s). Loopback and CNI/virtual/tunnel devices (veth*, lxc*, cilium*, gke*, docker*, br-*, vxlan*, tunnels, …) are excluded; bond* and VLAN sub-interfaces (eth0.100) are kept. Safe on Cilium/Calico/GKE-Dataplane-V2 nodes — it never attaches to (and so never clobbers) the CNI datapath. |
<name> | Attach to exactly that interface, even a virtual/CNI one (explicit opt-in, bypasses filtering). |
ifaces: [...] | Explicit list; overrides iface. Entries are attached as-is and bypass "auto" filtering — the way to deliberately enforce on a veth*/bridge. |
If an interface already has a foreign XDP program (e.g. another CNI), Synapse refuses to replace it and skips that interface (logged as a warning) rather than breaking the other datapath.
FIREWALL_MODE=auto
NETWORK_IFACE=eth0 NETWORK_IP_VERSION=both
mode | Behaviour |
|---|---|
auto | Select highest available: XDP → nftables → iptables → none |
xdp | Force XDP; startup fails if unavailable |
nftables | Force nftables; startup fails if unavailable |
iptables | Force iptables; startup fails if unavailable |
none | Userland enforcement only (proxy mode); agent mode has no enforcement |
→ Full backend comparison, requirements, diagnostics, and Windows path: FIREWALL.md
IDS / intrusion detection
The inline IDS (Thalamus engine) runs Suricata-format rules over captured
traffic and can feed verdicts to the firewall. It is configured under the
ids: block. The shipped config/config.yaml carries a
fully-commented reference of every option (including snaplen,
flow_timeout_secs, max_flows, and the bypass / consumer / post_tls
sub-fields).
ids:
enabled: true
capture_mode: xdp # xdp | afpacketrecv (alias: afpacket) for legacy kernels
rule_paths: # Suricata-format rule files (globs; hot-reloaded)
- "/etc/synapse/rules/*.rules"
address_vars: # Suricata $VARS referenced by rules (empty {} = rule defaults)
HOME_NET: "[10.0.0.0/8,172.16.0.0/12,192.168.0.0/16]"
EXTERNAL_NET: "!$HOME_NET"
port_vars:
HTTP_PORTS: "80"
enforce_block: false # true = push verdicts to the firewall; false = alert-only
# Managed-IDS rule delivery (pull rule bundles from ids-rules-api at runtime):
managed_rules_base_url: "https://api.gen0sec.com"
managed_rules_path: "/var/lib/synapse/managed.rules"
managed_rules_refresh_secs: 3600
# Flow bypass — stop exporting a flow once inspected enough (Linux/XDP):
bypass: { enabled: false, threshold_packets: 100, depth_bytes: 1048576 }
# XDP inspect-path parallelism / CPU pinning (Linux/XDP):
consumer: { inspect_threads: 8, inspect_channel_cap: 131072 }
# Post-TLS (decrypted-HTTP) IDS placement in proxy mode:
post_tls: { mode: inline } # inline | offload | off
| Key | Default | Purpose |
|---|---|---|
enabled | false | Turn the IDS engine on. |
capture_mode | xdp | xdp (eBPF) or afpacketrecv for legacy kernels (enforcement then routes via nftables/iptables). |
rule_paths | [] | Suricata-format rule files (globs allowed); loaded and hot-reloaded on change. |
address_vars / port_vars | {} | Suricata variables ($HOME_NET, $HTTP_PORTS, …) referenced by rules. On Kubernetes the operator can auto-populate HOME_NET/EXTERNAL_NET. |
enforce_block | false | true pushes IDS verdicts to the firewall (drop offending sources); false is alert-only. |
managed_rules_base_url / _path / _refresh_secs | "" / "" / 3600 | Pull per-class rule bundles from ids-rules-api at runtime, in addition to rule_paths. Empty base URL disables it; refresh_secs: 0 is push-only (SSE). |
bypass | off | Suricata-style flow bypass so one elephant flow can't flood the inspect channel (Linux/XDP). |
consumer | 8 threads | XDP inspect-path thread count, per-shard queue, and optional CPU pinning (Linux/XDP). |
post_tls.mode | inline | Where the decrypted-HTTP IDS runs in proxy mode: inline (every proxy process), offload (tap to a node-local agent over a UDS), or off. |
BPF statistics
logging:
bpf_stats:
enabled: true
log_interval_secs: 60
enable_dropped_ip_events: true
dropped_ip_events_interval_secs: 30
BPF_STATS_ENABLED=true BPF_STATS_LOG_INTERVAL=60
TCP fingerprinting
logging:
tcp_fingerprint:
enabled: true
log_interval_secs: 60
enable_fingerprint_events: true
fingerprint_events_interval_secs: 30
min_packet_count: 3
min_connection_duration_secs: 1
TCP_FINGERPRINT_ENABLED=true TCP_FINGERPRINT_LOG_INTERVAL=60
GeoIP databases
proxy:
geoip:
refresh_secs: 28800 # 8 hours
country: { url: "https://git.io/GeoLite2-Country.mmdb", path: "/var/lib/synapse" }
asn: { url: "https://git.io/GeoLite2-ASN.mmdb", path: "/var/lib/synapse" }
city: { url: "https://git.io/GeoLite2-City.mmdb", path: "/var/lib/synapse" }
GEOIP_COUNTRY_URL=... GEOIP_COUNTRY_PATH=/var/lib/synapse GEOIP_REFRESH_SECS=28800
Threat intelligence
platform:
threat:
url: "https://download.gen0sec.com/v1"
path: "/var/lib/synapse"
refresh_secs: 300
THREAT_MMDB_URL=https://download.gen0sec.com/v1 THREAT_MMDB_PATH=/var/lib/synapse
Platform (Gen0Sec API)
platform:
api_key: "your-api-key"
base_url: "https://api.gen0sec.com/v1"
log_sending_enabled: true
include_response_body: true
max_body_size: 1048576 # 1 MB
API_KEY=your-api-key LOG_SENDING_ENABLED=true
ACME / automatic TLS
proxy:
acme:
enabled: true
port: 9180
email: "admin@example.com"
storage_path: "/var/lib/synapse/acme"
storage_type: "redis" # or "file"
development: false # true = Let's Encrypt staging
ACME_ENABLED=true ACME_EMAIL=admin@example.com ACME_STORAGE_TYPE=redis
CAPTCHA protection
proxy:
captcha:
site_key: "your-site-key"
secret_key: "your-secret-key"
jwt_secret: "your-jwt-secret"
provider: "turnstile" # hcaptcha | recaptcha | turnstile
token_ttl: 7200
cache_ttl: 300
CAPTCHA_SITE_KEY=... CAPTCHA_SECRET_KEY=... CAPTCHA_PROVIDER=turnstile
Content scanning (ClamAV)
proxy:
content_scanning:
enabled: true
clamav_server: "localhost:3310"
max_file_size: 10485760 # 10 MB
scan_content_types:
- "text/html"
- "application/x-www-form-urlencoded"
- "multipart/form-data"
skip_extensions: [".jpg", ".png", ".gif"]
scan_expression: 'http.request.method eq "POST" or http.request.method eq "PUT"'
CONTENT_SCANNING_ENABLED=true CLAMAV_SERVER=localhost:3310
Internal services server
proxy:
internal_services:
enabled: true
port: 9180
bind_ip: "127.0.0.1"
Endpoints:
GET /health— health checkPOST /cgi-bin/captcha/verify— CAPTCHA verificationGET /.well-known/acme-challenge/*— ACME HTTP-01 challengesGET /cert/expiration— all certificate expiry statusGET /cert/expiration/:domain— specific certificate statusPOST /cert/renew/:domain— trigger manual renewal
HTTP SSE config push
Pushes a notification to connected clients over Server-Sent Events whenever
the daemon config is reloaded (e.g. on SIGHUP). Served on the internal
services server (same loopback bind), and disabled by default.
proxy:
config_stream:
enabled: false # opt-in
path: "/config/stream" # endpoint path on the internal services server
auth_token: null # optional; when set, require Authorization: Bearer <token>
max_connections: 32 # concurrent SSE clients (503 when exceeded)
Notification only. No configuration is ever streamed — only a generation counter and timestamp — so there is no secret-leak surface. Clients re-fetch config through their normal channel when notified.
SSE events emitted on GET <path>:
config_snapshot— sent once on connect:{"generation":N,"ts":"<rfc3339>","source":"reload"}config_changed— sent on each reload;generationincrementslagged— a slow client missed N notifications (it should force a full re-fetch); the stream is not terminated and resumes with live events- a
keep-alivecomment every 15s
Security notes:
- Requires
proxy.internal_services.enabled(the endpoint is hosted on that server) and a wired reload channel; if enabled without one, the route is not registered and a warning is logged. - Inherits the internal server's loopback bind. Do not expose the internal
services port; set
auth_tokenfor defense in depth (compared in constant time). - Responses:
401(bad/missing bearer whenauth_tokenis set),503(no reload channel, ormax_connectionsexceeded).
Logging
logging:
level: "info" # error | warn | info | debug | trace
file_logging_enabled: true
log_directory: "/var/log/synapse"
max_log_size: 104857600 # 100 MB
log_file_count: 10
syslog: # Linux only
enabled: false
facility: "daemon"
identifier: "synapse"
etw: # Windows only
enabled: false # emitted to ETW provider "Gen0Sec.Synapse"
Log files created: error.log, app.log, access.log — with automatic rotation and gzip compression.
PROXY protocol
See PROXY_PROTOCOL.md for full documentation.
proxy:
protocol:
enabled: true
timeout_ms: 1000
Multi-core runtime
See MULTICORE.md for full documentation.
TOKIO_WORKER_THREADS=8 # defaults to number of CPU cores
Upstreams
Synapse supports advanced upstream routing with hot-reloading. The shipped example file uses the v2 schema (recommended):
- config/upstreams.yaml — v2 reference example
proxy:
upstream:
conf: "/etc/synapse/upstreams.yaml"
# Optional: TTL (seconds) for the in-proxy DNS cache. Hostname
# backends are resolved once per TTL window; subsequent connections
# short-circuit pingora's blocking to_socket_addrs() call to a pure
# address parse. Unset or 0 = compile-time default (60).
dns_cache_ttl_secs: 60
Upstream DNS cache
Pingora's HttpPeer::new resolves upstream hostnames synchronously on every connection. Against a k8s CoreDNS that routinely costs 100–500 ms per request and becomes the dominant component of upstream_peer_select_ms in access logs. Synapse caches the resolved IP for dns_cache_ttl_secs seconds (default 60), bringing steady-state resolution down to microseconds. Raise the TTL on clusters where Service ClusterIPs are stable for hours/days; lower it (or 0 to disable, falling back to the default) if you actively rely on DNS-level failover from your upstream provider.
Schema versions
Synapse auto-detects the upstream file schema by the top-level version: field and dispatches to the right parser. Both formats produce the same in-memory shape, so consumers (ACME, load balancer, healthcheck, hot-reload) are version-agnostic.
| Schema | Detection | Status |
|---|---|---|
| v2 | version: 2 at top of file | Recommended for new deployments. Adds explicit tls.{terminate.{cert|acme}|passthrough} modes, layered headers/timeouts (global → host → route), single-upstream sugar, wildcard SNI passthrough. |
| v1 | No version: field, or version: 1 | Legacy. Still parsed for backward compatibility. Does not support TLS passthrough. |
Migration is opt-in — drop version: 2 at the top of your file and rewrite the body using the v2 grammar (see config/upstreams.yaml for the full template). Existing v1 files keep working unchanged.
v2 capabilities
- Explicit TLS mode —
tls.terminate.cert(static cert path),tls.terminate.acme(ACME-issued), ortls.passthrough: true(raw TCP routing by SNI, no L7 inspection). Mutually exclusive — invalid combinations rejected at parse time. - TLS passthrough — route Postgres/Redis/MQTT-over-TLS by SNI without terminating. Wildcard SNI supported (
*.internal.example.com). - Layered headers / timeouts —
headers.{request,response}andtimeouts.{connect,read,write,idle}at global, host, and route level. Per-route extends global; explicit per-route value overrides. - Single-upstream sugar —
upstream: host:portshort form for the common case (skip theupstreams: [{addr, weight}]list). - Health checks — TCP or HTTP, with consecutive-failure / consecutive-success thresholds.
- Multiple LB methods —
round_robin(default),random,least_request,weighted_round_robin,consistent.
Common features (both schemas)
Multiple service discovery providers, per-path rate limits/headers/timeouts, weighted load balancing, sticky sessions, zero-downtime reloads.
Config reload
Synapse supports runtime configuration reload without restarting:
# Linux — SIGHUP
kill -HUP $(cat /var/run/synapse.pid)
# Windows — Service control
sc control synapse paramchange
Hot-reloadable settings
| Setting | Config Path |
|---|---|
| Log level | logging.level |
| Log sending | platform.log_sending_enabled |
| API key | platform.api_key |
| Base URL | platform.base_url |
| Include response body | platform.include_response_body |
| Max body size | platform.max_body_size |
| Upstreams | proxy.upstream.conf |
| Security rules | Local file or API |
| GeoIP databases | proxy.geoip.* |
Settings that require restart
| Setting | Config Path | Reason |
|---|---|---|
| Listen addresses | proxy.address_http, proxy.address_tls | Port bindings set at startup |
| Network interface | network.iface, network.ifaces | XDP attached at startup |
| Firewall mode | firewall.mode | BPF programs loaded at startup |
| Runtime threads | multi_thread, worker_threads | Tokio runtime created at startup |
| Daemon settings | daemon.* | Process daemonisation is one-time |
| Redis connection | proxy.redis.* | Connection pool created at startup |