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.
Single YAML Configuration
Section titled “Single YAML Configuration”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:
tls.yamlfor osctrl-tlsapi.yamlfor osctrl-api
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: 4 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.
Generating and Validating Configuration
Section titled “Generating and Validating Configuration”Each osctrl binary includes built-in commands to help you create and validate configuration files:
Generate Configuration
Section titled “Generate Configuration”To generate a valid YAML configuration file with default values:
./osctrl-tls config-generate -f tls.yaml./osctrl-api config-generate -f api.yamlThis 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
Verify Configuration
Section titled “Verify Configuration”To validate an existing configuration file before starting the service:
./osctrl-tls config-verify --file tls.yaml./osctrl-api config-verify --file api.yamlThe 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.
Configuration Structure
Section titled “Configuration Structure”The YAML configuration file contains the following main sections:
Service Configuration
Section titled “Service Configuration”Basic service settings including network listener, ports, authentication, and logging:
version: 4 # 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 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 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 modelogSinksEnabledandauthProvidersEnableddefault totrueand gate the newer Log Sinks and Auth Providers API routes and frontend sections, independently ofserviceConfigEnabled.alertsEnableddefaults tofalse(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 — bothosctrl-apiandosctrl-tlswire their alert routes/ingest hooks at boot.healthEnabledturns on service heartbeats and/api/v1/health/status.eventsEnabledandeventsNamespaceenable best-effort Server-Sent Events live-update hints through/api/v1/events. Set both onosctrl-apiandosctrl-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 andnode_keyinstead. For osctrl-api,nonerequiresOSCTRL_INSECURE_NO_AUTH=1in 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.
Database Configuration
Section titled “Database Configuration”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 onlyRedis Configuration
Section titled “Redis Configuration”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)Rate Limits Configuration
Section titled “Rate Limits Configuration”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: 0osctrl-api only enforces login, preAuth and serviceConfigApply; osctrl-tls only enforces enroll. 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.
Logger Configuration
Section titled “Logger Configuration”Logging destination and settings:
logger: type: "db" # Logger type: db, splunk, graylog, s3, file, kinesis, kafka, logstash, elastic 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" 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 gzipLogger types:
db- Store logs in the backend databasesplunk- Send logs to Splunk HECgraylog- Send logs to Graylog GELF endpoints3- Upload logs to AWS S3file- Write logs to local file with rotationkinesis- Send logs to AWS Kinesiskafka- Send logs to Kafka topicslogstash- Send logs to Logstashelastic- Send logs directly to Elasticsearch
Log Sinks
Section titled “Log Sinks”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.
Carver Configuration
Section titled “Carver Configuration”File carving storage settings:
carver: type: "db" # Carver type: db, s3, local
# S3 carver settings (if type: s3) s3: bucket: "osctrl-carves" region: "us-east-1" accessKey: "your-access-key" secretAccessKey: "your-secret-key"
# Local carver settings (if type: local) local: carvesDir: "/var/osctrl/carves" # Directory to store carved filesCarver types:
db- Store carved files in the databases3- Upload carved files to AWS S3local- Store carved files in local directory
TLS Configuration
Section titled “TLS Configuration”TLS termination settings:
tls: termination: true # Enable TLS termination certificateFile: "/path/to/cert.pem" keyFile: "/path/to/key.pem"Osquery Configuration
Section titled “Osquery Configuration”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 changesconsolegates 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 requiresquery: true); on osctrl-tls it controls whether active console sessions can request accelerated query polling.fileExplorerbehaves the same way for the per-node file explorer, and no longer requiresaccelerated: true— onlyquery: true.sha256is 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.
MCP Endpoint Configuration
Section titled “MCP Endpoint Configuration”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 toolsIt 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.
Metrics Configuration
Section titled “Metrics Configuration”Prometheus metrics endpoint settings (TLS service only):
metrics: enabled: false # Enable Prometheus metrics listener: "0.0.0.0" port: "9090"JWT Configuration
Section titled “JWT Configuration”JWT authentication settings:
jwt: jwtSecret: "your-jwt-secret" # Secret for signing JWT tokens hoursToExpire: 3 # Token expiration time in hoursSAML Configuration
Section titled “SAML Configuration”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 loginRegister 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.
Osctrld Configuration
Section titled “Osctrld Configuration”Settings for the osctrld endpoints exposed by osctrl-tls:
osctrld: enabled: false # Enable osctrld endpointsBatch Writer Configuration
Section titled “Batch Writer Configuration”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 sizeDebug Configuration
Section titled “Debug Configuration”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 dumpsosctrl-mcp Configuration
Section titled “osctrl-mcp Configuration”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.
Service Configuration API and Restart
Section titled “Service Configuration API and Restart”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. - Edited values can be written back to the on-disk YAML file, and a restart of
osctrl-tlscan be requested fromosctrl-api(rate limited byrateLimits.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.
Example Configuration Files
Section titled “Example Configuration Files”osctrl-tls (tls.yaml)
Section titled “osctrl-tls (tls.yaml)”version: 4
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
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: falseCommand-Line Flags
Section titled “Command-Line Flags”All configuration values can be overridden using command-line flags. Use --help or -h to see all available flags for each service:
$ ./osctrl-tls --help$ ./osctrl-api --helpCommon flags:
--configor-c: Enable configuration from YAML file--config-fileor-C: Path to YAML configuration file--db-host: Database host--db-port: Database port--redis-host: Redis host--listeneror-l: Service listener--portor-p: Service port--trusted-proxies: Comma-separated CIDRs whose forwarded client IP headers are trusted--geoip-db: Path to a MaxMind GeoLite2-Country.mmdbfile 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-apionly)--log-sinks-enabled: Expose the log sinks API/frontend (defaulttrue)--auth-providers-enabled: Expose the auth providers API/frontend (defaulttrue)--alerts-enabled: Enable the alerting subsystem (defaultfalse, requires a restart to change)--health-enabled: Enable service health/status reporting--mcp-enabled,--mcp-allow-writes: Enable the hosted MCP endpoint onosctrl-api--mfa-required,--mfa-issuer,--mfa-rpid,--mfa-origins: MFA settings (osctrl-apionly)--osquery-console: Enable the per-node interactive console--query-dispatch-ttl: Cache TTL for empty distributed-query dispatch responses (osctrl-tlsonly)--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>islogin,pre-auth,service-config-apply(osctrl-api) orenroll(osctrl-tls)
Environment Variables
Section titled “Environment Variables”Configuration values can also be set using environment variables. Each flag has a corresponding environment variable (see --help output for details).
Example:
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"Migration from JSON to YAML
Section titled “Migration from JSON to YAML”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 settingsdb.json- Database settingsredis.json- Redis settingsjwt.json- JWT settingssaml.json- SAML settingslogger_tls.json- Logger settingscarver_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.