Skip to content

Engine

The EnforceGate vX engine processes requests received from local and remote connectors. It is the central intelligence component where all policy evaluation occurs. The engine maintains highly optimised in-memory match structures to scan request attributes (URI, SNI, user-agent, client IP, …) and persists compiled ACLs in a local DuckDB database.

The engine binary is /usr/local/bin/enforcegate-engine inside the standalone image. It runs as the unprivileged enforcegate user. Its long-running siblings are Squid (/usr/sbin/squid) and the Squid connector helper (/usr/local/bin/enforcegate-squid-connector); the connector is launched by Squid as a url_rewrite_program child, not by the container init.

When bump-mode SSL inspection is active, the engine also produces encrypted verdict-redirect payloads using the shared secret from the enforcegate-shared volume. The captive portal verifies and decrypts them on the visitor's behalf — see captive portal.

Command-line flags

Flag Effect
-h, --help Print help and exit 2.
-v, --version Print engine version (e.g. 2026.30.0.328) and exit 2.
-c <path>, --conf <path> Use <path> as the engine config file. Default /etc/enforcegate/engine.conf.
-D, --daemon Double-fork detach plus close stdin / stdout / stderr. For bare-metal / systemd installs.
--pidfile <path> Override the default PID file (/run/enforcegate/engine.pid). Daemon mode flocks this file exclusively.
--loglevel <level> Override the configured logging level for this run. One of emergency, alert, critical, error, warning, notice, info, debug.
--logtype <type> Override the configured logger type (file / console / syslog).

Configuration file

The engine reads its configuration from a TOML file. The default location is /etc/enforcegate/engine.conf. The shipped default ships with eight sections — [global], [connectors], [license], [database], [policy], [learning], [captive_portal], [aaa] — plus an optional [logging] section. Each is described below.

Operator-tunable keys can be overridden via the bundle's .env file: matching ENFORCEGATE_* environment variables take precedence over the persisted conf at every boot, so .env wins. See your image's documentation for the variable-name to conf-key mapping.

[global]

Engine-wide tunables that don't fit a more specific section.

Name Type Description Default
threads int Size of the connector listener pool — threads serving the Defendr wire from connected enforcegate-squid-connector (and future TLS-proxy / ICAP) sessions. Connector sessions multiplex onto this pool; a busy engine can carry many more sessions than threads. The Control API HTTPS pool is independent and fixed at 4 — see show system threads for the runtime split. 4

[connectors]

The [connectors] table contains one sub-table per declared connector. Each sub-table ([connectors.<name>]) describes one allowed peer. The engine accepts an incoming Defendr session only from a source IP that matches a configured [connectors.<name>].ip, after the connector authenticates with the shared key.

Name Type Description Default
net string Transport protocol. "tcp" (TCP/TLS) or "socket" (UNIX-domain socket — has known issues in current builds; prefer tcp for production). "tcp"
ip string Bind address for the engine's Defendr listener (use 0.0.0.0 to listen on all interfaces). 127.0.0.1
port int TCP port for the Defendr listener. 11224
key string Pre-shared authentication key. Up to 128 characters; longer is truncated. (required)

ip

In the standalone bundle the engine binds to 127.0.0.1 because the connector runs in the same container. Multi-image or distributed deployments override to 0.0.0.0.

key

Mandatory pre-shared key

EnforceGate enforces mutual peer authentication between the engine and the connectors. The key attribute is mandatory and must be identical on both sides — the engine's [connectors.<name>].key and the connector's [engine.<name>].key. A mismatch is a silent connect failure; show neighbor summary shows no connector, and no log line is emitted on the missing-key path.

In the standalone image, the generate-engine-key boot one-shot writes a random 32-character key into both files on first boot, so for the shipped deployment no manual configuration is required.

[license] — required for first boot

The license configuration. The engine refuses to start if any of serial, username, password is empty.

Name Type Description Default
serial string Per-deployment activation serial issued by Exosys. 22-char canonical XXXXX-XXXX-XXXX-XXXX-X format — see licensing. (required, no default)
username string Control-server account name authorised to bind this serial. (required)
password string Control-server account password. (required)
enforce_permissions bool Hard-fail the engine if license-file permissions are too permissive. Production deployments set true. false (dev) / true (prod images)

Override every credential field for test/prod tiers via .env (ENGINE_LICENSE_SERIAL, ENGINE_LICENSE_USERNAME, ENGINE_LICENSE_PASSWORD). The boot card prints a [ WARN ] line for the serial and username overrides; the password override is silenced (length-only) so credentials don't leak into docker logs.

[database]

Where the engine's DuckDB store lives.

Name Type Description Default
database_file path DuckDB file holding the live policy DB and engine session state. /var/lib/enforcegate/engine.db

[policy]

Policy source and snapshot configuration.

Name Type Description Default
default_action string Engine-synthesised no-match verdict — what the engine returns when no rule matches a request. One of "permit" (request proceeds), "deny" (block page), "warn" (warn page with proceed-anyway CTA), "aup" (Acceptable Use Policy gate). Replaces the historical pattern of shipping a catch-all .policy rule (retired in 2026.32.0); the no-match verdict no longer occupies a rule id, so it can never shadow rules in shared_path. On the captive-portal verdict pages and in show policy match, the synthesised verdict reports rule name default-permit / default-deny / default-warn / default-aup. "permit"
path path Directory the engine scans recursively for *.policy files on reload. Operator-authored rules. Loaded first; gets the lower rule ids; wins precedence on conflict (the engine's lowest-rule-id-wins rule). A parse error here fails the reload loudly. /etc/enforcegate/rules.d/
shared_path path Optional second directory the engine scans for *.policy files. Machine-generated rules handed off from the toolbox sidecar over a shared volume — see Toolbox. Loaded after path, so its rules get higher ids and lose the lowest-id-wins match — a generated policy can propose but never silently override an operator deny. A parse error here is logged as a Warning and the file is skipped; the rest of the load proceeds. Set to "" to disable. Snapshots and request policy rollback cover path only. /etc/enforcegate-shared/rules.d/
backup_path path Where pre-apply snapshots are written. Each snapshot is a timestamped subdirectory mirroring path/. /var/lib/enforcegate/policy-backups/
backup_depth int Retain the N most-recent snapshots; prune older ones after every successful reload. 10
domain_backend string In-memory backend for match-domain-list: data. "auto" (default) — engine picks at policy load based on entry count; "hashset" — force the smaller / faster backend; "sorted-arena" — force the larger-scale backend. The default is fine for almost every deployment. "auto"
domain_backend_auto_threshold int When domain_backend = "auto", the entry count above which the engine selects sorted-arena. Lower thresholds bias toward the larger-scale backend earlier; higher thresholds keep the faster backend longer. 50000000
git_mode string Tri-state opt-in for per-rule audit + history tracking via a git repository in [policy].path. "auto" (default) — enable if [policy].path/.git/ exists, fall back to snapshot-only otherwise; "force-on" — require .git/ to be present (engine logs Critical if it is not); "force-off" — never enable, ignore any .git/ directory present. See Policy audit and history for the operator workflow. "auto"
time_window_tz string Timezone the engine evaluates time-window: attributes against. "local" — engine host's local wall clock; reads /etc/localtime. Keep NTP in sync — a wrong system clock makes time-windows fire at the wrong times. "utc" — interpret HH:MM against UTC; the right choice for deployments with operators in multiple timezones. Confirm what "local" means on the box with show clock. "local"

See policy rollback for the snapshot workflow and policy audit for the optional git-tracked history layer that sits on top of snapshots. The single source of truth for the live policy is the engine's DuckDB store at [database].database_file — see Inspecting the live policy for the operator-side ways to read it.

The active domain backend is reported by show database status and the detailed show system uri-engine verb — operators sizing memory against real numbers reach for the latter.

[learning]

Learning-mode (V1 shipped 2026-05-26) configuration. If the section is absent or database_file is unset, the learning subsystem stays silently disabled and request learning … calls return "learning subsystem not initialised".

Name Type Description Default
database_file path Separate DuckDB file for learning sessions. Wipe to reset learning without touching policy. /var/lib/enforcegate/learning.db

See learning mode for the operator workflow.

[captive_portal]

Captive portal v=1 integration. The engine produces encrypted verdict-redirect URLs with the shared secret; the squid connector relays the URL to client browsers as HTTP 301.

Name Type Description Default
base_url URL Public URL clients reach the portal at. Substituted into the redirect URL the engine builds. Override at deployment time via ENFORCEGATE_CAPTIVE_BASE_URL. http://127.0.0.1/
secret base64 Shared secret with the portal. Base64-encoded; the same string must be configured on both sides. Generate fresh with openssl rand -base64 32. (one of secret / secret_file required)
secret_file path Path to a file holding the base64-encoded secret. Use for chmod 0400 isolation from the (potentially world-readable) main config. /etc/enforcegate-shared/captive-portal-secret (standalone)
ack_token_max_age_s int How long an ack_token is valid after the engine mints it. The visitor must click "Proceed" on the warn / aup page within this window. 300 (5 minutes)
ack_session_duration_s int After a successful ack, the same (rule_name, client_ip) pair won't re-trigger the warn / aup page for this duration. 3600 (1 hour)

In the standalone bundle, the generate-engine-key boot one-shot mints the shared secret on first boot and writes it to the shared volume — no operator paste, no two-place drift.

Spec: see captive portal.

[aaa]

Authentication, authorisation and first-boot bootstrap.

Name Type Description Default
passwd path The user database. Same shape as /etc/passwd but with auth-algorithm + salt + hash columns. /etc/enforcegate/passwd
auto_bootstrap bool If true, the engine on first boot (when passwd is empty) generates a random admin password, writes it to bootstrap_credential_file, and logs the cleartext once at [critical]. Production Docker bundles set this to true. false
bootstrap_credential_file path Where the bootstrap credential JSON is written. The installer's docker logs consumer surfaces the password in a dialog. /etc/enforcegate/aaa-bootstrap-credential.json
bootstrap_credential_ttl_seconds int How long the bootstrap credential file loiters if no admin login captures it. 86400 (24 hours)

See Privilege model below for the four levels and what each can do.

[control]

The Control API is the engine's HTTPS REST interface — egctl talks to it via docker exec and the captive portal sidecar reaches it server-side over the compose network for the /api/captive/ack round-trip.

Name Type Description Default
ip string IP address on which the Control API listens. 0.0.0.0
port integer TCP port for the Control API. 11225
key string Path to the private key file (TLS). conf/ssl/key.pem
cert string Path to the X.509 server certificate. conf/ssl/cert.pem
dhparams string Path to the Diffie-Hellman parameters file (for PFS). conf/ssl/dhparams.pem

ip

The default 0.0.0.0 inside the container makes :11225 reachable from the captive-portal sidecar across the compose network. The shipped docker-compose.yml does not publish :11225 to the host. For single-image deployments without a portal sidecar, lock down to 127.0.0.1 via ENGINE_CONTROL_API_IP=127.0.0.1 in .env.

port

The default 11225 matches egctl.conf's default and the captive portal sidecar's ENGINE_INTERNAL_URL.

TLS material

The Control API serves a self-signed X.509 certificate generated by the generate-ssl-certs boot one-shot on first boot. The portal sidecar verifies with verify=False because the TLS hop is loopback-equivalent inside the compose bridge; trust comes from the compose-network boundary, not from PKI verification. For egctl from outside the container, configure cert in egctl.conf to pin the certificate the engine serves.

[logging] (optional)

Logger configuration. Absent → file logger with default /var/log/enforcegate/engine.log and error level.

Name Type Description Default
type string Where logs go. "file", "console" (writes to stderr — captured by docker logs), or "syslog" (POSIX LOG_DAEMON, identity enforcegate-engine). "file"
file path Log file path when type = "file". /var/log/enforcegate/engine.log
level string Threshold. One of critical, error, warn, info, debug. error
Level When What you see
critical Fatal failures License-init exit, ASan abort, segfault
error Recoverable but bad Policy parse failure, DB connection lost
warning Suspicious Permission warnings, deprecated config keys
notice Normal operations to notice Engine startup, policy reload success, connector connect / disconnect, SIGTERM handling
info Normal operations (default) As above with more detail
debug Verbose tracing Per-request URL + verdict, hyperscan match events, AAA decisions

Debug rates the engine significantly

Per-request logging at debug level meaningfully impacts throughput. Don't leave debug on in production — use it for incident diagnosis only.

Foreground debug run from the host
eghost shell                                                       # → /bin/sh in the enforcegate container
/usr/local/bin/enforcegate-engine --loglevel debug --logtype console

Do not use --logtype console for the supervised engine

The engine's libc stdio is block-buffered when stdout is not a TTY, which starves docker logs. The shipped boot configuration uses the file logger plus a tail+awk pipeline to stream lines in real time. Only override the log type for one-off foreground instances launched interactively.

Diagnostic file

A top-level key (no enclosing section) that historically lived outside [license]:

Name Type Description Default
diagnostic_file string Absolute path inside the container where APM diagnostics land. /var/lib/enforcegate/apm-last-error.json

Override via ENGINE_APM_DIAGNOSTIC_FILE only for tmpfs deployments, systemd-managed paths, or read-only-rootfs containers where a different writable path is required. The reason-string set is enumerated in the troubleshooting page.

Privilege model

EnforceGate vX uses a four-level privilege model for operator accounts. Each Control API verb checks the requesting user's privilege against a minimum threshold; verbs that mutate state require higher privileges than verbs that read state.

The four levels

Level Numeric Typical bearer
Disabled 0 The account exists in /etc/enforcegate/passwd but cannot authenticate. Used to lock an account out without deleting it.
Monitoring user 1 Read-only analyst. Sees the engine state, neighbour table, policy backups, user list, learning sessions, and the verdict of show policy match <url> (without the matched regex pattern).
Standard user 2 Everything monitoring can do, plus request neighbor teardown. Operational role for someone who manages connector sessions but not policy or accounts.
Service account 3 Capability-scoped, not level-scoped. Can call request policy reload and the show status family — and nothing else. No user / license / neighbor / reboot ops. Intended bearer is automation that only needs to refresh policy (e.g. the toolbox sidecar's reload helper). Created via the R) Service account (policy-reload only) entry in request user add. New in 2026.33.0.
Administrator 10 Full policy and user management. Loads, reloads and rolls back policies; adds, removes and re-passwords users; sees the matched regex pattern from show policy match <url>.
Super Administrator 11 Same operational surface as Administrator, plus the unique ability to create, re-password, or remove Administrator-level accounts. The must-outrank invariant (see below) is what makes this distinct: only a Super Administrator outranks an Administrator. The shipped admin account is at this level.

Verb privilege thresholds

A representative sample of which egctl verbs each level can call:

Verb Minimum privilege
show status Monitoring (1)
show version Monitoring (1)
show neighbor summary Monitoring (1)
show neighbor detail Monitoring (1)
show users Monitoring (1)
show policy backups Monitoring (1)
show learning sessions Monitoring (1)
show policy match <url> (verdict only) Monitoring (1)
show policy match <url> (with matched regex) Administrator (10)
request neighbor teardown Standard (2)
request policy reload Administrator (10) — also callable by a Service account (3)
request policy rollback Administrator (10)
request engine restart Administrator (10)
request user add Administrator (10) — and must outrank the new user
request user passwd <self> Monitoring (1)
request user passwd <other> Administrator (10) — and must outrank the target
request user remove <other> Administrator (10) — and must outrank the target; refuses self-remove
request learning create/start/stop/delete/analyze Administrator (10)

Operational guidance

  • Principle of least privilege. Give an analyst Monitoring (1), not Administrator (10), unless they need to mutate state.
  • The request user * verbs enforce a must-outrank invariant: an Administrator cannot edit a Super-Administrator, and no user can remove themselves.
  • The shipped default account is admin / enforcegate-changeme at Super Administrator (11). The first thing to do on a fresh deployment is change that password — either via request user passwd admin (after auth) or by removing the account entirely once another Super Administrator exists.
  • The bootstrap admin is provisioned at Super Administrator (11) so it can mint additional Administrator-level accounts via request user add directly out of the box — the must-outrank invariant requires the requestor to outrank any account it creates.

First-boot bootstrap

With [aaa].auto_bootstrap = true in engine.conf, the engine on first boot (when passwd is empty) generates a random admin password, writes the cleartext to bootstrap_credential_file, and logs it once at [critical]. The appliance installer surfaces the password in the setup wizard; on Docker bundles operators read it from the boot log.

File paths summary

For quick reference — what the engine reads and writes when configured with the shipped defaults:

Path Role
/etc/enforcegate/engine.conf The config file documented above.
/etc/enforcegate/passwd User database (Administrator + monitoring + standard accounts).
/etc/enforcegate/license/cs.key.pub Control-server public key (shipped).
/etc/enforcegate/license/license.key Per-host private key (first-boot-generated).
/etc/enforcegate/license/license.key.pub Per-host public key.
/etc/enforcegate/license/license.lic Signed license blob.
/etc/enforcegate/rules.d/*.policy Operator-authored policy files.
/etc/enforcegate/ssl/{cert,key,dhparams}.pem Defendr listener + Control API TLS material.
/var/lib/enforcegate/engine.db Live compiled policy DuckDB.
/var/lib/enforcegate/learning.db Learning sessions + captured URIs.
/var/lib/enforcegate/policy-backups/<timestamp>/ Pre-reload snapshots of rules.d/.
/var/lib/enforcegate/apm-last-error.json Structured APM diagnostic written on fatal license-init exits.
/var/lib/enforcegate/.time-ratchet Engine-only internal state file (do not edit or delete).
/var/log/enforcegate/engine.log File-logger sink when [logging].type = "file".
/run/enforcegate/engine.pid Daemon-mode PID file.
/run/enforcegate/ (runtime sockets) UNIX-domain sockets used internally when the connector runs on the same host.