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:
- YAML configuration file — via
synapse -c /etc/synapse/config.yaml - Command-line flags — e.g.
synapse --mode proxy - Environment variables — e.g.
export MODE=agent
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-levelwins overLOGGING_LEVEL, which wins overlogging.level, which wins overRUST_LOG. daemon.event_socketanddaemon.control_socket— the YAML value wins over--event-socketand--control-socket. There is no environment variable for either.
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.
| Feature | YAML path | Environment variable | Default when omitted | As shipped |
|---|---|---|---|---|
| Packet Capture | capture.enabled | CAPTURE_ENABLED | true | true |
| Capture backend | capture.mode | CAPTURE_MODE | auto | auto |
| Inline IDS (Thalamus) | ids.enabled | IDS_ENABLED | false | true |
| IDS blocking | ids.enforce_block | IDS_ENFORCE_BLOCK | false | true |
| ML Fingerprint Classifier | ml.fingerprint_classifier.enabled | CLASSIFIER_ENABLED | false | false |
| Threat Intelligence | platform.threat.enabled | THREAT_MMDB_ENABLED | true † | true |
| GeoIP | platform.geoip.enabled | GEOIP_ENABLED | true † | true |
| Telemetry Sending (API) | platform.telemetry_sending_enabled | TELEMETRY_SENDING_ENABLED | true † | true |
| Unified Fingerprint Log | logging.fingerprint_log.enabled | LOGGING_EVENTBRIDGE_LOG_ENABLED | true | true |
| File Logging | logging.file_logging_enabled | LOGGING_FILE_ENABLED | false | true |
| Syslog (Linux) | logging.syslog.enabled | LOGGING_SYSLOG_ENABLED | false | false |
| ETW (Windows) | logging.etw.enabled | LOGGING_ETW_ENABLED | false | false |
| Windows Event Log | logging.windows_event_log.enabled | LOGGING_WINDOWS_EVENT_LOG_ENABLED | false | false |
| Content Scanning | proxy.content_scanning.enabled | CONTENT_SCANNING_ENABLED | false | false |
| CAPTCHA Protection | (enabled when keys are set) | CAPTCHA_SITE_KEY, CAPTCHA_SECRET_KEY | disabled | disabled |
| ACME (Auto TLS) — deprecated | proxy.acme.enabled | ACME_ENABLED | false | true |
| Internal Services | proxy.internal_services.enabled | INTERNAL_SERVICES_ENABLED | true | true |
| PROXY Protocol | proxy.protocol.enabled | PROXY_PROTOCOL_ENABLED | false | true |
| HTTP/2 cleartext (h2c) | proxy.h2c | PROXY_H2C | false | false |
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.enabledandplatform.geoip.enabledneed athreat:orgeoip:block to be on. Aplatform:block that sets onlyapi_key— which is what most hand-written configs and the Basic Example look like — leaves threat intelligence and GeoIP off.enabled: trueon 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-emptythreat.urlor athreat.path, and for GeoIP aurlorpathundercountry,asnorcity. Neither is derived fromplatform.base_url, so a block holding nothing butenabled: trueinitialises nothing and logs nothing you would recognise as a failure. The Platform section below has the shape that works. -
platform.telemetry_sending_enabledistruewhenever aplatform:block exists at all, andfalseifplatform: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.
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.
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
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
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
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 overwrittenThe 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.
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
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 path | Environment variable | Default when omitted | As shipped | What it does |
|---|---|---|---|---|
proxy.upstream.conf | UPSTREAM_CONF | "" | /etc/synapse/upstreams.yaml | Path to the upstreams file. Empty means no routing table is loaded |
proxy.upstream.healthcheck.method | UPSTREAM_HEALTHCHECK_METHOD | HEAD | HEAD | HTTP method the backend probe uses |
proxy.upstream.healthcheck.interval | UPSTREAM_HEALTHCHECK_INTERVAL | 2 | 2 | Seconds between probes |
proxy.upstream.dns_cache_ttl_secs | — | 60 | 60 | Seconds 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.
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.leveland theplatformlog-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.yamland hot-appliesids.enforce_blockand the engine fields. It is the only one of the IDS workers that does: withids.capture_mode: afpacketrecv, or whenxdpis configured and XDP turns out to be unavailable — the agent falls back to the AF_PACKET worker and logs one line about it — nothing inids.*reloads at all. On Windows the NDIS worker does not reload either; - on an
-mlpackage, four model reconcilers re-read it and apply each model'senabledandpool_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
| Setting | Config Path | Notes |
|---|---|---|
| Log level | logging.level | Switches log verbosity instantly |
| Telemetry sending | platform.telemetry_sending_enabled | Enable/disable API log sending |
| API key | platform.api_key | Update platform credentials |
| Base URL | platform.base_url | Change API endpoint |
| Include response body | platform.include_response_body | Toggle body capture in access logs |
| Max body size | platform.max_body_size | Adjust body size limit for logs |
| Upstreams | proxy.upstream.conf | Re-reads upstreams YAML file |
| Security rules | Local file or API | Re-fetches access rules, WAF rules, and IDS rules |
| GeoIP databases | platform.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 tuning | ids.rule_paths, ids.managed_rules_path, ids.address_vars, ids.port_vars, ids.flow_timeout_secs, ids.max_flows | XDP 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 enforcement | ids.enforce_block | XDP 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 pool | ml.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
| 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 |
| Per-interface features | network.interfaces | Links resolved and XDP attached at startup |
| Firewall mode | firewall.mode | BPF programs loaded at startup |
| Capture backend | capture.* | Capture ring/sockets set up at startup |
| Inline IDS capture | ids.enabled, ids.capture_mode, ids.snaplen, and the ids.* keys not named in the hot-reload table above | Capture 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 classifier | ml.fingerprint_classifier.* | ONNX model loaded at startup. It is the one model with no reconciler; the other four hot-reload, above |
| Runtime threads | multi_thread, worker_threads | Tokio runtime created at startup |
| Daemon settings | daemon.* | Process daemonization is one-time |
| Redis connection | proxy.redis.* | Connection pool created at startup |
| CAPTCHA config | proxy.captcha.* | Provider initialized once |
| Content scanning | proxy.content_scanning.* | Scanner initialized once |
| Certificate paths | proxy.certificates | Loaded at startup |
| ACME settings | proxy.acme.* | ACME manager created at startup |
| Internal services | proxy.internal_services.* | Server bound at startup |
| Fingerprint log | logging.fingerprint_log.* | Appender built at startup |
| File / syslog / Windows logging | logging.file_logging_enabled, logging.syslog.*, logging.etw.*, logging.windows_event_log.* | Appenders built at startup |
| IP version | network.ip_version | BPF filter compiled at startup |
| PROXY protocol | proxy.protocol.* | Listener configured at startup |
Internal Services Endpoints
When proxy.internal_services.enabled is true, the following endpoints are available:
| Endpoint | Method | Purpose |
|---|---|---|
/health | GET | Health check |
/cgi-bin/captcha/verify | POST | CAPTCHA verification |
/.well-known/acme-challenge/* | GET | ACME HTTP-01 challenges |
/cert/expiration | GET | Check all certificate expiration status |
/cert/expiration/:domain | GET | Check specific certificate status |
/cert/renew/:domain | POST | Manually trigger certificate renewal |
Next Steps
- Configuration Examples - Five complete files to copy: agent, per-interface features, proxy with TLS termination, database passthrough, Windows
- Daemon Mode - Run as a background service
- JA4+ Fingerprinting - Complete fingerprinting suite
- Testing Guide - Validate your configuration