-
Notifications
You must be signed in to change notification settings - Fork 0
Configuration
A complete reference for every environment variable and runtime setting OPNGMS understands. Most variables live in your .env file (see Installation for the step-by-step); a handful of operational settings are configured in-app rather than via the environment. For upgrades and key rotation see Upgrading; for the log lake see Log-Lake; for security hardening see Security.
- How to read this page
- Database & roles
- Secrets
- Image & version
- Timezone
- TLS & frontend exposure
- Broker & sessions
- Login & MFA
- Worker cadences & cron
- Boot-time tuning
- Runtime settings (in-app)
- Live config push
- Catalog distribution
- Log lake
- Where configuration lives
-
Variable — the exact environment-variable name (read from
.env, the Composeenvironment:blocks, orenv_file: .env). -
Default — the value applied when the variable is unset.
—means there is no default; the variable is required. - Required? — whether the app refuses to start (or misbehaves) without an explicit value.
- Variables backed by the application settings model are sourced from
backend/app/core/config.py. Variables consumed only by Docker Compose or the frontend image are noted as such.
Note: Pydantic settings names are lowercase in code (e.g.
database_url); the environment-variable form is uppercase (DATABASE_URL). Both refer to the same setting — set the uppercase form in.env.
OPNGMS uses two database identities so Postgres Row-Level Security can enforce tenant isolation: a non-superuser opngms_app role for the API (RLS-enforced) and the database owner for migrations and the worker (RLS-exempt, trusted backend only). This means there are two password pairs that must match — set them once and keep each pair aligned, or the app cannot connect.
| Variable | Default | Required? | Description |
|---|---|---|---|
POSTGRES_USER |
opngms |
Yes | Owner/superuser the TimescaleDB container creates on first boot. Consumed by the db service. |
POSTGRES_PASSWORD |
— | Yes | Password for POSTGRES_USER, created on first boot. Must equal the password embedded in ADMIN_DATABASE_URL. |
POSTGRES_DB |
opngms |
Yes | Database name created on first boot. |
DATABASE_URL |
— | Yes | Async DSN the API uses, connecting as the non-superuser opngms_app role (RLS enforced). Form: postgresql+asyncpg://opngms_app:<pw>@db:5432/opngms. Its embedded password must equal APP_ROLE_PASSWORD. |
APP_ROLE_PASSWORD |
— | Yes | Password the migrate job uses to CREATE/ALTER the opngms_app role (interpolated into SQL DDL — no single quotes). Must equal the password embedded in DATABASE_URL. |
ADMIN_DATABASE_URL |
unset | Yes (prod) | Async DSN the worker and migrations use, connecting as the owner (DDL + fleet-wide writes; bypasses RLS). Its embedded password must equal POSTGRES_PASSWORD. Compose also exports this value as ALEMBIC_DATABASE_URL to the migrate service. |
TEST_DATABASE_URL |
unset | No | Optional override used only by the test suite; never set in production. |
The two pairs that must match:
| Password in… | …must equal | Role it authenticates |
|---|---|---|
DATABASE_URL |
APP_ROLE_PASSWORD |
Non-superuser opngms_app — API, RLS-enforced |
ADMIN_DATABASE_URL |
POSTGRES_PASSWORD |
DB owner — migrations + worker, RLS-exempt |
See the matching table and worked example in Installation; this page does not repeat the example.
Generate fresh values for these on every install — never reuse the .env.example placeholders. The API fails closed at startup if any guarded secret still contains the literal substring change-me (the placeholder shipped in .env.example).
| Variable | Default | Required? | Description |
|---|---|---|---|
SESSION_SECRET |
— | Yes | Server-side session signing key. Generate with python -c "import secrets; print(secrets.token_urlsafe(48))". Rotating it invalidates all active sessions. |
MASTER_KEY |
— | Yes | Fernet key (urlsafe-base64) encrypting all secrets at rest — device API credentials, config snapshots, MFA TOTP secrets, the SMTP password, and the syslog CA key. Generate with python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())". Losing it makes all stored secrets undecryptable. Rotation procedure: see Upgrading. |
MASTER_KEY_OLD_KEYS |
"" (empty) |
No | Comma-separated retired Fernet keys, kept for decryption only during a key rotation. Leave empty on a fresh install; see the rotation procedure in Upgrading. |
assert_secure_secrets() (in backend/app/core/config.py) rejects startup if any of these still contain change-me:
POSTGRES_PASSWORD-
DATABASE_URL(its embedded password) APP_ROLE_PASSWORD-
ADMIN_DATABASE_URL(its embedded password) SESSION_SECRETMASTER_KEY
Real values — including dev, test, and CI — never contain the placeholder, so the guard is a no-op for them. See Security for hardening guidance on these secrets.
| Variable | Default | Required? | Description |
|---|---|---|---|
OPNGMS_VERSION |
latest |
No | Pins the ghcr.io/l0rdg3x/opngms-{backend,frontend} image tag the Compose files pull. latest resolves to the newest semver release (images are published only from release tags, never from dev commits). Pin a specific tag (e.g. 0.1.0) for reproducible deploys. See Upgrading for the bump-and-redeploy flow. |
| Variable | Default | Required? | Description |
|---|---|---|---|
TZ |
UTC |
No | IANA timezone name (e.g. Europe/Rome, America/New_York) applied to the db, api, worker, and frontend containers so logs read in local time. All data is stored in UTC (timestamptz), and scheduled-report hours are interpreted in UTC by design — changing TZ does not shift when a report fires, only how timestamps appear in container logs. |
OPNGMS supports four TLS models, selected by which Compose overlay you stack on top of docker-compose.prod.yml (full walkthrough in Installation). Each variable below applies to specific models.
| Variable | Default | Required? | Used by | Description |
|---|---|---|---|---|
FRONTEND_BIND |
127.0.0.1 |
No | Model 1 (base) | Host bind address of the plain-HTTP frontend port. Keep on localhost unless an upstream proxy on another machine needs 0.0.0.0 — and only if something terminates TLS in front of it. |
FRONTEND_HTTP_PORT |
8080 |
No | Model 1 (base) | Host port the base frontend listens on (mapped to container port 80). Your upstream proxy forwards here. |
TLS_MODE |
off |
No | Frontend image | Frontend image switch: off = terminate TLS upstream (base); the built-in-TLS overlay flips this to builtin. Set by the Compose overlays — you normally do not set it by hand. |
SERVER_NAME |
_ (Compose) / opngms.example.com (.env.example) |
No | Models 1 & 2 | Your hostname, used as nginx server_name for the built-in TLS server; informational otherwise. |
CERT_DIR |
./certs |
No | Model 2 | Host directory mounted read-only into nginx containing fullchain.pem + privkey.pem. If absent, a self-signed cert is generated at startup so the container still boots. |
HTTP_PORT |
80 |
No | Models 2, 3a, 3b | Published HTTP host port (used for the HTTP→HTTPS redirect and ACME HTTP-01 challenge). |
HTTPS_PORT |
443 |
No | Models 2, 3a, 3b | Published HTTPS host port. |
DOMAIN |
opngms.example.com |
Yes (3a/3b) | Models 3a (Caddy), 3b (Traefik) | Public hostname Let's Encrypt issues a certificate for. The Caddy/Traefik overlays require it (${DOMAIN:?…}) — DNS must resolve to this host and ports 80/443 must be internet-reachable. |
ACME_EMAIL |
you@example.com |
Yes (3a/3b) | Models 3a, 3b | ACME account email for Let's Encrypt. Required by the Caddy/Traefik overlays (${ACME_EMAIL:?…}). |
Note: HTTPS is mandatory in production — browsers drop
Securesession cookies over plain HTTP on a real domain, which breaks login. With Model 1, your upstream proxy must forwardX-Forwarded-Proto: https. See Installation for per-model setup and Security for TLS hardening.
| Variable | Default | Required? | Description |
|---|---|---|---|
REDIS_URL |
redis://localhost:6379 (code) / redis://redis:6379 (.env.example) |
No | Redis connection URL used as the broker/cache. The bundled redis service is reachable at redis://redis:6379 inside the Compose network. |
SESSION_TTL_HOURS |
12 |
No | Absolute session lifetime — a session is invalid after this many hours regardless of activity. |
SESSION_IDLE_MINUTES |
120 |
No | Sliding/idle timeout — a session expires after this many minutes of inactivity, alongside the absolute SESSION_TTL_HOURS. |
CORS_ALLOW_ORIGINS |
"" (empty) |
No | Comma-separated list of allowed CORS origins. Empty = CORS disabled (same-origin only), which is correct for the standard single-origin deployment. Set only if you serve the SPA from a different origin than the API. |
| Variable | Default | Required? | Description |
|---|---|---|---|
LOGIN_MAX_ATTEMPTS |
5 |
No | Failed-login attempts allowed before lockout. |
LOGIN_LOCKOUT_WINDOW_SECONDS |
900 |
No | Lockout window in seconds (default 15 minutes) over which failed attempts are counted. |
MFA_PENDING_TTL_MINUTES |
5 |
No | Lifetime of the short-lived mfa_pending challenge — the window after a correct password during which the TOTP code must be submitted (POST /api/login/mfa). |
The MFA enforcement policy (off / all / privileged) is set in-app under Two-factor auth, not via environment. See Security.
These tune the background worker's polling and scheduled jobs. Defaults are sensible for most fleets; change them only with cause.
| Variable | Default | Required? | Description |
|---|---|---|---|
POLL_INTERVAL_SECONDS |
60 |
No | Base device-polling interval in seconds. |
INGEST_EVERY_MINUTES |
5 |
No | Event-ingest cadence (Suricata alerts, DNS queries) in minutes; valid range 1–30. |
CONFIG_BACKUP_HOUR |
3 |
No | UTC hour (0–23) of the daily device config backup. |
SWEEP_EVERY_MINUTES |
5 |
No | Cadence of the orphaned-scheduled-action sweeper in minutes; valid range 1–30. |
ORPHAN_GRACE_MINUTES |
5 |
No | How long a scheduled row may be overdue before the sweeper touches it. |
MAX_REENQUEUE_ATTEMPTS |
5 |
No | Give up on an orphaned action after this many device-free re-enqueues. |
SESSION_CLEANUP_MINUTE |
0 |
No | Minute-of-hour the hourly expired-session cleanup runs. |
REPORT_WEEKDAY |
mon |
No |
Legacy default weekly-report day (mon–sun), superseded by per-schedule weekday/hour configured in-app. |
REPORT_HOUR |
4 |
No | Legacy default weekly-report UTC hour (0–23), superseded by per-schedule settings configured in-app. |
DEVICE_CERT_DAYS |
90 |
No | Lifetime in days of a device's syslog-forwarding certificate (short lifetime bounds a stolen-key window). Log lake only. |
CERT_RENEWAL_WINDOW_DAYS |
30 |
No | Renew a forwarding cert when its not_after falls within this many days. Log lake only. |
CERT_RENEWAL_HOUR |
3 |
No | UTC hour the daily cert-renewal cron runs. Log lake only. |
SILENT_ALERT_ENABLED |
true |
No | Master switch for the silent-tenant detector cron (alerts when a tenant stops shipping logs). |
SILENT_ALERT_AFTER_HOURS |
6 |
No | Alert when a tenant has been silent longer than this many hours (the UI badge uses 1h). |
SILENT_ALERT_CRON_MINUTE |
0 |
No | Minute of each hour the silent-tenant detector runs. |
These variables tune performance and concurrency limits that are read once at process startup. Changing one requires a restart of the affected service (docker compose up -d to recreate the container). Each default reproduces the prior, pre-v0.8.0 behaviour, so an existing deployment needs no changes unless you are deliberately tuning it.
| Variable | Default | Required? | Description |
|---|---|---|---|
WORKER_MAX_JOBS |
10 |
No | Maximum number of jobs the ARQ worker runs concurrently. Operations on different devices run in parallel up to this cap; a single device always serializes its operations behind a per-device advisory lock, so raising this never causes two concurrent writes to the same box. Raise it for larger fleets, lower it to cap worker load. |
DB_POOL_SIZE |
5 |
No | SQLAlchemy connection-pool size, applied to both the API engine (opngms_app, RLS-enforced) and the worker engine (owner, RLS-exempt). |
DB_MAX_OVERFLOW |
10 |
No | SQLAlchemy pool overflow — extra connections opened beyond DB_POOL_SIZE under burst load, also applied to both engines. Effective ceiling per engine is DB_POOL_SIZE + DB_MAX_OVERFLOW; size your Postgres max_connections to cover both engines plus headroom. |
OPNSENSE_HTTP_TIMEOUT |
10.0 |
No | Default per-request timeout, in seconds, for outbound calls from the worker to managed OPNsense boxes (a deploy/network characteristic). Raise it for slow links or heavily loaded boxes; lower it to fail faster. |
Note: The repo ships a comprehensive
.env.exampleorganised into three commented sections — Required secrets · Boot-time tuning (requires restart) · Runtime defaults — so you can see every tunable, its default, and which tier it belongs to in one place. The boot-time variables above live in the middle section.
A set of operational settings can be changed live, without a restart, by a superadmin under System → Runtime settings. These are layered over the application's key/value store as a generic settings registry: the value from .env (or the code default) is the default, and a database override — when set — wins. Each setting shows its default in the UI and can be reset back to it.
Reads and writes go through GET / PUT /api/admin/settings — superadmin-gated, CSRF-protected, and audited. Because the default is sourced from the environment, you can pre-seed a value in .env (the Runtime defaults section of .env.example) and still override it later in the UI without a redeploy.
The runtime-editable settings:
| Setting | Default | Description |
|---|---|---|
firmware_max_status_polls |
30 |
How many times the worker polls a box for firmware-update status before giving up. |
firmware_poll_interval_seconds |
10 |
Seconds between firmware status polls. Together with firmware_max_status_polls this bounds the total firmware-poll budget. |
catalog_auto_fetch |
true |
Fetch and cache version-aware config catalogs on a cache miss. The runtime mirror of CATALOG_AUTO_FETCH; set false for cache-only (air-gapped) operation. |
geoip_auto_fetch |
true |
Fetch and cache the offline GeoIP database on a cache miss. Set false for air-gapped operation, where the database must be pre-seeded. |
silent_alert_enabled |
true |
Master switch for the silent-tenant detector (alerts when a tenant stops shipping logs). The runtime mirror of SILENT_ALERT_ENABLED. |
silent_alert_after_hours |
6 |
Alert when a tenant has been silent longer than this many hours. The runtime mirror of SILENT_ALERT_AFTER_HOURS. |
login_max_attempts |
5 |
Failed-login attempts allowed before lockout. The runtime mirror of LOGIN_MAX_ATTEMPTS. |
login_lockout_window_seconds |
900 |
Lockout window in seconds (default 15 minutes) over which failed attempts are counted. The runtime mirror of LOGIN_LOCKOUT_WINDOW_SECONDS. |
session_ttl_hours |
12 |
Absolute session lifetime in hours, regardless of activity. The runtime mirror of SESSION_TTL_HOURS. |
session_idle_minutes |
120 |
Sliding/idle session timeout in minutes. The runtime mirror of SESSION_IDLE_MINUTES. |
perimeter_retention_days |
30 |
Global default retention, in days, for the perimeter rollup (failed logins + firewall blocks). Per-tenant-overridable. |
events_retention_days |
90 |
Global default retention, in days, for the events hypertable (IDS / DNS). Per-tenant-overridable. |
metrics_retention_days |
30 |
Global default retention, in days, for device-health metrics. Per-tenant-overridable. |
log_lake_retention_days |
30 |
Global default retention, in days, for the log lake (OpenSearch). Bridges the existing LOG_RETENTION_DAYS. Per-tenant-overridable. See Log-Lake. |
Note: Where a setting mirrors an environment variable (the table above), the
.envvalue is only the default: once a superadmin saves an override in Runtime settings, the override takes precedence until it is reset. To change one of these permanently for a fresh install set it in.env; to change one now on a running deployment use the System page.
Yes. Every in-app override — the global Runtime settings and each tenant's per-tenant retention — is stored in Postgres (app_setting.runtime_config for the global values; tenant_retention for the per-tenant ones), which lives in the opngms_pg named volume. So they persist across a restart, an image upgrade, and docker compose down followed by up — restarting the stack does not reset settings to the .env/built-in defaults. The .env value is read at startup only as the fallback for a setting that has no override yet, and editing .env after you've overridden a setting in the UI has no effect (the DB override wins — change it in the UI instead).
| Action | Effect on settings (and data) |
|---|---|
docker compose restart / stop+start / down then up
|
Volume intact → all overrides (and data) persist. No reset. |
docker compose down -v |
The -v destroys the named volumes → Postgres is wiped, all data and overrides are lost, and the stack starts again from the .env/built-in defaults. |
The same persistence applies to the underlying data: metrics / events / the perimeter rollup live in opngms_pg; the log lake's indices live in the opngms_os volume — both survive a restart and are only lost by removing the volume.
The four *_retention_days settings above are the global defaults for how long OPNGMS keeps the data behind dashboards and reports — across four stores: the perimeter rollup, the events hypertable (IDS / DNS), device metrics, and the log lake (OpenSearch). Tenant-aware worker purge jobs own deletion.
Each default can be overridden per tenant from that tenant's settings page (a "Retention" card; tenant_admin / superadmin, via GET / PUT /api/tenants/{id}/retention). The effective retention for a store is per-tenant override ?? global default — an override may be longer or shorter than the global default.
Note: Retention is consistency-checked against reports — a report cannot be configured to cover more days than the tenant's effective retention for the stores its enabled sections use. Over-long on-demand and scheduled reports are blocked; lowering retention is allowed but surfaces a warning (and, for a global lowering, the list of impacted tenants). See Reporting and Log-Lake.
| Variable | Default | Required? | Description |
|---|---|---|---|
LIVE_PUSH_ENABLED |
false |
No |
Master switch for real configuration push to managed OPNsense boxes. Default false runs every apply as a dry-run; set true only when you intend the worker to write live changes. See Configuration-Editor. |
These control where the version-aware config editor fetches versioned OPNsense catalogs (see Configuration-Editor).
| Variable | Default | Required? | Description |
|---|---|---|---|
CATALOG_RELEASE_BASE_URL |
https://github.com/l0rdg3x/OPNGMS/releases/download/catalogs |
No | Base URL the app fetches versioned catalogs from on a cache miss. |
CATALOG_AUTO_FETCH |
true |
No | Fetch and cache catalogs on a cache miss. Set false for cache-only (air-gapped) operation, where catalogs must be pre-seeded. |
The log lake is an opt-in overlay (docker-compose.logs.yml, docker-compose.logs.multinode.yml, or bundled in docker-compose.full.yml). These variables are unused unless an overlay is active. See Log-Lake for the full bring-up, network requirements, and the multi-node HA option.
| Variable | Default | Required? | Description |
|---|---|---|---|
SYSLOG_RECEIVER_HOST |
logs.opngms.local (code) / logs.opngms.example (.env.example) |
No | Public name/IP that managed devices ship logs to over mTLS syslog. Must be reachable by the devices. |
SYSLOG_TLS_PORT |
6514 |
No | TLS syslog (RFC 5425) listener port published by the syslog-ng receiver; must be reachable by managed devices. |
OPENSEARCH_URL |
http://opensearch:9200 (single-node) / http://opensearch-n1:9200 (multi-node) |
No | Internal URL the backend and receiver use to reach OpenSearch. OpenSearch is internal-only (plain HTTP, not published). |
LOG_RETENTION_DAYS |
30 |
No |
Global default retention horizon in days for ingested logs, bridged by the log_lake_retention_days runtime setting and per-tenant-overridable (Retention card). Enforced by the purge_log_lake worker job, not an ISM policy. See Log-Lake. |
LOG_SEARCH_MAX_SIZE |
200 |
No | Maximum hits returned per log-search request. |
LOG_SEARCH_MAX_RANGE_DAYS |
31 |
No | Maximum time span (days) a single log search may cover. |
LOG_FLEET_TERMS_SIZE |
10000 |
No | Max tenants in the MSP log-fleet terms aggregation (sized to avoid silent truncation). |
Note: The multi-node overlay (
docker-compose.logs.multinode.yml) sets OpenSearch clustering directly in the Compose file —cluster.name,discovery.seed_hosts,cluster.initial_cluster_manager_nodes,node.name, andOPENSEARCH_JAVA_OPTS(heap). These are container settings, not.envvariables; pointOPENSEARCH_URLat the first node (opensearch-n1). See Log-Lake.
OPNGMS configuration comes from three places:
-
.env(gitignored). The single file that holds all secrets and host-specific values. Copy it from.env.example, fill it in, and never commit it. Theapi,worker, andmigrateservices load it viaenv_file: .env; thedbandfrontendservices read individual${VAR}interpolations. This is where every variable in the tables above belongs. -
Compose
environment:blocks. A few values are derived or fixed inside the Compose files rather than typed into.env— e.g.ALEMBIC_DATABASE_URL(set to${ADMIN_DATABASE_URL}for themigrateservice) andTLS_MODE(flipped tobuiltinby the TLS overlay). The multi-node OpenSearch clustering keys live entirely indocker-compose.logs.multinode.yml. You normally do not edit these. -
In-app settings (not environment variables). Some operational configuration is intentionally stored in the database and managed through the admin UI, with secrets encrypted at rest using
MASTER_KEY:-
SMTP delivery — relay host, port, security, username/password, and default From address. Configured under Admin → SMTP delivery; there are no SMTP variables in
.env. See Reporting. - Per-tenant report settings & schedules — report title, logo, language, white-label sender, and weekly/monthly/on-demand schedules with recipient lists. Configured per tenant in the console. See Reporting.
- MFA enforcement policy and per-user TOTP enrolment — configured under Two-factor auth. See Security.
-
Runtime settings — the live-editable tunables described in Runtime settings (in-app), under System → Runtime settings (including the four global retention defaults, each per-tenant-overridable from a tenant's Retention card). For these the
.env/code value is only the default; a superadmin's database override wins until reset, no restart required.
-
SMTP delivery — relay host, port, security, username/password, and default From address. Configured under Admin → SMTP delivery; there are no SMTP variables in
For the install-time walkthrough of .env and TLS, see Installation. For rotating MASTER_KEY / SESSION_SECRET and bumping OPNGMS_VERSION, see Upgrading.
Deploy & operate
Understand & extend