Skip to content

Configuration

Each component of osctrl requires configuration in order to operate properly. As of PR #754, osctrl uses a single YAML configuration file per service, consolidating all settings into one place.

The core osctrl services (osctrl-tls and osctrl-api) now use a unified YAML configuration file that includes all necessary settings in one place. The default filenames are:

osctrl-mcp has no YAML configuration of its own: it talks to osctrl-api as a client over stdio and is configured entirely through flags or environment variables.

You can specify a different configuration file using the --config-file or -C flag.

Only the service, db and redis sections are required — every other section (rateLimits, osquery, saml, oidc, jwt, tls, logger, carver, debug, and the osctrl-tls-only batchWriter, configEndpoints, osctrld, metrics) can be omitted entirely and the service falls back to its built-in defaults, or to values already stored in the database.

Every generated config file also starts with a top-level version: 5 field — the configuration schema version the file targets. It’s checked against the version the running binary supports: an older or missing version logs a startup warning that fields added since may be missing, and a newer one warns that this binary ignores fields it doesn’t know about. It’s never fatal, just a heads-up to compare against a fresh config-generate output.

Each osctrl binary includes built-in commands to help you create and validate configuration files:

To generate a valid YAML configuration file with default values:

Terminal window
./osctrl-tls config-generate -f tls.yaml
./osctrl-api config-generate -f api.yaml

This will create configuration files (tls.yaml, api.yaml) populated with all required fields and default settings. If not using -f, the file will be created in config/<service>.yml by default.

The generated configuration includes:

  • All required configuration sections
  • Default values for each setting
  • Service-specific options relevant to each binary

To validate an existing configuration file before starting the service:

Terminal window
./osctrl-tls config-verify --file tls.yaml
./osctrl-api config-verify --file api.yaml

The verification process checks:

  • YAML syntax validity
  • Required fields are present
  • Configuration values are within valid ranges
  • Authentication method compatibility
  • Logger and carver type validity
  • Database and Redis connection parameters

If the configuration is valid, the command will exit with status code 0. If there are errors, detailed error messages will be displayed indicating what needs to be fixed.

The YAML configuration file contains the following main sections:

Basic service settings including network listener, ports, authentication, and logging:

version: 5 # Configuration schema version
service:
listener: "0.0.0.0" # Network interface to bind to
port: "9000" # TCP port for the service
host: "tls.example.com" # Public hostname
auth: "none" # Authentication method
logLevel: "info" # Logging level: debug, info, warn, error
logFormat: "json" # Log format: json, console
serviceConfigEnabled: false # osctrl-api only: expose /service-config API + frontend UI
logSinksEnabled: true # Expose the log sinks API + frontend section
authProvidersEnabled: true # Expose the auth providers API + frontend section
alertsEnabled: false # Enable the alerting subsystem (routes, tables, ingest)
healthEnabled: false # Enable component health/status reporting
vulnEnabled: false # Enable vulnerability monitoring (restart required)
basePath: "" # osctrl-api only: public URL prefix, e.g. "/osctrl"
eventsEnabled: false # Enable SSE live-update invalidation hints
eventsNamespace: "" # Deployment-unique Redis namespace for live updates
mfaRequired: false # osctrl-api only: require MFA for password logins
mfaIssuer: "" # Authenticator app label; empty uses "osctrl (<host>)"
mfaRPID: "" # WebAuthn Relying Party ID; empty uses `host`
mfaOrigins: "" # Comma-separated origins allowed for WebAuthn ceremonies
auditLog: true # Enable audit logging for sensitive actions (retention: audit_log_retention_days setting)
trustedProxies: "" # CIDR list trusted for X-Forwarded-For/X-Real-IP
geoipDBPath: "" # Path to a GeoLite2-Country .mmdb for node IP geolocation
postureEnabled: false # Enable security & compliance posture collection
postureQueryPrefix: "osctrl:posture:" # Scheduled-query name prefix ingested as posture data
dbHealthCheck: false # Enable DB health monitor / stale-serve mode on outages
dbHealthInterval: 5 # Seconds between DB health pings
dbHealthThreshold: 3 # Consecutive failures before entering stale-serve mode
  • logSinksEnabled and authProvidersEnabled default to true and gate the newer Log Sinks and Auth Providers API routes and frontend sections, independently of serviceConfigEnabled.
  • alertsEnabled defaults to false (unlike the two above) and gates the Alerts subsystem end to end: with it off, no alert tables are created, no routes are registered, and there’s no ingest overhead. Changing it requires a service restart — both osctrl-api and osctrl-tls wire their alert routes/ingest hooks at boot.
  • healthEnabled turns on service heartbeats and /api/v1/health/status.
  • vulnEnabled turns on vulnerability monitoring. On osctrl-tls it stores software inventory; on osctrl-api it also registers the /api/v1/vulnerabilities routes and downloads OSV and CISA KEV data. When off, no tables are created and nothing is downloaded. Requires a restart. The feed settings (vulnOSVURL, vulnKEVURL, vulnEcosystems, vulnSyncHours, vulnRetentionDays, vulnMaxDownloadMB, vulnNVDEnabled, vulnNVDURL) are read by osctrl-api only; set the optional NVD API key through VULN_NVD_API_KEY, never in YAML.
  • basePath (osctrl-api only; flag --base-path, env SERVICE_BASE_PATH) is the public URL prefix the frontend and API are served under when osctrl shares a hostname behind a reverse proxy, for example https://host/osctrl/. Empty (default) serves at the root. See Serving under a URL prefix.
  • eventsEnabled and eventsNamespace enable best-effort Server-Sent Events live-update hints through /api/v1/events. Set both on osctrl-api and osctrl-tls, and use a namespace unique to the deployment sharing Redis.

Authentication types (auth):

  • none - No authentication. For osctrl-tls this is the only valid value — osquery nodes authenticate with their enroll secret and node_key instead. For osctrl-api, none requires OSCTRL_INSECURE_NO_AUTH=1 in the environment and is intended for local development only, since it impersonates a super-admin on every request.
  • jwt - JWT token-based authentication (osctrl-api). The only supported value for production deployments.

SAML and OIDC federated login are configured independently of auth, via the saml.enabled and oidc.enabled switches — the frontend discovers which methods are available through GET /api/v1/auth/methods and can offer JWT, SAML and OIDC login side by side.

MFA (mfaRequired and friends) adds a second factor — an authenticator app (TOTP) or a WebAuthn passkey/security key — to password logins on osctrl-api. When mfaRequired: true, users without an enrolled factor are sent to enrollment on their next login instead of being locked out; service accounts (token auth) and federated SAML/OIDC logins are unaffected. Users can always enroll a factor voluntarily from their profile page regardless of this setting.

serviceConfigEnabled, MFA and posture fields also appear under service: in tls.yaml, but only osctrl-api acts on them — they’re kept in both files so the shared service section round-trips through the Service Configuration API.

With postureEnabled: true, individual posture checks no longer have to be pulled out of a built-in profile wholesale — a Posture tab on the environment config page in osctrl-frontend lists the osctrl:posture:-prefixed scheduled queries for that environment and lets an operator add or remove individual checks directly.

Backend database connection settings:

db:
type: "postgres" # Database type: postgres, mysql, sqlite
host: "127.0.0.1"
port: "5432"
name: "osctrl"
username: "postgres"
password: "postgres"
sslmode: "disable" # SSL mode for database connection
maxIdleConns: 20 # Maximum idle connections
maxOpenConns: 100 # Maximum open connections
connMaxLifetime: 30 # Connection max lifetime (minutes)
connRetry: 5 # Connection retry timeout (seconds, 0=no retry)
filePath: "./osctrl.db" # For SQLite only
read: # Optional read replica (off by default)
enabled: false
host: "" # Empty fields inherit the primary's value
port: 0
username: ""
password: ""
sslmode: ""
maxIdleConns: 0
maxOpenConns: 0

With db.read.enabled: true, a service opens a second connection pool to a read replica of the database. Reads that tolerate a little replication lag go to the replica, which takes load off the primary that also serves the write path (check-ins, query results, enrollment). It is off by default, and with it off nothing changes.

  • Any db.read field left empty (or 0) inherits the primary’s value, so a replica on another host usually needs only enabled and host.
  • The database type, database name and connMaxLifetime always follow the primary.
  • SQLite is not supported: the service refuses to start with db.read.enabled set.
  • If the replica cannot be reached at startup, the service fails or retries the same way as it does for the primary (see connRetry).
  • The database health check pings both pools, so a replica outage marks the service as degraded even though the primary still accepts writes.
  • The replica password is handled like the primary’s db.password: set it through the environment (DB_READ_PASS) or a protected config file.

What osctrl-api reads from the replica:

  • The dashboard: node stats, platform and osquery version counts, active query and carve counts, and the environment activity chart.
  • Node lists (active, inactive, all, and the paged and searchable list) and the activity chart on the node detail page.
  • The audit log list.
  • Vulnerability views: findings, summary, advisories, per-node report and feed status.
  • Posture data: risk level and uptime in the node list and node detail, and the node posture page.

Everything else stays on the primary: authentication, permissions, enrollment, node-key lookups, query and carve lists and detail pages (a lagging replica could return a 404 right after a query is created), posture checks and profiles, node tags, the osquery and osctrld request paths and the CLI. Only display paths move, so a just-recorded audit entry or posture result may appear a little later.

What osctrl-tls reads from the replica:

  • The node scans behind alert rules.
  • The osctrld Prometheus gauges (osqueryd down, osquery behind its target version, paused rollouts), see metrics.

Everything osquery and osctrld agents request (enrollment, configuration, logs, queries, carves) stays on the primary.

Cache configuration for session management and performance:

redis:
host: "127.0.0.1"
port: "6379"
password: ""
db: 0
connectionString: "" # Optional: full connection string
connRetry: 5 # Connection retry timeout (seconds)

HTTP rate limiting, shared by both services but enforced per endpoint:

rateLimits:
login: # Password/SSO login attempts (osctrl-api)
burst: 10 # Tokens available immediately
period: 1m # Refill window
evictAfter: 10m # Idle client buckets are evicted after this
retryAfter: 60 # Retry-After response header, in seconds
maxBuckets: 0 # Max tracked client buckets; 0 = internal default
preAuth: # Pre-auth discovery routes (osctrl-api)
burst: 60
period: 1m
evictAfter: 10m
retryAfter: 60
maxBuckets: 0
serviceConfigApply: # POST /service-config/apply restart requests (osctrl-api)
burst: 3
period: 10m
evictAfter: 30m
retryAfter: 60
maxBuckets: 0
enroll: # osquery enroll attempts (osctrl-tls)
burst: 20
period: 1m
evictAfter: 10m
retryAfter: 60
maxBuckets: 0
osctrldAuth: # Wrong osctrld secrets, per client IP and environment (osctrl-tls)
burst: 10
period: 1h
evictAfter: 2h
retryAfter: 360
maxBuckets: 0
osctrld: # Every osctrld request, per client IP (osctrl-tls)
burst: 300
period: 1m
evictAfter: 10m
retryAfter: 60
maxBuckets: 0
osctrldDownload: # osctrld extension downloads, per client IP (osctrl-tls)
burst: 30
period: 1m
evictAfter: 10m
retryAfter: 60
maxBuckets: 0

osctrl-api only enforces login, preAuth and serviceConfigApply; osctrl-tls only enforces enroll, osctrldAuth, osctrld and osctrldDownload. osctrldAuth is charged only by wrong secrets: once an address spends its budget it gets 429 on that environment’s osctrld routes, even with the right secret, until it refills. The osctrld limits key on the client IP, so behind a reverse proxy set service.trustedProxies to the proxy’s address, otherwise every client shares the proxy’s budgets. Every limiter still has to appear in both tls.yaml and api.yaml so the section has one shared shape and can round-trip through the Service Configuration API — the values the other service doesn’t use are simply ignored.

Logging destination and settings:

logger:
type: "db" # Logger type: db, splunk, graylog, s3, file, kinesis, kafka, logstash, elastic, http
loggerDBSame: true # Use same DB config as main DB for logging
alwaysLog: false # Always log to DB regardless of other loggers
# Database logger settings (if type: db and loggerDBSame: false)
db:
type: "postgres"
host: "127.0.0.1"
# ... (same structure as main db config)
# S3 logger settings (if type: s3)
s3:
bucket: "osctrl-logs"
region: "us-east-1"
accessKey: "your-access-key" # Optional: leave both keys empty to use the default AWS credential chain (environment, shared config, IAM roles)
secretAccessKey: "your-secret-key"
# Splunk logger settings (if type: splunk)
splunk:
url: "https://splunk.example.com:8088/services/collector"
token: "your-hec-token"
host: "osctrl"
index: "osquery"
# Graylog logger settings (if type: graylog)
graylog:
url: "http://graylog.example.com:12201/gelf"
host: "osctrl"
queries: "osquery_queries"
status: "osquery_status"
results: "osquery_results"
# File logger settings (if type: file)
local:
filePath: "/var/log/osctrl/osctrl.log"
maxSize: 25 # Max file size in MB before rotation
maxBackups: 5 # Number of old log files to retain
maxAge: 10 # Max days to retain old log files
compress: true # Compress rotated files with gzip
# Generic HTTP logger settings (if type: http)
http:
url: "https://logs.example.com/ingest"
method: "POST" # Default POST
headers: # Extra request headers, e.g. authentication
Authorization: "Bearer your-token"
format: "json" # json (default), ndjson or raw
contentType: "" # Defaults to match the format
timeoutSeconds: 30
includeMetadata: false # Wrap events with environment, uuid, logType and timestamp

Logger types:

  • db - Store logs in the backend database
  • splunk - Send logs to Splunk HEC
  • graylog - Send logs to Graylog GELF endpoint
  • s3 - Upload logs to AWS S3
  • file - Write logs to local file with rotation
  • kinesis - Send logs to AWS Kinesis
  • kafka - Send logs to Kafka topics
  • logstash - Send logs to Logstash
  • elastic - Send logs directly to Elasticsearch
  • http - Send logs to any HTTP/HTTPS endpoint. format: json sends the payload as-is, ndjson splits arrays into one object per line, and raw sends the bytes verbatim

The logger: YAML section above is no longer read directly at every boot — it’s now only a seed source. On first boot, osctrl-tls creates one row per entry in logger.types (or logger.type) in a log_sinks database table; from then on, that table — not the YAML file — is what actually drives log export, and it’s managed live through the Log Sinks section in osctrl-frontend or the /api/v1/log-sinks API, gated by service.logSinksEnabled (default true).

This means an operator can now run multiple sinks at once, enable/disable each independently, scope sinks per environment, and route specific data categories (status, result, query, carve.meta, carve.data) to specific sinks — none of which was possible with a single logger.type/types YAML value alone. Editing the YAML and restarting still works (it just reseeds any sink that hasn’t been edited through the API), but a sink edited through the UI is marked source: db and from then on ignores further YAML changes for that sink until it’s deleted or explicitly reverted.

File carving storage settings:

carver:
type: "db" # Carver type: db, s3
# S3 carver settings (if type: s3)
s3:
bucket: "osctrl-carves"
region: "us-east-1"
accessKey: "your-access-key"
secretAccessKey: "your-secret-key"

Carver types:

  • db - Store carved files in the database
  • s3 - Upload carved files to AWS S3 The local carver was never implemented and is now rejected at startup; use db or s3.

TLS termination settings:

tls:
termination: true # Enable TLS termination
certificateFile: "/path/to/cert.pem"
keyFile: "/path/to/key.pem"

Osquery-specific settings for TLS service:

osquery:
queryDispatchTTL: 2m # Empty distributed-query cache TTL; <=0 uses 2m
sha256: "" # Expected SHA-256 for a single osquery package artifact
version: "5.23.1" # Osquery version
tablesFile: "data/5.23.1.json" # Path to osquery tables JSON
logger: true # Enable remote TLS logger endpoint
config: true # Enable remote TLS config endpoint
query: true # Enable remote TLS query endpoints
carve: true # Enable remote TLS carver endpoints
accelerated: false # Enable accelerated query polling
console: false # Enable the per-node interactive console
fileExplorer: false # Enable the per-node file explorer (requires query: true)
readOnly: false # Prevent operator-driven osquery configuration changes
  • console gates the per-node interactive console (reachable from the node detail page in osctrl-frontend): on osctrl-api it controls whether the feature is advertised and its routes registered (also requires query: true); on osctrl-tls it controls whether active console sessions can request accelerated query polling.
  • fileExplorer behaves the same way for the per-node file explorer, and no longer requires accelerated: true — only query: true.
  • sha256 is handed to osctrld so nodes can verify the osquery package they download. Set it only when every node installs the same package artifact; mixed fleets should leave it empty and pass a per-node digest to osctrld instead.

osctrl-api can also host an authenticated MCP endpoint at /api/v1/mcp:

mcp:
enabled: false # Mount /api/v1/mcp
allowWrites: false # Also expose mutating MCP tools

It uses the caller’s normal osctrl token, permissions and audit path. Keep allowWrites off unless you deliberately want MCP clients to schedule queries, complete/expire queries, or tag nodes.

Prometheus metrics endpoint settings (TLS service only):

metrics:
enabled: false # Enable Prometheus metrics
listener: "0.0.0.0"
port: "9090"

JWT authentication settings:

jwt:
jwtSecret: "your-jwt-secret" # Secret for signing JWT tokens
hoursToExpire: 3 # Token expiration time in hours

SAML 2.0 federated login for osctrl-api, disabled by default. When enabled: true, the API fetches the IdP metadata at startup and refuses to start if that fails; the frontend discovers the method via GET /api/v1/auth/methods and shows a “Continue with SAML” button automatically:

saml:
enabled: false # Enable SAML routes
entityId: "" # SP entity ID, conventionally the metadata URL
acsUrl: "" # Where the IdP POSTs the SAMLResponse (must end with /api/v1/auth/saml/acs)
metadataUrl: "https://idp.example.com/metadata"
logoutUrl: "https://idp.example.com/logout" # IdP session-termination URL, used on logout
jitProvision: false # Just-in-time user provisioning, as non-admin
linkLocalAccounts: false # Let this identity claim an existing local password account with the same username
usernameAttribute: "" # SAML attribute mapped to the osctrl username
signingCertPath: "" # PEM cert + key for signing outbound AuthnRequests
signingKeyPath: ""
forceAuthn: true # Force re-authentication at the IdP on every login

Register osctrl with the IdP by pointing it at the SP metadata URL: https://<host>/api/v1/auth/saml/metadata. The certPath, keyPath, rootUrl, loginUrl and spInitiated fields are legacy, consumed only by the retired osctrl-admin service, and ignored by osctrl-api. OIDC has the equivalent oidc.linkLocalAccounts field.

By default, a federated login whose resolved username matches an existing local password account is refused, not silently merged — otherwise anyone who can make the IdP assert a given username (e.g. admin) could inherit that account’s privileges. Set linkLocalAccounts: true on the provider to let it adopt pre-created local accounts (it never grants extra privileges — a non-admin local account stays non-admin). Accounts created by federated login are always matched, including across protocols.

Usernames resolved from a SAML attribute or OIDC claim accept either a plain handle (^[a-zA-Z0-9_-]{1,64}$) or an email address (stored lowercased). An email claim/attribute is only accepted when the IdP also asserts it’s verified; otherwise login falls back to the provider’s subject identifier. See Auth providers for the full OIDC/SAML environment variable reference, account-linking details, and per-IdP setup notes (Keycloak, Auth0, Okta, Entra ID).

A new Auth Providers management layer, gated by service.authProvidersEnabled (default true), lets an operator review, test, and edit these SAML/OIDC settings from osctrl-frontend or the /api/v1/auth-providers API instead of only through this YAML file — see that section for what it currently does and does not change about how login actually works.

Settings for the osctrld endpoints exposed by osctrl-tls:

osctrld:
enabled: false # Enable osctrld endpoints

Database batch writer settings for TLS service:

batchWriter:
writerBatchSize: 50 # Events before flushing
writerTimeout: 60s # Max wait time before flush
writerBufferSize: 2000 # Event channel buffer size

HTTP request debugging:

debug:
enableHTTP: false # Enable HTTP request debugging
httpFile: "debug-http.log" # File to dump HTTP requests
showBody: false # Include request body in dumps

osctrl-mcp is the Model Context Protocol server for operators and automation. It has no YAML configuration: it speaks MCP over stdio, is launched by the MCP client rather than run as a service, and takes everything it needs from flags or environment variables.

Flag Environment variable Purpose
--api-url OSCTRL_API_URL Base URL of osctrl-api
--api-token OSCTRL_API_TOKEN Bearer token; prefer the environment variable so it stays out of the process list
--config, -c OSCTRL_API_FILE Path to an osctrl-api.json holding url + token
--insecure OSCTRL_INSECURE Skip TLS verification — development only
--allow-writes OSCTRL_MCP_ALLOW_WRITES Expose the mutating tools; off by default
--log-level OSCTRL_LOG_LEVEL debug, info, warn, error (default info)

The config file is read first and explicit flags or environment variables override it. Give it a dedicated service-account token with the smallest permissions that fit your workflow.

For the full tool list and client registration examples, see the usage of osctrl-mcp.

The frontend build is prefix-agnostic, so one build serves any prefix. The prefix is set in two places, and both must match: the reverse proxy, and basePath on osctrl-api.

  1. Proxy: strip the prefix before forwarding, so API routes stay /api/..., and rewrite the <base href> of index.html. In the bundled deploy/nginx/frontend.conf uncomment the two rewrite lines and the sub_filter lines in location /, changing osctrl in all four places:

    rewrite ^/osctrl$ /osctrl/ permanent;
    rewrite ^/osctrl(/.*)$ $1 last;
    # in location /
    sub_filter '<base href="/" />' '<base href="/osctrl/" />';
    sub_filter_once on;
  2. API: set service.basePath: "/osctrl" (or SERVICE_BASE_PATH). osctrl-api uses it only for the paths it sends to the browser: the session, CSRF, ID-token and auth-state cookie paths, SSO callback and unauthorized redirects, and the loginUrls in /api/v1/auth/methods.

Only plain path segments (letters, digits, -, _, ., ~) are accepted; an invalid value fails startup. If the two settings disagree, cookies are set for / and redirects go to the host root. With a prefix set, cookies are scoped to it, so browsers must use the prefixed URL; bearer-token clients are unaffected. The OIDC redirect URL and SAML ACS URL, and the callbacks registered at the IdP, must include the prefix, for example https://host/osctrl/api/v1/auth/oidc/callback; see Auth providers.

Set service.serviceConfigEnabled: true (osctrl-api only) to expose /api/v1/service-config and show the Service Config section in osctrl-frontend. Once enabled, operators can review and edit any of the sections above from the UI, persisted to the service_config table in the backend database.

  • This switch only gates the read/edit/apply UI and API surface. Configuration is always resolved the same way at every boot: the YAML file’s sections are seeded into service_config, and the stored rows are then resolved back over them — so the service always runs on the stored values, whether or not the API is enabled.
  • Pinned fields beat flags and environment variables; nothing else does. Saving a field from the Service Config page pins it: its stored value wins over flags, environment and YAML for that field only, while unpinned fields keep following their flag. The page marks pinned fields and lets you release them. Rows saved by older releases pin every field in their section until released. Pins keep applying after --service-config-enabled is switched off. If a flag such as --port seems ignored, look for “overrides flags, environment and YAML” in the startup log, which names the section and fields with both values.
  • Edited values can be written back to the on-disk YAML file, and a restart of osctrl-tls can be requested from osctrl-api (rate limited by rateLimits.serviceConfigApply) so it picks up the new values. The frontend shows a confirmation warning before requesting a restart, since it can disrupt in-flight osquery traffic.
version: 5
service:
listener: "0.0.0.0"
port: "9000"
host: "tls.example.com"
auth: "none"
logLevel: "info"
logFormat: "json"
db:
type: "postgres"
host: "127.0.0.1"
port: "5432"
name: "osctrl"
username: "postgres"
password: "postgres"
sslmode: "disable"
maxIdleConns: 20
maxOpenConns: 100
connMaxLifetime: 30
connRetry: 5
read:
enabled: false
redis:
host: "127.0.0.1"
port: "6379"
password: ""
db: 0
connRetry: 5
rateLimits:
enroll:
burst: 20
period: 1m
evictAfter: 10m
retryAfter: 60
maxBuckets: 0
logger:
type: "db"
loggerDBSame: true
alwaysLog: false
carver:
type: "db"
tls:
termination: false
osquery:
version: "5.12.1"
tablesFile: "data/5.12.1.json"
logger: true
config: true
query: true
carve: true
accelerated: false
console: false
fileExplorer: false
readOnly: false
metrics:
enabled: true
listener: "0.0.0.0"
port: "9090"
osctrld:
enabled: false
batchWriter:
writerBatchSize: 50
writerTimeout: 60s
writerBufferSize: 2000
debug:
enableHTTP: false
httpFile: "debug-http-tls.log"
showBody: false

All configuration values can be overridden using command-line flags. Use --help or -h to see all available flags for each service:

Terminal window
$ ./osctrl-tls --help
$ ./osctrl-api --help

Common flags:

  • --config or -c: Enable configuration from YAML file
  • --config-file or -C: Path to YAML configuration file
  • --db-host: Database host
  • --db-port: Database port
  • --db-read-enabled: Open a second connection to a read replica (also --db-read-host, --db-read-port, --db-read-user, --db-read-pass, --db-read-sslmode, --db-read-max-idle-conns, --db-read-max-open-conns; environment variables DB_READ_*)
  • --redis-host: Redis host
  • --listener or -l: Service listener
  • --port or -p: Service port
  • --trusted-proxies: Comma-separated CIDRs whose forwarded client IP headers are trusted
  • --geoip-db: Path to a MaxMind GeoLite2-Country .mmdb file for node country enrichment
  • --loggers: Comma-separated logger list for running multiple log sinks from seed configuration
  • --posture-enabled: Enable posture ingestion and API/frontend posture surfaces
  • --posture-query-prefix: Scheduled-query name prefix ingested as posture data
  • --service-config-enabled: Expose the service configuration API/frontend (osctrl-api only)
  • --log-sinks-enabled: Expose the log sinks API/frontend (default true)
  • --auth-providers-enabled: Expose the auth providers API/frontend (default true)
  • --alerts-enabled: Enable the alerting subsystem (default false, requires a restart to change)
  • --health-enabled: Enable service health/status reporting
  • --base-path: Public URL prefix the frontend and API are served under (osctrl-api only)
  • --vuln-enabled: Enable vulnerability monitoring (default false, requires a restart to change)
  • --vuln-osv-url, --vuln-kev-url, --vuln-ecosystems, --vuln-sync-hours, --vuln-retention-days, --vuln-max-download-mb, --vuln-nvd-enabled, --vuln-nvd-url, --vuln-nvd-api-key: Advisory feed settings (osctrl-api only)
  • --mcp-enabled, --mcp-allow-writes: Enable the hosted MCP endpoint on osctrl-api
  • --mfa-required, --mfa-issuer, --mfa-rpid, --mfa-origins: MFA settings (osctrl-api only)
  • --osquery-console: Enable the per-node interactive console
  • --query-dispatch-ttl: Cache TTL for empty distributed-query dispatch responses (osctrl-tls only)
  • --rate-limit-<name>-burst, --rate-limit-<name>-period, --rate-limit-<name>-evict-after, --rate-limit-<name>-retry-after, --rate-limit-<name>-max-buckets: Rate limit tuning, where <name> is login, pre-auth, service-config-apply (osctrl-api) or enroll (osctrl-tls)

Configuration values can also be set using environment variables. Each flag has a corresponding environment variable (see --help output for details).

Example:

Terminal window
export SERVICE_PORT="9000"
export DB_HOST="postgres.example.com"
export REDIS_HOST="redis.example.com"
export SERVICE_TRUSTED_PROXIES="10.0.0.0/8,172.16.0.0/12"
export SERVICE_GEOIP_DB="/var/osctrl/GeoLite2-Country.mmdb"
export SERVICE_POSTURE_ENABLED="true"
export SERVICE_ALERTS_ENABLED="true"
export SERVICE_HEALTH_ENABLED="true"
export SERVICE_CONFIG_ENABLED="true"
export MCP_ENABLED="true"

If you’re upgrading from the old JSON-based configuration, you’ll need to consolidate your separate JSON files into a single YAML file:

Old (JSON):

  • tls.json - Service settings
  • db.json - Database settings
  • redis.json - Redis settings
  • jwt.json - JWT settings
  • saml.json - SAML settings
  • logger_tls.json - Logger settings
  • carver_tls.json - Carver settings

New (YAML):

  • tls.yaml - All settings in one file

All configuration is now organized into sections within a single YAML file, making it easier to manage and version control your osctrl deployment configuration.