Configuration

June 25, 2026 · View on GitHub

Priority order

Settings are resolved highest to lowest:

  1. Command-line flagssynapse --log-level debug
  2. YAML config filesynapse -c /etc/synapse/config.yaml
  3. Environment variablesexport LOG_LEVEL=debug

CLI options

FlagDescriptionDefault
-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 XDPeth0
--ifaces <LIST>Additional interfaces, comma-separated (overrides --iface)
--log-level <LEVEL>Log level: error, warn, info, debug, traceinfo
--disable-xdpDisable XDP packet filteringfalse
--redis-url <URL>Redis connection URLredis://127.0.0.1/0
--redis-prefix <PREFIX>Redis namespace prefixax: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-enabledEnable PROXY protocolfalse
--proxy-protocol-timeout <MS>PROXY protocol timeout (ms)1000
-d, --daemonRun as daemon (Linux)false
--service <CMD>Windows SCM: install or uninstall
--terminalEnable ratatui TUIfalse
--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

FeatureYAML PathEnvironment VariableDefault
BPF Statisticslogging.bpf_stats.enabledBPF_STATS_ENABLEDtrue
TCP Fingerprintinglogging.tcp_fingerprint.enabledTCP_FINGERPRINT_ENABLEDtrue
Content Scanningproxy.content_scanning.enabledCONTENT_SCANNING_ENABLEDfalse
CAPTCHA Protection(enabled when keys are set)CAPTCHA_SITE_KEY, CAPTCHA_SECRET_KEYdisabled
ACME (Auto TLS)proxy.acme.enabledACME_ENABLEDfalse
Internal Servicesproxy.internal_services.enabledINTERNAL_SERVICES_ENABLEDtrue
Config-Push SSEproxy.config_stream.enabledfalse
PROXY Protocolproxy.protocol.enabledPROXY_PROTOCOL_ENABLEDfalse
File Logginglogging.file_logging_enabledLOGGING_FILE_ENABLEDfalse
Syslog (Linux)logging.syslog.enabledLOGGING_SYSLOG_ENABLEDfalse
ETW (Windows)logging.etw.enabledLOGGING_ETW_ENABLEDfalse
Log Sending (API)platform.log_sending_enabledLOG_SENDING_ENABLEDtrue
XDP Firewallfirewall.modeFIREWALL_MODEauto

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

ValueBehaviour
"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
modeBehaviour
autoSelect highest available: XDP → nftables → iptables → none
xdpForce XDP; startup fails if unavailable
nftablesForce nftables; startup fails if unavailable
iptablesForce iptables; startup fails if unavailable
noneUserland 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
KeyDefaultPurpose
enabledfalseTurn the IDS engine on.
capture_modexdpxdp (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_blockfalsetrue pushes IDS verdicts to the firewall (drop offending sources); false is alert-only.
managed_rules_base_url / _path / _refresh_secs"" / "" / 3600Pull 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).
bypassoffSuricata-style flow bypass so one elephant flow can't flood the inspect channel (Linux/XDP).
consumer8 threadsXDP inspect-path thread count, per-shard queue, and optional CPU pinning (Linux/XDP).
post_tls.modeinlineWhere 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 check
  • POST /cgi-bin/captcha/verify — CAPTCHA verification
  • GET /.well-known/acme-challenge/* — ACME HTTP-01 challenges
  • GET /cert/expiration — all certificate expiry status
  • GET /cert/expiration/:domain — specific certificate status
  • POST /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; generation increments
  • lagged — 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-alive comment 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_token for defense in depth (compared in constant time).
  • Responses: 401 (bad/missing bearer when auth_token is set), 503 (no reload channel, or max_connections exceeded).

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):

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.

SchemaDetectionStatus
v2version: 2 at top of fileRecommended for new deployments. Adds explicit tls.{terminate.{cert|acme}|passthrough} modes, layered headers/timeouts (global → host → route), single-upstream sugar, wildcard SNI passthrough.
v1No version: field, or version: 1Legacy. 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 modetls.terminate.cert (static cert path), tls.terminate.acme (ACME-issued), or tls.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 / timeoutsheaders.{request,response} and timeouts.{connect,read,write,idle} at global, host, and route level. Per-route extends global; explicit per-route value overrides.
  • Single-upstream sugarupstream: host:port short form for the common case (skip the upstreams: [{addr, weight}] list).
  • Health checks — TCP or HTTP, with consecutive-failure / consecutive-success thresholds.
  • Multiple LB methodsround_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

SettingConfig Path
Log levellogging.level
Log sendingplatform.log_sending_enabled
API keyplatform.api_key
Base URLplatform.base_url
Include response bodyplatform.include_response_body
Max body sizeplatform.max_body_size
Upstreamsproxy.upstream.conf
Security rulesLocal file or API
GeoIP databasesproxy.geoip.*

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
Firewall modefirewall.modeBPF programs loaded at startup
Runtime threadsmulti_thread, worker_threadsTokio runtime created at startup
Daemon settingsdaemon.*Process daemonisation is one-time
Redis connectionproxy.redis.*Connection pool created at startup