Configuration Interfaces
Four things configure synapse, and they are not interchangeable:
config.yaml- the full schema, documented on Configuration.- Command-line flags - a subset of the schema, plus a handful of one-shot commands.
- Environment variables - a different subset, plus variables that exist nowhere in the YAML.
- The package you installed - not every build contains every subsystem. A key for a subsystem your package does not carry still parses, and then does nothing.
This page covers how the first three combine, lists all of them, and ends with what each package actually contains.
Every flag, variable, default and behaviour here was read out of the v0.8.8 source. The counts are exact for that release: 84 flags, of which 63 write a configuration key; 129 environment variable names in the merge path, of which 116 are canonical and 13 are fallbacks; and 51 further variables read at runtime outside the merge. The four per-signal rows under OpenTelemetry stand for three names each.
Which source wins
Configuration is assembled in three steps, in this order:
- the YAML file is parsed (
--config, or built-in defaults if no file is given); - command-line flags are merged over it;
- environment variables are applied over that.
Step 3 assigns unconditionally. So the order of precedence is:
environment variable → command-line flag → config.yaml → built-in default.
This is the opposite of the usual convention, and the opposite of what synapse's own
in-repo docs/CONFIGURATION.md says. If a flag appears to be ignored, look for the
environment variable that writes the same key - a stray FIREWALL_MODE in a unit file or a
container spec silently wins over --firewall-mode on the command line.
There is no diagnostic for this. Nothing logs that an environment variable replaced a flag's value.
Two settings do not follow that order.
Exception 1: the log level
The log level is resolved separately, and the flag wins:
--log-level → logging.level (YAML, or LOGGING_LEVEL over it) → RUST_LOG → info.
RUST_LOG is only consulted when neither the flag nor logging.level is set, and only a
bare level is accepted: error, warn, info, debug, trace, off. A per-module
directive such as RUST_LOG=synapse=debug,hyper=warn is not passed through - it does not
match one of the six names, so the level degrades to info with no warning.
Synapse then overwrites RUST_LOG in its own environment with the level it resolved, so
anything reading the variable later sees the resolved value rather than what you set.
Exception 2: the event and control sockets
daemon.event_socket and daemon.control_socket in the YAML win over --event-socket
and --control-socket in the process that serves those sockets - the daemon. The
flags only apply there when the YAML leaves the key unset, and neither key has an
environment variable.
The client side does not consult the YAML at all. synapse --terminal connects to
whatever --event-socket says, default included, so moving the socket with
daemon.event_socket alone leaves the terminal looking at the old path. Pass
--event-socket to the terminal as well, or keep the socket where the default puts it.
Three flags overwrite the YAML even when you do not pass them
Most flags are merged only when you actually pass them. These three are assigned unconditionally, so the flag's own default lands on top of whatever the YAML said:
| Flag | Key it overwrites | Value written when the flag is absent |
|---|---|---|
--arxignis-include-response-body | platform.include_response_body | false |
--arxignis-max-body-size | platform.max_body_size | 1048576 |
--daemon-chown-pid-file | daemon.chown_pid_file | true |
Setting any of these three in config.yaml has no effect. Use the flag or, where one
exists, the environment variable.
proxy.redis.prefix cannot be set from the YAML
Seven flags are merged only when their value differs from a sentinel, which is how a flag
with a built-in default avoids clobbering the YAML. For six of them the sentinel is the
flag's own default and the mechanism works. For --redis-prefix the two disagree: the
flag defaults to g0s:synapse and the comparison is against ax:synapse, so the test
always passes and proxy.redis.prefix from the YAML is always replaced by g0s:synapse.
To use a different prefix, pass --redis-prefix or set REDIS_PREFIX. Both work.
What a bad value does
An environment variable that cannot be parsed into the key's type is handled in one of four ways. All four are silent for all but three variables:
| Behaviour | What happens | Variables |
|---|---|---|
| used as given | the string is stored unvalidated | 55 |
| built-in default | the key is reset to the code default, discarding the YAML value | 35 |
| ignored | the assignment is skipped and the YAML value survives | 20 |
| cleared | the key is set to "unset", discarding the YAML value | 6 |
IDS_MAX_FLOWS=lots resets ids.max_flows to the code default even if the YAML set it;
CAPTURE_ENABLED=yes is ignored entirely, because only true and false parse as a
boolean; GEOIP_COUNTRY_HEADERS=junk clears platform.geoip.country.headers, so a YAML
header map is dropped rather than kept. The Unparseable value column in the tables
below says which applies to each variable.
Only three variables log anything when their value does not parse, and the tables mark
them "and it warns": CAPTURE_MODE and IDS_CAPTURE_MODE, which warn and then use the
built-in default, and NETWORK_IP_VERSION, which warns and then changes nothing. Its
message claims it is "using default 'both'", but the code makes no assignment, so the
YAML value actually survives.
FIREWALL_MODE silently disables what you configuredfirewall.mode is one of the 35. Any value that is not auto, xdp, nftables,
iptables or none resets the key to auto, with nothing logged - so
FIREWALL_MODE=xpd in a unit file replaces a deliberate xdp or none in your
config.yaml with mode selection you did not ask for. The same shape applies to
capture.mode and ids.capture_mode, which at least warn.
Command-line flags
synapse --help lists these. 84 flags exist; 63 of them write a configuration key.
Flags that write a configuration key
The key is the YAML path on Configuration, which is where its meaning is documented. "Overrides the YAML" says when the merge applies the flag.
| Flag | Value | Flag default | YAML key it writes | Overrides the YAML |
|---|---|---|---|---|
--redis-url | string | redis://127.0.0.1/0 | proxy.redis.url | unless it is redis://127.0.0.1/0 |
--redis-prefix | string | g0s:synapse | proxy.redis.prefix | unless it is ax:synapse |
--iface | string | auto | network.iface | unless it is auto |
--ifaces | list | — | network.ifaces | when non-empty |
--arxignis-api-key | string | — | platform.api_key | when passed |
--arxignis-base-url | string | https://api.gen0sec.com/v1 | platform.base_url | unless it is https://api.gen0sec.com/v1 |
--arxignis-telemetry-sending-enabledalso accepted: --arxignis-log-sending-enabled | true/false | — | platform.telemetry_sending_enabled | when passed |
--arxignis-include-response-body | switch | false | platform.include_response_body | always, even when absent |
--arxignis-max-body-size | integer | 1048576 | platform.max_body_size | always, even when absent |
--firewall-mode | string | — | firewall.mode | when passed |
--captcha-site-key | string | — | proxy.captcha.site_key | when passed |
--captcha-secret-key | string | — | proxy.captcha.secret_key | when passed |
--captcha-jwt-secret | string | — | proxy.captcha.jwt_secret | when passed |
--captcha-provider | hcaptcha/recaptcha/turnstile/prosopo | — | proxy.captcha.provider | when passed |
--proxy-protocol-enabled | switch | false | proxy.protocol.enabled | when given |
--proxy-protocol-timeout | integer | 1000 | proxy.protocol.timeout_ms | unless it is 1000 |
-d, --daemon | switch | false | daemon.enabled → true | when given |
--daemon-pid-file | string | /var/run/synapse.pid (Linux), empty (Windows) | daemon.pid_file | unless it is the built-in default |
--daemon-working-dir | string | / (Linux), C:\ProgramData\Gen0Sec\Synapse (Windows) | daemon.working_directory | unless it is the built-in default |
--daemon-user | string | — | daemon.user | when passed |
--daemon-group | string | — | daemon.group | when passed |
--daemon-chown-pid-file | switch | true | daemon.chown_pid_file | always, even when absent |
--mode | string | — | mode | when passed |
--network-ip-version | string | — | network.ip_version | when passed |
--threat-mmdb-url | string | — | platform.threat.url | when passed |
--threat-mmdb-path | string | — | platform.threat.path | when passed |
--threat-mmdb-refresh-secs | integer | — | platform.threat.refresh_secs | when passed |
--file-logging-enabled | true/false | — | logging.file_logging_enabled | when passed |
--log-directory | string | — | logging.log_directory | when passed |
--max-log-size | integer | — | logging.max_log_size | when passed |
--log-file-count | integer | — | logging.log_file_count | when passed |
--syslog-enabled | true/false | — | logging.syslog.enabled | when passed |
--syslog-facility | string | — | logging.syslog.facility | when passed |
--syslog-identifier | string | — | logging.syslog.identifier | when passed |
--proxy-address-http | string | — | proxy.address_http | when passed |
--proxy-address-tls | string | — | proxy.address_tls | when passed |
--proxy-certificates | string | — | proxy.certificates | when passed |
--proxy-tls-grade | string | — | proxy.tls_grade | when passed |
--proxy-default-certificate | string | — | proxy.default_certificate | when passed |
--upstream-conf | string | — | proxy.upstream.conf | when passed |
--upstream-healthcheck-method | string | — | proxy.upstream.healthcheck.method | when passed |
--upstream-healthcheck-interval | integer | — | proxy.upstream.healthcheck.interval | when passed |
--geoip-country-url | string | — | platform.geoip.country.url | when passed |
--geoip-country-path | string | — | platform.geoip.country.path | when passed |
--geoip-asn-url | string | — | platform.geoip.asn.url | when passed |
--geoip-asn-path | string | — | platform.geoip.asn.path | when passed |
--geoip-city-url | string | — | platform.geoip.city.url | when passed |
--geoip-city-path | string | — | platform.geoip.city.path | when passed |
--geoip-refresh-secs | integer | — | platform.geoip.refresh_secs | when passed |
--acme-enabled | true/false | — | proxy.acme.enabled | when passed |
--acme-port | integer | — | proxy.acme.port | when passed |
--acme-email | string | — | proxy.acme.email | when passed |
--acme-storage-path | string | — | proxy.acme.storage_path | when passed |
--acme-storage-type | string | — | proxy.acme.storage_type | when passed |
--acme-development | true/false | — | proxy.acme.development | when passed |
--acme-redis-url | string | — | proxy.acme.redis_url | when passed |
--content-scanning-enabled | true/false | — | proxy.content_scanning.enabled | when passed |
--content-scanning-clamav-server | string | — | proxy.content_scanning.clamav_server | when passed |
--content-scanning-max-file-size | integer | — | proxy.content_scanning.max_file_size | when passed |
--internal-services-enabled | true/false | — | proxy.internal_services.enabled | when passed |
--internal-services-port | integer | — | proxy.internal_services.port | when passed |
--internal-services-bind-ip | string | — | proxy.internal_services.bind_ip | when passed |
-t, --terminal | switch | false | daemon.enabled → false | when given |
--captcha-provider exists only in builds that carry the proxy, so it is absent from
synapse --help on Windows and on the legacy agent. Every other flag is present
everywhere, whether or not the subsystem behind it is.
Flags that do not write a configuration key
| Flag | Value | Default | What it does |
|---|---|---|---|
-c, --config | path | — | The YAML file to load. Without it, synapse starts from built-in defaults plus flags and environment. |
--security-rules-config | path | security_rules.yaml | Local access and WAF rules, used when no Gen0Sec API key is configured. |
--log-level | error / warn / info / debug / trace | — | Sets the log level. Beats logging.level and RUST_LOG - see Exception 1. The flag has no off; only logging.level, LOGGING_LEVEL and RUST_LOG accept it. |
--event-socket | path | /var/run/synapse-events.sock (Linux), C:\ProgramData\Gen0Sec\Synapse\events.sock (Windows) | Where the daemon serves events for terminal mode, and where --terminal connects. daemon.event_socket wins over it in the daemon only - see Exception 2. |
--control-socket | path | /var/run/synapse-control.sock (Linux), C:\ProgramData\Gen0Sec\Synapse\control.sock (Windows) | Where the daemon serves control commands. daemon.control_socket wins over it in the daemon only - see Exception 2. |
--service | install / uninstall | — | Windows only: register or remove the Windows service, then exit. |
--upgrade | switch | — | Signal the running synapse to replace its binary in place, then exit. Linux only; on Windows the flag is accepted and synapse starts normally instead. It needs a build that carries graceful restart, which the legacy agent does not: there the signal has no handler and stops the process rather than upgrading it. |
--show-firewall-rules | text / json | — | Print the rules the kernel is actually enforcing, read out of the pinned maps, then exit. Read-only, needs root, and does not need synapse to be running. |
--firewall-pin-root | path | /sys/fs/bpf/hillock | Where to look for those pinned maps. Only meaningful with --show-firewall-rules. |
--force-stop | switch | — | Stop synapse and remove its kernel datapath - attached programs and pinned maps outlive the process, so a crashed agent leaves enforcement running with nothing owning it. Needs root; safe to run twice. |
--force-stop-dry-run | switch | — | Print what --force-stop would remove, and remove nothing. |
--clear-certificate | host name | — | Remove one certificate from local storage and from Redis, then exit. Needs a build that carries the proxy; on Windows and on the legacy agent the flag is accepted and synapse starts normally instead. |
-T, --tcpdump | switch | false | Non-interactive packet output instead of the terminal view. Mutually exclusive with --terminal. |
-r, --reply | switch | false | In terminal mode, correlate SYN with SYN-ACK. |
--block-mode | switch | false | In terminal and replay runs, block what is classified as a threat and add the source address to the runtime blocklist. |
--pcap-file | path | — | Replay a capture file through the full pipeline: same fingerprints, classification and rule evaluation as live traffic. Not supported with --daemon. |
--pcap-limit | integer | 0 | Stop after this many packets. 0 reads the whole file. |
--pcap-start | YYYY-MM-DD or YYYY-MM-DD HH:MM:SS (UTC) | — | Skip packets before this timestamp. |
--pcap-end | YYYY-MM-DD or YYYY-MM-DD HH:MM:SS (UTC) | — | Skip packets after this timestamp. |
--captcha-token-ttl | integer | 7200 | Nothing. Parsed and never read. |
--captcha-cache-ttl | integer | 300 | Nothing. Parsed and never read. |
--disable-xdp does not existSynapse's in-repo docs/CONFIGURATION.md lists a --disable-xdp flag. There is no such
flag in the code, at this release or any other. To not attach XDP, set
firewall.mode: none or pick a non-XDP capture.mode.
Environment variables
The deprecated prefixes
Every variable in the merge path is looked up unprefixed first. If that is unset, a deprecated prefixed form is tried, and using one logs a deprecation warning naming the variable to move to:
- all variables:
NAME, thenAX_NAME. - the ten platform variables marked ◆ below:
NAME, thenAX_NAME, thenAX_ARXIGNIS_NAME, thenARXIGNIS_NAME.
An unset variable and an empty one are different: an empty value is a value, and it is assigned.
Variables that override a configuration key
116 variables, listed by the section of config.yaml they write. 13 further names are
fallbacks, shown under the variable they stand in for. The key's meaning is documented on
Configuration; this table only maps the variable onto the key.
Mode and threading
| Variable | YAML key | Value | Unparseable value |
|---|---|---|---|
MODE | mode | string | used as given |
MULTI_THREAD | multi_thread | true / false | ignored |
WORKER_THREADS | worker_threads | integer | ignored |
Network
| Variable | YAML key | Value | Unparseable value |
|---|---|---|---|
NETWORK_IFACE | network.iface | string | used as given |
NETWORK_IFACES | network.ifaces | comma-separated list | used as given |
NETWORK_IP_VERSION | network.ip_version | ipv4 / ipv6 / both | ignored, and it warns |
Firewall
| Variable | YAML key | Value | Unparseable value |
|---|---|---|---|
FIREWALL_MODEfalls back to NETWORK_FIREWALL_MODE | firewall.mode | auto / xdp / nftables / iptables / none | built-in default |
IDS (Thalamus)
| Variable | YAML key | Value | Unparseable value |
|---|---|---|---|
IDS_ENABLEDfalls back to FIREWALL_IDS_ENABLED | ids.enabled | true / false | built-in default |
IDS_RULE_PATHSfalls back to FIREWALL_IDS_RULE_PATHS | ids.rule_paths | comma-separated list | used as given |
IDS_SNAPLENfalls back to FIREWALL_IDS_SNAPLEN | ids.snaplen | integer | built-in default |
IDS_CAPTURE_MODEfalls back to FIREWALL_IDS_CAPTURE_MODE | ids.capture_mode | xdp / afpacketrecv | built-in default, and it warns |
IDS_POLL_TIMEOUT_MSfalls back to FIREWALL_IDS_POLL_TIMEOUT_MS | ids.poll_timeout_ms | integer | built-in default |
IDS_FLOW_TIMEOUT_SECSfalls back to FIREWALL_IDS_FLOW_TIMEOUT_SECS | ids.flow_timeout_secs | integer | built-in default |
IDS_MAX_FLOWSfalls back to FIREWALL_IDS_MAX_FLOWS | ids.max_flows | integer | built-in default |
IDS_CLEANUP_INTERVAL_SECSfalls back to FIREWALL_IDS_CLEANUP_INTERVAL_SECS | ids.cleanup_interval_secs | integer | built-in default |
IDS_STATS_LOG_INTERVAL_SECSfalls back to FIREWALL_IDS_STATS_LOG_INTERVAL_SECS | ids.stats_log_interval_secs | integer | built-in default |
IDS_ENFORCE_BLOCKfalls back to FIREWALL_IDS_ENFORCE_BLOCK | ids.enforce_block | true / false | built-in default |
IDS_ENFORCE_EDGE_DROPfalls back to FIREWALL_IDS_ENFORCE_EDGE_DROP | ids.enforce_edge_drop | true / false | built-in default |
Capture
| Variable | YAML key | Value | Unparseable value |
|---|---|---|---|
CAPTURE_MODE | capture.mode | auto / xdp / af_packet / af_packet_recv / pfring / af_xdp / ndis | built-in default, and it warns |
CAPTURE_SNAPLEN | capture.snaplen | integer | built-in default |
CAPTURE_ENABLED | capture.enabled | true / false | ignored |
CAPTURE_FILTER | capture.filter | string | used as given |
PFRING_ZERO_COPY | capture.pfring.zero_copy | true / false | built-in default |
PFRING_CLUSTER_ID | capture.pfring.cluster_id | integer | cleared |
ML fingerprint classifier
| Variable | YAML key | Value | Unparseable value |
|---|---|---|---|
CLASSIFIER_ENABLED | classifier.enabled | true / false | ignored |
CLASSIFIER_MODEL_PATH | classifier.model_path | string | used as given |
CLASSIFIER_CACHE_SIZE | classifier.cache_size | integer | ignored |
CLASSIFIER_THRESHOLD | classifier.threshold | number | ignored |
Platform: API, GeoIP and threat feed
| Variable | YAML key | Value | Unparseable value |
|---|---|---|---|
API_KEY ◆ | platform.api_key | string | used as given |
BASE_URL ◆ | platform.base_url | string | used as given |
AUTHCHECK_URL ◆ | platform.authcheck.url | string | used as given |
REMOTE_CONFIG_URL ◆ | platform.remote_config.url | string | used as given |
AGENT_EVENTS_URL ◆ | platform.agent_events.url | string | used as given |
DATA_EVENTS_URL ◆ | platform.data_events.url | string | used as given |
TELEMETRY_URL ◆ | platform.telemetry_endpoint.url | string | used as given |
TELEMETRY_SENDING_ENABLED ◆falls back to LOG_SENDING_ENABLED | platform.telemetry_sending_enabled | true / false | ignored |
INCLUDE_RESPONSE_BODY ◆ | platform.include_response_body | true / false | built-in default |
MAX_BODY_SIZE ◆ | platform.max_body_size | integer | built-in default |
THREAT_MMDB_URL | platform.threat.url | string | used as given |
THREAT_MMDB_PATH | platform.threat.path | string | used as given |
THREAT_MMDB_REFRESH_SECS | platform.threat.refresh_secs | integer | ignored |
GEOIP_COUNTRY_URL | platform.geoip.country.url | string | used as given |
GEOIP_COUNTRY_PATH | platform.geoip.country.path | string | used as given |
GEOIP_ASN_URL | platform.geoip.asn.url | string | used as given |
GEOIP_ASN_PATH | platform.geoip.asn.path | string | used as given |
GEOIP_CITY_URL | platform.geoip.city.url | string | used as given |
GEOIP_CITY_PATH | platform.geoip.city.path | string | used as given |
GEOIP_ENABLED | platform.geoip.enabled | true / false | ignored |
THREAT_MMDB_ENABLED | platform.threat.enabled | true / false | ignored |
GEOIP_COUNTRY_HEADERS | platform.geoip.country.headers | comma-separated k=v | cleared |
GEOIP_ASN_HEADERS | platform.geoip.asn.headers | comma-separated k=v | cleared |
GEOIP_CITY_HEADERS | platform.geoip.city.headers | comma-separated k=v | cleared |
THREAT_MMDB_HEADERS | platform.threat.headers | comma-separated k=v | cleared |
GEOIP_REFRESH_SECS | platform.geoip.refresh_secs | integer | ignored |
Logging
| Variable | YAML key | Value | Unparseable value |
|---|---|---|---|
LOGGING_LEVEL | logging.level | string | used as given |
LOGGING_FILE_ENABLED | logging.file_logging_enabled | true / false | built-in default |
LOGGING_DIRECTORY | logging.log_directory | string | used as given |
LOGGING_MAX_FILE_SIZE | logging.max_log_size | integer | built-in default |
LOGGING_FILE_COUNT | logging.log_file_count | integer | built-in default |
LOGGING_SYSLOG_ENABLED | logging.syslog.enabled | true / false | built-in default |
LOGGING_SYSLOG_FACILITY | logging.syslog.facility | string | used as given |
LOGGING_EVENTBRIDGE_LOG_ENABLED | logging.eventbridge_log.enabled | true / false | ignored |
LOGGING_SYSLOG_IDENTIFIER | logging.syslog.identifier | string | used as given |
LOGGING_ETW_ENABLED | logging.etw.enabled | true / false | built-in default |
LOGGING_WINDOWS_EVENT_LOG_ENABLED | logging.windows_event_log.enabled | true / false | built-in default |
LOGGING_WINDOWS_EVENT_LOG_SOURCE | logging.windows_event_log.source | string | used as given |
Daemon
| Variable | YAML key | Value | Unparseable value |
|---|---|---|---|
DAEMON_ENABLED | daemon.enabled | true / false | built-in default |
DAEMON_PID_FILE | daemon.pid_file | string | used as given |
DAEMON_WORKING_DIRECTORY | daemon.working_directory | string | used as given |
DAEMON_USER | daemon.user | string | used as given |
DAEMON_GROUP | daemon.group | string | used as given |
DAEMON_CHOWN_PID_FILE | daemon.chown_pid_file | true / false | built-in default |
Proxy
| Variable | YAML key | Value | Unparseable value |
|---|---|---|---|
REDIS_URL | proxy.redis.url | string | used as given |
REDIS_PREFIX | proxy.redis.prefix | string | used as given |
REDIS_SSL_CA_CERT_PATH | proxy.redis.ssl.ca_cert_path | string | used as given |
REDIS_SSL_CLIENT_CERT_PATH | proxy.redis.ssl.client_cert_path | string | used as given |
REDIS_SSL_CLIENT_KEY_PATH | proxy.redis.ssl.client_key_path | string | used as given |
REDIS_SSL_INSECURE | proxy.redis.ssl.insecure | true / false | ignored |
CONTENT_VERDICT_CACHE_TTL_SECS | proxy.content_scanning.verdict_cache_ttl_secs | integer | ignored |
CONTENT_SCAN_MEMORY_BUDGET | proxy.content_scanning.scan_memory_budget | integer | ignored |
CONTENT_SCANNING_ENABLED | proxy.content_scanning.enabled | true / false | built-in default |
CLAMAV_SERVER | proxy.content_scanning.clamav_server | string | used as given |
CONTENT_MAX_FILE_SIZE | proxy.content_scanning.max_file_size | integer | built-in default |
CAPTCHA_SITE_KEY | proxy.captcha.site_key | string | used as given |
CAPTCHA_SECRET_KEY | proxy.captcha.secret_key | string | used as given |
CAPTCHA_JWT_SECRET | proxy.captcha.jwt_secret | string | used as given |
CAPTCHA_PROVIDER | proxy.captcha.provider | string | used as given |
CAPTCHA_TOKEN_TTL | proxy.captcha.token_ttl | integer | built-in default |
CAPTCHA_CACHE_TTL | proxy.captcha.cache_ttl | integer | built-in default |
INTERNAL_SERVICES_ENABLED | proxy.internal_services.enabled | true / false | built-in default |
INTERNAL_SERVICES_PORT | proxy.internal_services.port | integer | built-in default |
INTERNAL_SERVICES_BIND_IP | proxy.internal_services.bind_ip | string | used as given |
INTERNAL_SERVICES_SERVE_ACME_CHALLENGE | proxy.internal_services.serve_acme_challenge | true / false | built-in default |
PROXY_PROTOCOL_ENABLED | proxy.protocol.enabled | true / false | built-in default |
PROXY_PROTOCOL_TIMEOUT | proxy.protocol.timeout_ms | integer | built-in default |
PROXY_ADDRESS_HTTP | proxy.address_http | string | used as given |
PROXY_ADDRESS_TLS | proxy.address_tls | string | used as given |
PROXY_CERTIFICATES | proxy.certificates | string | used as given |
PROXY_H2C | proxy.h2c | true / false | ignored |
PROXY_ALLOW_CONNECT_METHOD_PROXYING | proxy.allow_connect_method_proxying | true / false | ignored |
PROXY_KEEPALIVE_REQUEST_LIMIT | proxy.keepalive_request_limit | integer | cleared |
PROXY_TLS_GRADE | proxy.tls_grade | string | used as given |
PROXY_DEFAULT_CERTIFICATE | proxy.default_certificate | string | used as given |
PROXY_DEFAULT_CERT_PATH | proxy.default_cert_path | string | used as given |
PROXY_DEFAULT_KEY_PATH | proxy.default_key_path | string | used as given |
UPSTREAM_CONF | proxy.upstream.conf | string | used as given |
UPSTREAM_HEALTHCHECK_METHOD | proxy.upstream.healthcheck.method | string | used as given |
UPSTREAM_HEALTHCHECK_INTERVAL | proxy.upstream.healthcheck.interval | integer | ignored |
ACME_ENABLED | proxy.acme.enabled | true / false | built-in default |
ACME_PORT | proxy.acme.port | integer | ignored |
ACME_BIND_IP | proxy.acme.bind_ip | string | used as given |
ACME_EMAIL | proxy.acme.email | string | used as given |
ACME_STORAGE_PATH | proxy.acme.storage_path | string | used as given |
ACME_STORAGE_TYPE | proxy.acme.storage_type | string | used as given |
ACME_DEVELOPMENT | proxy.acme.development | true / false | built-in default |
ACME_REDIS_URL | proxy.acme.redis_url | string | used as given |
Two notes on that table:
- Setting any one of
REDIS_SSL_CA_CERT_PATH,REDIS_SSL_CLIENT_CERT_PATH,REDIS_SSL_CLIENT_KEY_PATHorREDIS_SSL_INSECUREcreates theproxy.redis.sslblock if the YAML has none, which turns Redis TLS on as a side effect. CAPTCHA_TOKEN_TTLandCAPTCHA_CACHE_TTLwriteproxy.captcha.token_ttlandproxy.captcha.cache_ttl, and both keys are read: the token TTL sets the expiry of the captcha token and theMax-Ageof the cookie carrying it. The--captcha-token-ttland--captcha-cache-ttlflags are the ones that do nothing - they are parsed and never read, so set the keys or the variables instead.
Variables read at runtime
These are not part of the configuration merge and have no YAML equivalent. They are read where they are used, so an invalid value is usually ignored silently at that point.
Agent identity and status
Reported to the Gen0Sec API. AGENT_TAGS, AGENT_IP_ADDRESSES, AGENT_IPS,
AGENT_HEARTBEAT_SECS and AGENT_ID are read only when platform.api_key is set and
the run is not a PCAP replay. AGENT_NAME and HOSTNAME are read on every run: they
also name the instance in OpenTelemetry resource attributes and the host field on CEF
lines.
| Variable | Value | Default | What it does |
|---|---|---|---|
AGENT_NAME | string | the host name | The name this agent reports. The agent's id is derived from it, so changing the name changes the id. |
AGENT_TAGS | comma-separated list | empty | Tags reported with the agent's status. |
AGENT_IP_ADDRESSES | comma-separated list | empty | The addresses this agent reports. Not validated as IP addresses. |
AGENT_IPS | comma-separated list | empty | Older name for the same thing, used only when AGENT_IP_ADDRESSES is unset. |
AGENT_HEARTBEAT_SECS | integer seconds | 30 | How often the agent reports status. An unparseable value falls back to 30. |
AGENT_ID | — | — | Nothing. Setting it logs AGENT_ID is ignored; agent_id is derived from agent_name. |
HOSTNAME | string | the host name | Used as the agent name when AGENT_NAME is unset, and as the host field on CEF and syslog lines. |
Smart firewall
| Variable | Value | Default | What it does |
|---|---|---|---|
AMYGDALA_PREBLOCK | comma-separated kind=value, optionally allow:kind=value | empty | Seeds block and allow rules at startup. kind is one of ja4, ja4s, ja4t, ja4ts, ja4l, ja4ls, ja4h, ja4x, ja4ssh; entries with no =, or an unknown kind, are skipped with a warning. Setting it also starts the bridge even with firewall.amygdala.enabled false. |
AMYGDALA_REACTOR_SCOPE | src_tuple, src_ip, src_ip:<prefix> | src_ip with prefix 32 | What a block covers: the full source tuple, the whole source address, or a prefix of it. An unknown value warns and leaves the default; an unparseable prefix falls back to 32. Setting it also starts the bridge. |
AMYGDALA_SWEEP_INTERVAL_MS | integer milliseconds | 30000 | How often expired entries are evicted. Values below 1000 are raised to 1000 with a warning; an unparseable value falls back to 30000. |
DANGEROUS_AFXDP_OWN_MODE | exactly 1 | unset | Permits an afxdp_attach_mode other than shared. Without it, any other value is refused and forced back to shared. Devmap forwarding can rewrite the source MAC, so use it only on isolated interfaces. |
Logging and tracing
| Variable | Value | Default | What it does |
|---|---|---|---|
RUST_LOG | a bare level | info | Log level, consulted last - see Exception 1. |
SYNAPSE_LOG_FORMAT | json (any case) | text | Writes app.log, error.log and the console line as JSON. access.log is unaffected - it is already JSON. |
SYNAPSE_ACCESS_LOG_SAMPLE_RATE | number from 0 to 1 | telemetry.access_log.sample_rate | Fraction of access-log events kept. A value outside the range, or an unparseable one, is ignored. |
OpenTelemetry
The endpoint, header and compression variables describe one OTLP destination, the
environment's. It comes into being as soon as any of the four endpoint variables is set,
and sits alongside the destination telemetry.otlp.endpoint names and the platform's.
With only a per-signal endpoint set, the signals without one are not exported there at
all.
The order of those three matters for everything below: the environment's, then
telemetry.otlp.endpoint, then the platform. A URL two of them name is pushed to once,
as the earlier, carrying the headers of both — the earlier's winning a clash.
The two timeouts are not so confined, and their rows say what each reaches.
The last five are not about a destination at all. They shape the resource, the sampler and the metric interval, and are read whether or not any OTLP endpoint is set — though with no destination configured only the sampler still acts, since the file sink carries no resource and the metric interval times a reader that exists only per destination.
OTEL_EXPORTER_OTLP_HEADERS is attached to the first destination only. The one
configured under telemetry: gets telemetry.otlp.headers and nothing else, and the
platform gets its own key. Nothing fails when a token is set in the wrong place; the
destination that needed it simply receives requests without it. Synapse does warn at
startup for the one case it can see — headers set in the environment while the
environment names no endpoint to send them to.
Through 0.8.7 these variables reached every exporter, so a configuration that relied on one token covering all three destinations stops working on 0.8.8. See Upgrade Notes.
| Variable | Value | Default | What it does |
|---|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT | base URL | unset | Adds an OTLP destination, the first of the three. The signal's path (/v1/logs, /v1/metrics, /v1/traces) is appended, replacing one the URL already ends in. An empty value counts as unset. |
OTEL_EXPORTER_OTLP_{LOGS,METRICS,TRACES}_ENDPOINT | URL | unset | Sends that one signal to this URL as given, in place of the general endpoint. |
OTEL_EXPORTER_OTLP_HEADERS | comma-separated k=v | none | Headers for the first destination only, which is where an API token for it goes. Values are percent-decoded; an entry with no =, an empty name or an empty value is skipped. |
OTEL_EXPORTER_OTLP_{LOGS,METRICS,TRACES}_HEADERS | comma-separated k=v | none | That signal's headers, read in place of the general variable rather than alongside it. |
OTEL_EXPORTER_OTLP_COMPRESSION | gzip, zstd, none | unset, which compresses nothing | Compression for the first destination only; the other two are sent uncompressed either way. Any other value warns at startup and is treated as none. |
OTEL_EXPORTER_OTLP_{LOGS,METRICS,TRACES}_COMPRESSION | gzip, zstd, none | unset | That signal's compression, in place of the general variable — including when it is set to nothing. |
OTEL_EXPORTER_OTLP_TIMEOUT | integer milliseconds | 10000 | Two timeouts at once. It is the per-request timeout of the environment's destination — and, because synapse leaves the SDK's own timeout unset, the deadline the SDK gives the export retry loop at every destination. An unparseable value falls back to 10 s for both. |
OTEL_EXPORTER_OTLP_{LOGS,METRICS,TRACES}_TIMEOUT | integer milliseconds | unset | That signal's retry deadline, at every destination, read in place of the general variable; an unparseable value falls through to it. No destination's per-request timeout changes. |
OTEL_METRIC_EXPORT_INTERVAL | integer milliseconds | 30000 | How often metrics are exported, to every destination. |
OTEL_SERVICE_NAME | string | synapse | The reported service name. telemetry.resource.service_name wins over it. |
OTEL_RESOURCE_ATTRIBUTES | comma-separated k=v, percent-encoded | none | Extra resource attributes. A bad escape discards the whole variable with a warning; telemetry.resource.attributes is applied after and wins. |
OTEL_TRACES_SAMPLER | always_on, always_off, traceidratio, parentbased_always_on, parentbased_always_off, parentbased_traceidratio | parentbased_traceidratio | Trace sampler. An unknown value warns and uses the default. |
OTEL_TRACES_SAMPLER_ARG | number from 0 to 1 | telemetry.tracing.sample_rate | The ratio for the traceidratio samplers. Ignored by the always_* ones. |
Proxy and ACME
| Variable | Value | Default | What it does |
|---|---|---|---|
REDIS_URL | Redis URL | redis://127.0.0.1:6379 | Several ACME storage paths read this variable directly rather than proxy.redis.url, so a Redis URL set only in the YAML leaves those paths on localhost. Set the variable too, or set proxy.acme.redis_url. |
SYNAPSE_NODE_IP | IP address | the local socket address | The node address recorded as the destination for post-TLS IDS events. Intended for the Kubernetes downward API (status.hostIP). An unparseable value is ignored. |
Windows
| Variable | Value | Default | What it does |
|---|---|---|---|
SYNAPSE_WINDOWS_IFINDEX | positive integer | unset | Interface index for Windows IDS capture. Wins over SYNAPSE_WINDOWS_IFACE. |
SYNAPSE_WINDOWS_IFACE | interface name or number | unset | Interface for Windows IDS capture, used when the index is unset or zero. |
Diagnostics
Off unless set. The first three are switches: any value turns them on, including an empty one.
| Variable | Value | What it does |
|---|---|---|
SYNAPSE_BPF_PROBE | any value | Runs map-creation probes at startup and logs the results. |
SYNAPSE_LIBBPF_VERBOSE | any value | Sends libbpf and verifier output to the log. |
SYNAPSE_IDS_SELFTEST | any value | Runs an IDS-to-firewall decision self-test at startup. |
SYNAPSE_FP_BUS_WRITER | 1 or true | Starts a second, bus-driven fingerprint writer alongside the normal one. A flow can then be logged twice. |
THALAMUS_TRACE_ENABLED | 1, true, yes or on | Traces IDS rule evaluation per packet. The four variables below apply only when it is on. |
THALAMUS_TRACE_DIR | path (default ./trace) | Where the trace files are written. Created if missing. |
THALAMUS_TRACE_SIDS | comma-separated rule ids | Trace only these rules. Unset traces all of them. |
THALAMUS_TRACE_FLOWS | comma-separated ip:port-ip:port | Trace only these flows. |
THALAMUS_TRACE_PREVIEW_BYTES | positive integer (default 96) | How many payload bytes each trace entry records. |
Set by synapse itself
| Variable | What it does |
|---|---|
ECDYSIS_RUST_UPGRADE | Marks a process as the child of a graceful binary upgrade. Set by the upgrade machinery; not an operator setting. |
Custom builds only
SYNAPSE_EGRESS_IDS and SYNAPSE_EGRESS_IFACES configure the egress IDS, which is in no
published package - see What your package contains.
Documented elsewhere, never read
BPF_STATS_ENABLED, TCP_FINGERPRINT_ENABLED and TOKIO_WORKER_THREADS appear in
synapse's in-repo docs/ENV_VARS.md. No Rust file reads any of them. The thread count is
worker_threads in the YAML, or WORKER_THREADS.
What your package contains
Not every subsystem is in every package. A configuration key for one that is missing still parses; what happens then depends on the key, and it is not consistent.
synapse-agentsynapse-proxy | synapse-agent-mlsynapse-proxy-ml | Legacy agent (not published) | Windows | |
|---|---|---|---|---|
| L7 proxy: TLS termination, upstreams, ACME, WAF, captcha | yes | yes | no | no |
| eBPF/XDP datapath and XDP capture | yes | yes | no | no |
AF_PACKET capture | yes | yes | yes | no |
| NDIS capture | - | - | - | yes |
Smart firewall (firewall.amygdala) | yes | yes | iptables/nftables only | no |
| Inline JA4 ClientHello enforcement | yes | yes | no | no |
ML scoring (ml.*) | no | yes | no | no |
Graceful binary upgrade (--upgrade) | yes | yes | no | no |
| Egress IDS | no | no | no | no |
| PF_RING capture | no | no | no | no |
- The
-mlpackages are the only ones with ML scoring. They are published separately because the ONNX runtime needs glibc 2.38 or newer; the plain packages have a glibc 2.31 floor and the-mlpackages 2.39. Both come inx86_64andarm64. - The agent and proxy packages carry the same binary. They differ in the unit file
and in the configuration they ship. The binary being identical, a
synapse-agentinstall can run a proxy: setmode: proxyinconfig.yaml. The two packages conflict with each other, so one host runs one of them. - On the
synapse-proxypackage,mode:inconfig.yamldoes nothing. Its unit file carriesEnvironment="MODE=proxy", andMODEis an environment variable, so it beats the file - which still saysmode: "agent", because both packages ship the sameconfig.yaml. This is the rule at the top of the page in its most consequential form: if you need that host to stop proxying, edit the unit or overrideMODE, not the YAML. - The legacy agent is the static build for old kernels and old glibc: agent only,
AF_PACKETcapture,iptables/nftablesenforcement. It is not among the published release artefacts - it is built and tested per change, so ask support for a build if you need it. The column is here because the keys behave the same way on it. - The Windows build is agent only. It captures fingerprints through NDIS; it has no proxy, no eBPF, and no smart-firewall enforcement.
- Egress IDS and PF_RING capture are in no published package. Both need a build made with non-default options.
Keys that are accepted and do nothing
| Key | Needs | Without it |
|---|---|---|
ml.* | an -ml package, on Linux | named in a startup warning: Unused config options in <file>: ml. No scoring happens |
CLASSIFIER_* | an -ml package, on Linux | accepted, no scoring, nothing logged |
capture.mode: pf_ring | a custom build | accepted, no capture backend starts, nothing logged |
firewall.amygdala.* | Linux; a Windows build with the NDIS bridge, which no published Windows package is | accepted, no enforcement, nothing logged |
ids.egress_ifaces, SYNAPSE_EGRESS_IDS | a custom build | accepted, no egress IDS, nothing logged |
network.interfaces with the ids_egress feature | a custom build | synapse refuses to start: network.interfaces enables 'ids_egress', but this build has no egress IDS |
Three outcomes, then, and it is worth knowing which one you are in. The last row refuses
to start. The ml.* row is named in the Unused config options warning synapse logs for
any section its build does not recognise - which is also how you find a typo in a section
name. The rest are silent, and the only signal is the absence of a log line saying the
subsystem started, so check the log on first start rather than assuming the key took
effect.
The CLASSIFIER_* variables are silent where ml.* is not, because the unused-key
warning comes from parsing the file: nothing inspects the environment for variables that
this build cannot use.
capture.mode: auto never selects PF_RING, so it is safe everywhere: it resolves to XDP
when the eBPF programs load, AF_PACKET when they do not, and NDIS on Windows.