Docs / Reference / Configuration

Configuration

All settings are environment variables prefixed MONSTEROPS_, read from .env (production: /opt/monsterops/.env; development: the project root) or the process environment. Only DATABASE_URL and SECRET_KEY are required in practice — everything else has a sensible default. System → Settings renders the current effective config and can generate a starter .env.

Restart the service after changing any variable: sudo systemctl restart monsterops.

Core

Variable Default Purpose
MONSTEROPS_DATABASE_URL postgresql+asyncpg://radius:radius@localhost/radius Async SQLAlchemy DSN — must use the +asyncpg driver
MONSTEROPS_SECRET_KEY change-me-before-production Signs JWTs and derives the key that encrypts stored credentials — change this; rotate later with monsterops rotate-secret-key
MONSTEROPS_ACCESS_TOKEN_EXPIRE_MINUTES 30 Access-token lifetime; the refresh cookie rotates a new one silently
MONSTEROPS_COOKIE_SECURE (auto) Force Secure on session cookies; unset derives it from the request scheme (X-Forwarded-Proto aware)
MONSTEROPS_DEBUG false Enables /api/docs and verbose errors — never on in production
MONSTEROPS_LOG_LEVEL INFO DEBUG / INFO / WARNING / ERROR
MONSTEROPS_ALLOWED_ORIGINS "" Comma-separated CORS origins (empty = CORS disabled)
MONSTEROPS_ENABLED_MODULES "" (all) Comma-separated allow-list; disabled modules are never imported
MONSTEROPS_PLUGINS "" Comma-separated third-party plugin entry-point names to load

Database pool

Variable Default Purpose
MONSTEROPS_DB_POOL_SIZE 10 Steady-state connections
MONSTEROPS_DB_MAX_OVERFLOW 20 Burst connections above pool size
MONSTEROPS_DB_POOL_TIMEOUT 30 Seconds to wait for a free connection
MONSTEROPS_DB_POOL_RECYCLE 1800 Recycle connections older than N seconds

Paths & integrations

Variable Default Purpose
MONSTEROPS_RADIUS_LOG_FILES /var/log/freeradius/radius.log Comma-separated log paths shown in the log viewer
MONSTEROPS_GEOIP_DB "" Path to a MaxMind GeoLite2-City.mmdb (empty = geolocation off)
MONSTEROPS_BACKUP_DIR /var/backups/monsterops Where named DB/config snapshots are stored
MONSTEROPS_FREERADIUS_PROXY_CONF /etc/freeradius/3.0/proxy.conf Where Realms writes generated proxy config
MONSTEROPS_VPN_CONFIG_DIR /etc/monsterops/vpn Where VPN writes wg-quick configs (0600)
MONSTEROPS_FIREWALL_RULESET_PATH /etc/monsterops/firewall.nft Confirmed ruleset re-applied at boot by the firewall unit
MONSTEROPS_RADIUS_SERVER_IP "" (auto) Advertised address for the NAS RADIUS Setup deploy; blank auto-detects the egress IP toward each NAS

NAS reachability & Server Console

Variable Default Purpose
MONSTEROPS_NAS_PROBE_ENABLED true Background ICMP probe for the dashboard up/down state
MONSTEROPS_NAS_PROBE_INTERVAL_SECONDS 60 Seconds between probe sweeps
MONSTEROPS_NAS_PROBE_TIMEOUT_SECONDS 3 Per-probe timeout
MONSTEROPS_CONSOLE_ENABLED false Enable the Server Console (opt-in: its palette restarts FreeRADIUS / runs migrations from the browser)
MONSTEROPS_REQUIRE_2FA false Require two-factor (TOTP) for every admin; unenrolled admins must set it up at next sign-in

Log retention

Applied by the scheduled Log Retention job. 0 disables pruning for that table (keep forever).

Variable Default (days) Table
MONSTEROPS_RETENTION_AUTH_LOG_DAYS 90 radpostauth
MONSTEROPS_RETENTION_AUDIT_LOG_DAYS 365 audit_log
MONSTEROPS_RETENTION_NOTIFICATION_DAYS 90 mr_notification_history
MONSTEROPS_RETENTION_DISPATCH_LOG_DAYS 180 mr_nas_dispatch_log

Email (scheduled reports & notifications)

MONSTEROPS_SMTP_HOST, MONSTEROPS_SMTP_PORT (587), MONSTEROPS_SMTP_USER, MONSTEROPS_SMTP_PASSWORD, MONSTEROPS_SMTP_FROM, MONSTEROPS_SMTP_TLS (false).

Rotating the secret key

MONSTEROPS_SECRET_KEY does double duty: it signs session tokens and derives the key that encrypts credentials at rest — NAS Manager SSH/Telnet secrets and directory (AD/LDAP) bind passwords. So you can't just change it — the old ciphertext would no longer decrypt.

Rotate, don't replace

Changing MONSTEROPS_SECRET_KEY directly orphans every stored credential. Rotate with the CLI while the old key is still configured:

# Dry run first — reports how many credentials would be re-encrypted, writes nothing
monsterops rotate-secret-key --new-key "<new-key>" --dry-run

# Then rotate for real (old key read from the current MONSTEROPS_SECRET_KEY)
monsterops rotate-secret-key --new-key "<new-key>"

It re-encrypts every stored credential in one pass and is abort-safe — if any value fails to decrypt with the old key it stops and changes nothing. When it finishes, set MONSTEROPS_SECRET_KEY to the new value and restart.