Skip to main content

Configuration Reference

Synapse reads configuration from three places. They are applied in this order, and each one overwrites what came before — so the last one wins:

  1. YAML configuration file — via synapse -c /etc/synapse/config.yaml
  2. Command-line flags — e.g. synapse --mode proxy
  3. Environment variables — e.g. export MODE=agent
Environment variables beat command-line flags

This is the opposite of what most tools do, and of what earlier revisions of this page claimed. MODE=agent synapse --mode proxy runs in agent mode: the environment is applied after the flags and assigns unconditionally.

Two settings do not follow the rule:

  • Log level — --log-level wins over LOGGING_LEVEL, which wins over logging.level, which wins over RUST_LOG.
  • daemon.event_socket and daemon.control_socket — the YAML value wins over --event-socket and --control-socket. There is no environment variable for either.
Environment variable prefix

The AX_ (and AX_ARXIGNIS_) environment-variable prefix is deprecated. Use the unprefixed variable names (e.g. API_KEY, not AX_API_KEY). The old prefixes still work but log a deprecation warning and will be removed in a future release.

This page is the schema: what each key means and what it defaults to. The interfaces around it — every command-line flag, every environment variable, and what each package actually contains — are on Configuration Interfaces.

Operating Modes​

mode: "agent" # or "proxy"
  • agent (default) - Access rules, fingerprinting, IDS, and monitoring only. The reverse proxy (pingora) is disabled.
  • proxy - Full reverse proxy functionality plus everything in agent mode.

Terminal Mode (TUI)​

Synapse ships a real-time ratatui terminal UI that displays live JA4+ fingerprints, traffic, and threat data. Works on Linux and Windows.

# Embedded: TUI runs in the same process as the agent/proxy
synapse --mode agent --terminal

# Standalone: attach to a running daemon over its unix socket (Linux only)
synapse --terminal

YAML Configuration​

Basic Example​

mode: "agent"

network:
iface: "auto" # auto-attach physical uplinks only
ip_version: "auto" # processes both IPv4 and IPv6

firewall:
mode: "auto" # auto, xdp, nftables, iptables, none

platform:
api_key: "your-api-key"
base_url: "https://api.gen0sec.com/v1"
telemetry_sending_enabled: true
# Threat intelligence and GeoIP are OFF in this config. They need their own
# blocks with a `url` or `path` — see "Platform" below for the working shape.

logging:
level: "info"

Feature Toggles​

Two different defaults matter here. The code default is what applies when you omit the key — which is the common case in containers, Helm values and hand-written configs. The shipped value is what config/config.yaml in the release sets, and it turns several things on that are off by default. Where the two differ, the file wins for anyone who installed from a package.

FeatureYAML pathEnvironment variableDefault when omittedAs shipped
Packet Capturecapture.enabledCAPTURE_ENABLEDtruetrue
Capture backendcapture.modeCAPTURE_MODEautoauto
Inline IDS (Thalamus)ids.enabledIDS_ENABLEDfalsetrue
IDS blockingids.enforce_blockIDS_ENFORCE_BLOCKfalsetrue
ML Fingerprint Classifierml.fingerprint_classifier.enabledCLASSIFIER_ENABLEDfalsefalse
Threat Intelligenceplatform.threat.enabledTHREAT_MMDB_ENABLEDtrue †true
GeoIPplatform.geoip.enabledGEOIP_ENABLEDtrue †true
Telemetry Sending (API)platform.telemetry_sending_enabledTELEMETRY_SENDING_ENABLEDtrue †true
Unified Fingerprint Loglogging.fingerprint_log.enabledLOGGING_EVENTBRIDGE_LOG_ENABLEDtruetrue
File Logginglogging.file_logging_enabledLOGGING_FILE_ENABLEDfalsetrue
Syslog (Linux)logging.syslog.enabledLOGGING_SYSLOG_ENABLEDfalsefalse
ETW (Windows)logging.etw.enabledLOGGING_ETW_ENABLEDfalsefalse
Windows Event Loglogging.windows_event_log.enabledLOGGING_WINDOWS_EVENT_LOG_ENABLEDfalsefalse
Content Scanningproxy.content_scanning.enabledCONTENT_SCANNING_ENABLEDfalsefalse
CAPTCHA Protection(enabled when keys are set)CAPTCHA_SITE_KEY, CAPTCHA_SECRET_KEYdisableddisabled
ACME (Auto TLS) — deprecatedproxy.acme.enabledACME_ENABLEDfalsetrue
Internal Servicesproxy.internal_services.enabledINTERNAL_SERVICES_ENABLEDtruetrue
PROXY Protocolproxy.protocol.enabledPROXY_PROTOCOL_ENABLEDfalsetrue
HTTP/2 cleartext (h2c)proxy.h2cPROXY_H2Cfalsefalse
† An absent block is not the same as an absent key

Omitting one key gives you that key's default from the table. Omitting the whole block can give you something different, because the parent falls back to its type's own Default — and the per-key defaults inside that block never run.

The three rows marked † are true only when their own block is present:

  • platform.threat.enabled and platform.geoip.enabled need a threat: or geoip: block to be on. A platform: block that sets only api_key — which is what most hand-written configs and the Basic Example look like — leaves threat intelligence and GeoIP off.

    enabled: true on its own is still not enough to load anything. At startup each subsystem is initialised only if the flag is set and it has somewhere to fetch from: a non-empty threat.url or a threat.path, and for GeoIP a url or path under country, asn or city. Neither is derived from platform.base_url, so a block holding nothing but enabled: true initialises nothing and logs nothing you would recognise as a failure. The Platform section below has the shape that works.

  • platform.telemetry_sending_enabled is true whenever a platform: block exists at all, and false if platform: is absent entirely.

proxy.captcha fails the other way round: with no proxy: block you get provider hcaptcha and TTLs of 7200/300, but a proxy: block that merely leaves out captcha: gives you an empty provider and TTLs of 0.

Set the keys you care about explicitly rather than relying on a parent block's fallback.

telemetry_sending_enabled (renamed)

The platform log/telemetry toggle is now platform.telemetry_sending_enabled (formerly log_sending_enabled), and its environment override is TELEMETRY_SENDING_ENABLED. The old LOG_SENDING_ENABLED is still read as a fallback, and the YAML alias log_sending_enabled still parses.

Core Sections​

Network​

network:
# "auto" attaches XDP ONLY to physical UP uplink interfaces. Loopback and
# CNI/virtual/tunnel devices (veth*, lxc*, cilium*, docker*, bridges,
# vxlan/tunnels, etc.) are excluded, so it never clobbers a CNI datapath
# (e.g. Cilium) on Kubernetes nodes. bond*/VLAN uplinks (eth0.100) are kept.
iface: "auto"

# Explicit list overrides `iface`; entries are attached as-is (bypasses the
# "auto" filtering above — use this to opt in to a virtual interface).
ifaces: []

# "auto" (default) processes both IPv4 and IPv6.
ip_version: "auto"

To enable features per interface rather than everywhere — the firewall on one link, the IDS on another, nothing at all on the management port — use network.interfaces, which replaces iface and ifaces. See Per-Interface Features.

Packet Capture​

Controls the backend used for JA4+ fingerprint packet capture.

capture:
enabled: true

# Backend:
# auto — Auto-detect: XDP > af_packet_recv > pfring (default)
# xdp — eBPF/XDP BPF map polling (highest performance)
# af_packet — AF_PACKET TPACKET_V3 mmap ring buffer (zero-copy)
# af_packet_recv — AF_PACKET plain recvfrom() (works on all kernels,
# including 4.19 where TPACKET_V3 poll() is broken)
# pfring — PF_RING zero-copy (requires pf_ring.ko + libpfring,
# build with --features pfring-capture, which no shipped
# package enables)
# ndis — NDIS lightweight filter (Windows only)
# af_xdp — AF_XDP socket (Windows only)
mode: auto

# interface: auto # "auto" = all interfaces; or a specific iface
snaplen: 65535 # max bytes per packet (af_packet/af_packet_recv/pfring)
filter: "tcp" # tcpdump-syntax BPF filter (af_packet, pfring)

af_packet: # only used when mode: af_packet
block_size: 4194304 # ring block size (power of 2, default 4MB)
block_count: 4 # number of blocks; total ring = block_size * block_count
block_timeout_ms: 100
promiscuous: true

pfring: # only used when mode: pfring
zero_copy: true
# cluster_id: 1 # multi-queue NIC load balancing (optional)

Environment overrides: CAPTURE_MODE, CAPTURE_SNAPLEN, CAPTURE_FILTER, PFRING_ZERO_COPY, PFRING_CLUSTER_ID.

Collector hygiene. capture.dedup_ttl_secs (default 600) suppresses re-processing a source already seen, so an established ban is not re-emitted on every poll. capture.collector_stale_secs (default 900) evicts idle entries from the fingerprint map. Keep the stale age comfortably above the dedup window, or live flows get dropped from the map while still inside their suppression period.

Firewall​

firewall:
# auto: XDP > nftables > iptables > none
# xdp / nftables / iptables: force a backend
# none: disable kernel firewall, userland enforcement only
mode: "auto"

Blocklist capacity. firewall.max_ip_filter_entries_v4 and _v6 size the kernel maps, 50,000 each by default.

Raising it needs the pins gone

The maps are pinned and reused across restarts, and a pinned map keeps the size it was created with. A raised value takes effect only once the pins are removed — synapse --force-stop, or a reboot. A plain restart silently keeps the old size.

Inline IDS (Thalamus)​

Synapse embeds the Thalamus IDS engine for inline, signature-based detection on captured traffic.

ids:
enabled: true # shipped value; the code default is false
capture_mode: xdp # xdp | afpacketrecv (alias: afpacket)
rule_paths:
- "/etc/synapse/rules/*.rules"
address_vars: {} # Suricata-style address variable definitions
port_vars: {} # Suricata-style port variable definitions
snaplen: 512
poll_timeout_ms: 100
flow_timeout_secs: 120
max_flows: 200000
cleanup_interval_secs: 30
stats_log_interval_secs: 5
enforce_block: true # ban the offending SOURCE via the firewall
# (false = alert only). The per-flow kernel drop is
# a separate key, enforce_edge_drop.
# Shipped value; the code default is false, and the
# shipped comment itself recommends false until the
# rules are tuned.
Rule loading

Thalamus loads one rule per line — there is no \ line-continuation. A rule split across multiple lines silently loads as zero rules. Validate offline before deploying.

ML Classifier​

Optional ONNX-based JA4+ fingerprint classification (ships with the -ml package variant).

ml:
fingerprint_classifier:
enabled: false
model_path: "" # path to the ONNX model file
cache_size: 10000 # max cached predictions (0 = no cache)
threshold: 0.5 # malicious probability threshold (0.0–1.0).
# Shipped value; the code default is 0.52.

# Platform-managed models. `enabled: true` is enough — the model is
# downloaded and the remaining paths default.
traffic_classifier:
enabled: false
flow_classifier:
enabled: false
penalty:
enabled: false
jepa_embedder:
enabled: false
The top-level keys are deprecated

classifier, traffic_classifier, flow_classifier and penalty were once top-level. They are still accepted, but nest them under ml: instead — and note that the fingerprint model is now ml.fingerprint_classifier, not classifier.

Platform​

platform:
api_key: ""
base_url: "https://api.gen0sec.com/v1"

# Send access logs / telemetry to the platform (formerly log_sending_enabled)
telemetry_sending_enabled: true

# WARNING: may capture PII, tokens, or API responses in logs
include_response_body: false
max_body_size: 1048576 # bytes; do not raise above the Basic plan maximum

# Threat intelligence MMDB
threat:
enabled: true
url: "https://download.gen0sec.com/v1"
path: "/var/lib/synapse"
headers: null
refresh_secs: 300 # 5 minutes

# GeoIP MMDB — ships with the IPinfo Lite bundle (single combined DB with
# both country + ASN). Synapse auto-detects the IPinfo schema, so the DB
# configured under `country` serves both country and ASN lookups; `asn`
# and `city` are unused and their refresh workers don't run.
geoip:
enabled: true
country:
url: "https://github.com/gen0sec/geoip-databases/raw/download/ipinfo_lite.mmdb"
path: "/var/lib/synapse/ipinfo_lite.mmdb"
headers: null
asn: { url: "", path: "", headers: null } # unused with IPinfo Lite
city: { url: "", path: "", headers: null } # unused with IPinfo Lite
refresh_secs: 28800 # 8 hours
GeoIP changed to IPinfo Lite

Synapse now defaults to a single combined IPinfo Lite database (ipinfo_lite.mmdb) instead of the split MaxMind GeoLite2 country/ASN/city files. To switch back to GeoLite2, point country/asn at the respective GeoLite2-*.mmdb URLs.

Platform integrations​

Four platform.* subsections govern what the agent exchanges with the platform beyond threat intelligence. ban_ledger, fleet_bans and service_graph are off at every level — omitting the key and writing the block but omitting enabled both leave them off; the shipped config.yaml turns the first two on. platform.identity is the exception: it is the same type as platform.threat, so writing an identity: block turns it on even if you never say enabled: true, and the † note above applies to it.

platform:
# Upload this agent's bans so the fleet can corroborate them.
# Shipped as true; the code default is false.
ban_ledger:
enabled: true
interval_secs: 60

# Download the corroborated result. Shipped as true; the code default is
# false. The refresh is a cheap version check and the artifact is only
# fetched when the version changes.
# `enforce: true` blocks what it contains instead of only reporting it.
fleet_bans:
enabled: true
refresh_secs: 60
advisory: true
advisory_dedup_secs: 300
enforce: false

# East-west workload identity. Off by default: it needs a cluster-side
# producer publishing the identity MMDB before there is anything to fetch.
identity:
enabled: false
url: ""
path: "/var/lib/synapse"

# Report the observed service graph. Off by default.
service_graph:
enabled: false
interval_secs: 300

ban_ledger and fleet_bans are the two halves of one loop (see Fleet Bans) — you upload what this agent blocked, and pull back what the fleet corroborated. As shipped they share a 60-second cadence (each defaults to 300 when left out), so neither leg is the slow one. Turning off the upload while leaving the download on is valid; you consume the fleet's conclusions without contributing.

fleet_bans.enforce is off by default. Turned on (it needs fleet_bans.enabled), the agent installs each fleet ban in the kernel IP filter (Linux, unless firewall.mode: none) and its proxy refuses fleet-banned clients; see Enforcing the set for what is and is not enforced. Turn it on per agent once advisory mode has shown the set is safe for its traffic. It is read at startup, so turning it off takes a restart, which lifts what the agent installed.

identity and service_graph both ship disabled because each needs something on the other side. Identity needs a producer publishing the MMDB; enabling it without one just produces periodic failures. See Service Graph.

Logging​

logging:
level: "info" # error, warn, info, debug, trace
file_logging_enabled: true # shipped value; the code default is false
log_directory: "/var/log/synapse" # Windows: C:\ProgramData\Gen0Sec\Synapse\logs
max_log_size: 104857600 # 100MB
log_file_count: 10 # rotated files are gzip-compressed

# Syslog (Linux/Unix only; silently ignored on Windows).
# Requires file_logging_enabled: true — the syslog appender is only built as part
# of file logging, so syslog alone emits nothing.
syslog:
enabled: false
facility: "daemon"
identifier: "synapse"
levels: # per-log-type priority
error: "err"
app: "info"
access: "info" # proxy mode only

# Event Tracing for Windows — Windows equivalent of syslog.
# Events emit to ETW provider "Gen0Sec.Synapse".
etw:
enabled: false

# Classic Windows Event Log (Event Viewer → Windows Logs → Application)
windows_event_log:
enabled: false
source: "Synapse"

# Unified per-connection fingerprint/BPF event log (agent mode).
fingerprint_log:
enabled: true
# file: /var/log/synapse/eventbridge.log # default: {log_directory}/eventbridge.log
Per-type fingerprint logging has been removed

The six per-type sections — bpf_stats, tcp_fingerprint, ssh_fingerprint, latency_fingerprint, tls_fingerprint and http_fingerprint — are no longer in the configuration schema at all. The shipped config.yaml still carries them, but the parser does not read them: they appear in the Unused config options warning at startup and change nothing.

fingerprint_log (a.k.a. eventbridge_log) replaces all six, emitting every fingerprint and BPF event through one unified JSON log.

When file_logging_enabled is true, Synapse writes (and rotates/compresses) error.log, app.log, and — in proxy mode — access.log (JSON).

Daemon / Service​

Linux runs as a Unix daemon; Windows registers with the Service Control Manager (synapse --service install / sc start synapse; sc control synapse paramchange triggers a config reload).

daemon:
enabled: true # shipped value; the code default is false
pid_file: "/var/run/synapse.pid" # Linux only
working_directory: "/var/lib/synapse" # shipped value; the code default is "/" on
# Linux, C:\ProgramData\Gen0Sec\Synapse on Windows
user: root # Linux only
group: root # Linux only
chown_pid_file: true # Linux only

# Sockets used by standalone terminal clients
# event_socket: "/var/run/synapse-events.sock" # Windows: TCP, e.g. 127.0.0.1:19199
# control_socket: "/var/run/synapse-control.sock"

Proxy (proxy mode)​

proxy.redis.prefix in YAML is overwritten

The key prefix is reset to g0s:synapse on every start unless you pass --redis-prefix, so a value set only in config.yaml does not survive. Pass the flag, or set REDIS_PREFIX, which is applied after it and overrides both.

ACME is deprecated

The embedded ACME client is no longer a supported way to obtain certificates, and it is not documented as a feature anywhere else. It still ships, and the config.yaml we ship still sets enabled: true even though the code default is false, so the keys are listed here for anyone who has it running — but do not build on it.

Provide your own certificate and key, or pass the connection through to a backend that holds them. On Kubernetes, use cert-manager — see Kubernetes Ingress.

proxy:
address_http: "0.0.0.0:80"
address_tls: "0.0.0.0:443"
certificates: "/etc/synapse/certs" # cert files: {NAME}.crt, {NAME}.key
tls_grade: "medium" # high, medium, unsafe
default_certificate: "default" # fallback cert (file stem)

# Enable HTTP/2 cleartext on the plaintext listener (gRPC over plaintext,
# --http2-prior-knowledge clients). Prior knowledge only: the first 24 bytes are
# peeked and anything that is not the h2 preface is served as HTTP/1.1, so
# turning this on does not disturb HTTP/1 clients. `Upgrade: h2c` is a different
# mechanism and is not implemented. Forced off on the TLS listener, where ALPN
# decides.
h2c: false

# Unsupported. CONNECT always answers 405, whatever this is set to — setting it
# to true logs an error and forces it back to false at startup.
allow_connect_method_proxying: false

# Max requests per connection before closing (frees per-connection memory).
# null = no limit (default).
keepalive_request_limit: null

redis:
url: "redis://127.0.0.1:6379/0"
prefix: "g0s:synapse"
# ssl:
# ca_cert_path: "/path/to/ca.crt"
# client_cert_path: "/path/to/client.crt" # optional, mutual TLS
# client_key_path: "/path/to/client.key" # optional, mutual TLS
# insecure: false

upstream:
conf: "/etc/synapse/upstreams.yaml"
healthcheck:
method: "HEAD" # HEAD, GET, POST
interval: 2 # seconds

# Shipped value; the code default is false. Keep it on ONLY when something in
# front of Synapse actually sends a PROXY header — otherwise the first bytes of
# every connection fail to parse.
protocol:
enabled: true
timeout_ms: 1000

captcha:
site_key: null
secret_key: null
jwt_secret: null # openssl rand -base64 48
provider: "hcaptcha" # hcaptcha, recaptcha, turnstile, prosopo
token_ttl: 7200
cache_ttl: 300

acme:
enabled: true # shipped value; the code default is false
port: 9180 # binds to 127.0.0.1 only
email: null
storage_type: null # "file" or "redis" (defaults by whether redis is set)
storage_path: "/var/lib/synapse/acme"
development: false # true = Let's Encrypt staging
redis_url: null # overrides global redis.url for ACME storage

# Enabling the scanner scans nothing on its own — a WAF rule with action
# `content_scanning` is what selects the traffic.
content_scanning:
enabled: false
clamav_server: "localhost:3310"
max_file_size: 10485760 # 10MB
# A selected request whose body cannot be inspected: block (413) or pass.
unscannable: block
# Ceiling on body memory held across all in-flight scans. Over it, scans
# fall back to streaming rather than refusing — load shedding, not policy.
scan_memory_budget: 268435456
# Reuse a verdict for an identical body. 0 disables. Failures never cached.
verdict_cache_ttl_secs: 300
verdict_cache_entries: 8192

internal_services:
enabled: true # auto-disabled in agent mode
port: 9180
bind_ip: "127.0.0.1"

# CIDRs whose L4 peer is a trusted upstream reverse proxy. Empty by default.
trusted_proxies: []

Client address behind a proxy​

Two things key off the client address: the WAF rate-limit bucket, and ip.src in the wirefilter context. Both default to the L4 socket peer — which, when Synapse sits behind a load balancer, is the load balancer.

That breaks quietly rather than loudly:

  • An LB that rotates its source addresses scatters one client across many rate-limit buckets, so no bucket reaches the threshold and the rule never fires.
  • An LB that collapses many clients onto a few addresses puts unrelated users in one bucket, where they rate-limit each other.

proxy.trusted_proxies fixes both. List the CIDRs your proxies connect from, and for connections arriving from those ranges Synapse takes the client address from the first X-Forwarded-For token instead of the socket peer:

proxy:
trusted_proxies:
- "35.191.0.0/16" # GCP LB health checks and GFE
- "130.211.0.0/22" # GCP LB health checks
- "10.10.0.0/20" # an internal NLB range
Why a list, and not just trusting the header

X-Forwarded-For is client-supplied and trivially forged. A client connecting directly is not inside any listed range, so its header is ignored and the socket peer wins. Trust is granted to the hop, never to the header — which is why an empty list is the safe default and why widening these ranges beyond your actual proxies hands clients a way to spoof their own address.

PROXY protocol v2 (proxy.protocol.enabled) solves the same problem for L4 proxies that speak it. Use trusted_proxies when the upstream does not — a GCP Global External Application Load Balancer, for instance.

Environment Variables​

# Application mode & runtime
export MODE="agent" # or "proxy"
export MULTI_THREAD="false" # enable multi-threaded runtime
export WORKER_THREADS="4" # worker threads when MULTI_THREAD=true

# Redis
export REDIS_URL="redis://127.0.0.1:6379/0"
export REDIS_PREFIX="g0s:synapse"
export REDIS_SSL_CA_CERT_PATH="..."
export REDIS_SSL_CLIENT_CERT_PATH="..."
export REDIS_SSL_CLIENT_KEY_PATH="..."
export REDIS_SSL_INSECURE="false"

# Network & capture
export NETWORK_IFACE="auto"
export NETWORK_IFACES="eth0,eth1"
export NETWORK_IP_VERSION="both" # ipv4, ipv6, both - "auto" is refused here
export CAPTURE_MODE="auto" # xdp, af_packet, af_packet_recv, pfring, ndis, af_xdp
export CAPTURE_SNAPLEN="65535"
export CAPTURE_FILTER="tcp"
export PFRING_ZERO_COPY="true"
export PFRING_CLUSTER_ID="1"
export FIREWALL_MODE="auto"

# Gen0Sec Platform
export API_KEY="your-api-key"
export BASE_URL="https://api.gen0sec.com/v1"
export TELEMETRY_SENDING_ENABLED="true" # platform.telemetry_sending_enabled
# (LOG_SENDING_ENABLED still works)

# Logging
export LOGGING_LEVEL="info"
export LOGGING_FILE_ENABLED="true"
export LOGGING_DIRECTORY="/var/log/synapse"
export LOGGING_MAX_FILE_SIZE="104857600"
export LOGGING_FILE_COUNT="10"
export LOGGING_SYSLOG_ENABLED="false" # Linux
export LOGGING_SYSLOG_FACILITY="daemon"
export LOGGING_SYSLOG_IDENTIFIER="synapse"
export LOGGING_ETW_ENABLED="false" # Windows
export LOGGING_WINDOWS_EVENT_LOG_ENABLED="false" # Windows
export LOGGING_WINDOWS_EVENT_LOG_SOURCE="Synapse"

# Content scanning
export CONTENT_SCANNING_ENABLED="true"
export CLAMAV_SERVER="localhost:3310"
export CONTENT_MAX_FILE_SIZE="10485760"
# `scan_expression`, `scan_content_types` and `skip_extensions` are gone — they
# are not environment variables and no longer YAML keys either. Selecting which
# traffic to scan is now a WAF rule with action `content_scanning`.

# CAPTCHA
export CAPTCHA_SITE_KEY="your-site-key"
export CAPTCHA_SECRET_KEY="your-secret-key"
export CAPTCHA_JWT_SECRET="your-jwt-secret"
export CAPTCHA_PROVIDER="hcaptcha" # hcaptcha, recaptcha, turnstile, prosopo
export CAPTCHA_TOKEN_TTL="7200"
export CAPTCHA_CACHE_TTL="300"

# Internal services & PROXY protocol
export INTERNAL_SERVICES_ENABLED="true"
export INTERNAL_SERVICES_PORT="9180"
export INTERNAL_SERVICES_BIND_IP="127.0.0.1"
export PROXY_PROTOCOL_ENABLED="true"
export PROXY_PROTOCOL_TIMEOUT="1000"

# Daemon
export DAEMON_ENABLED="true"
export DAEMON_PID_FILE="/var/run/synapse.pid"
export DAEMON_WORKING_DIRECTORY="/var/lib/synapse"
export DAEMON_USER="root"
export DAEMON_GROUP="root"
export DAEMON_CHOWN_PID_FILE="true"

Upstreams Configuration​

Routing lives in its own file, upstreams.yaml, parsed and reloaded independently of this one. It declares which hosts Synapse serves, how TLS is handled for each, and which backends a path forwards to. The whole schema — every key, every default, and what each kind of bad file does — is on its own page: Upstreams & Routing.

What config.yaml contributes is the pointer to that file and the settings that apply to whatever backends it finds:

YAML pathEnvironment variableDefault when omittedAs shippedWhat it does
proxy.upstream.confUPSTREAM_CONF""/etc/synapse/upstreams.yamlPath to the upstreams file. Empty means no routing table is loaded
proxy.upstream.healthcheck.methodUPSTREAM_HEALTHCHECK_METHODHEADHEADHTTP method the backend probe uses
proxy.upstream.healthcheck.intervalUPSTREAM_HEALTHCHECK_INTERVAL22Seconds between probes
proxy.upstream.dns_cache_ttl_secs—6060Seconds a resolved backend hostname is cached

The first three also take a flag: --upstream-conf, --upstream-healthcheck-method and --upstream-healthcheck-interval. dns_cache_ttl_secs is YAML-only — there is no flag and no environment variable for it.

The health-check interval is global, not per route

healthcheck.method and .interval are the probe settings for every terminating backend. A v2 route's own health_check: block chooses the probe type and path; it cannot set an interval, and the parser rejects one. Only a passthrough host carries its own interval. See Health checks.

When a backend is named by hostname, the resolved address is cached for dns_cache_ttl_secs rather than resolved on every connection. On Kubernetes that matters more than it sounds: without it, every upstream connection makes a blocking call to the cluster resolver, which becomes the floor on peer-select latency. Raise it where Service ClusterIPs are stable; lower it if you rely on DNS-level failover. Unset or 0 both mean the built-in 60 seconds.

SIGHUP Configuration Reload​

Synapse supports runtime configuration reload via SIGHUP signal (Windows: sc control synapse paramchange):

kill -HUP $(cat /var/run/synapse.pid)

A SIGHUP does not re-read config.yaml in one place. The signal is broadcast, and every subscriber that acts on it is listed here:

  • the daemon applies logging.level and the platform log-sender settings, and re-opens the GeoIP databases from disk;
  • the upstreams watcher re-reads the upstreams file;
  • the rules worker re-reads its rules file, or re-fetches the rules from the platform when an API key is set;
  • the XDP IDS worker re-reads config.yaml and hot-applies ids.enforce_block and the engine fields. It is the only one of the IDS workers that does: with ids.capture_mode: afpacketrecv, or when xdp is configured and XDP turns out to be unavailable — the agent falls back to the AF_PACKET worker and logs one line about it — nothing in ids.* reloads at all. On Windows the NDIS worker does not reload either;
  • on an -ml package, four model reconcilers re-read it and apply each model's enabled and pool_size.

Between them they cover the table below: no other key is applied in full. The restart table that follows names the ones operators most often expect to be live, with the one key that is applied in part; it is not the full remainder.

Hot-reloadable Settings​

SettingConfig PathNotes
Log levellogging.levelSwitches log verbosity instantly
Telemetry sendingplatform.telemetry_sending_enabledEnable/disable API log sending
API keyplatform.api_keyUpdate platform credentials
Base URLplatform.base_urlChange API endpoint
Include response bodyplatform.include_response_bodyToggle body capture in access logs
Max body sizeplatform.max_body_sizeAdjust body size limit for logs
Upstreamsproxy.upstream.confRe-reads upstreams YAML file
Security rulesLocal file or APIRe-fetches access rules, WAF rules, and IDS rules
GeoIP databasesplatform.geoip.*Re-opens the files from disk, at the paths the process started with. Nothing is downloaded, and a changed path is not picked up
IDS rules and tuningids.rule_paths, ids.managed_rules_path, ids.address_vars, ids.port_vars, ids.flow_timeout_secs, ids.max_flowsXDP IDS worker only. Rebuilds and swaps the signature engine; in-flight flow state is reset. A managed-rules bundle rewritten in place is picked up too, by content. With ids.post_tls.mode: offload the node-wide L7 consumer runs its own engine beside this one and keeps the ruleset it started with, so a reload leaves the two halves on different rules
IDS enforcementids.enforce_blockXDP IDS worker only, as above — there a SIGHUP can start or stop dropping traffic. On the AF_PACKET worker it needs a restart
ML models, enablement and poolml.penalty.*, ml.traffic_classifier.*, ml.flow_classifier.*, ml.jepa_embedder.* — each one's enabled and pool_size, and the penalty model's calibration_a / calibration_b-ml packages. Enabling a model loads it once its file is on disk; until then enabling only starts the fetch, and a later reload loads it. Disabling unloads it

Settings That Require Restart​

SettingConfig PathReason
Listen addressesproxy.address_http, proxy.address_tlsPort bindings set at startup
Network interfacenetwork.iface, network.ifacesXDP attached at startup
Per-interface featuresnetwork.interfacesLinks resolved and XDP attached at startup
Firewall modefirewall.modeBPF programs loaded at startup
Capture backendcapture.*Capture ring/sockets set up at startup
Inline IDS captureids.enabled, ids.capture_mode, ids.snaplen, and the ids.* keys not named in the hot-reload table aboveCapture and engine set up at startup. On a host running the AF_PACKET IDS worker this row is the whole of ids.*. ids.bypass.* is the half-case: the poll loop follows a new enabled at once, but the kernel-side depth caps are written only at startup, so give it a restart
Fingerprint classifierml.fingerprint_classifier.*ONNX model loaded at startup. It is the one model with no reconciler; the other four hot-reload, above
Runtime threadsmulti_thread, worker_threadsTokio runtime created at startup
Daemon settingsdaemon.*Process daemonization is one-time
Redis connectionproxy.redis.*Connection pool created at startup
CAPTCHA configproxy.captcha.*Provider initialized once
Content scanningproxy.content_scanning.*Scanner initialized once
Certificate pathsproxy.certificatesLoaded at startup
ACME settingsproxy.acme.*ACME manager created at startup
Internal servicesproxy.internal_services.*Server bound at startup
Fingerprint loglogging.fingerprint_log.*Appender built at startup
File / syslog / Windows logginglogging.file_logging_enabled, logging.syslog.*, logging.etw.*, logging.windows_event_log.*Appenders built at startup
IP versionnetwork.ip_versionBPF filter compiled at startup
PROXY protocolproxy.protocol.*Listener configured at startup

Internal Services Endpoints​

When proxy.internal_services.enabled is true, the following endpoints are available:

EndpointMethodPurpose
/healthGETHealth check
/cgi-bin/captcha/verifyPOSTCAPTCHA verification
/.well-known/acme-challenge/*GETACME HTTP-01 challenges
/cert/expirationGETCheck all certificate expiration status
/cert/expiration/:domainGETCheck specific certificate status
/cert/renew/:domainPOSTManually trigger certificate renewal

Next Steps​