System Settings
EnterpriseConfigure NetStacks Controller: authentication, security, SSH/terminal, collection, integrations, plugins, HA, and the license — via the Settings UI and key-value API.
Overview
System Settings in the NetStacks Controller provide centralized, key-value configuration for platform-wide behavior. The Controller is the multi-user, commercial side of NetStacks; the single-user Terminal does not have these settings. Settings are edited in the Admin UI under Settings, and exposed through a generic key-value API.
The Settings UI is organized into these categories:
- General — application name, base URL, timezone, date format, items-per-page, and the session-timeout warning.
- Authentication — local login, LDAP/Active Directory, OIDC, and session/JWT lifetimes (see Authentication).
- Security — rate limiting for requests and failed logins.
- Collection — config-backup collection defaults (concurrency, retries, timeouts, default credential).
- Integrations — external systems such as NetBox.
- Jumpboxes, TLS / SSL, External SSH Proxy, and Terminal Sessions — connectivity and proxy behavior.
- Plugins — plugin system configuration and per-plugin settings (see Plugin System).
- HA / Clustering — advertise-address field and session lifecycle for high-availability deployments (the operative advertise address is the
ADVERTISE_ADDRenvironment variable). - AI and AI Data Security — provider configuration and credential redaction (see LLM Configuration).
- Token Budget — monthly LLM token limits.
- License — activate or view the Controller license key.
Viewing and changing system settings requires the system.manage permission (label System Settings). This permission also gates license activation, backup/restore, and plugin management. Grant it through a role — see Roles & Permissions.
How It Works
Settings Storage
Settings live in PostgreSQL as rows in a settings table. Each row has a key (dot-separated, e.g. auth.ldap.enabled), a value (JSONB — boolean, number, string, or object), a category (the first segment of the key), an optional description, and an is_secret flag. Secret values (such as auth.jwt_secret or auth.ldap.bind_password) are stored with is_secret = true.
Two API Surfaces
- Admin API (
/api/admin/settings) — lists settings grouped by category, fetches a single key, and updates a key. Used by the Admin UI. Requires authentication. - Terminal-compatible API (
/api/settings/{key}) — a simpler interface that returns the raw JSON value (not wrapped) and accepts a raw JSON body on PUT. Used by client applications for preferences.
Upsert Behavior
A PUT to a key that does not yet exist creates it: the category is derived from the key prefix and is_secret defaults to false. A GET for a key that was never set returns 404 Not Found — settings are created on first write, not on first read.
The Controller accepts writes to ssh.host_key_checking for backward compatibility but ignores the value — strict SSH host-key checking is always on and cannot be disabled through settings.
Setting Categories
Settings are grouped by the first segment of the key. The table below lists the real categories and representative seeded keys shipped with the Controller. This is not exhaustive — use GET /api/admin/settings to enumerate everything in your instance.
| Category | Example Keys | Purpose |
|---|---|---|
general | general.app_name, general.base_url, general.timezone, general.date_format | Organization and display defaults |
auth | auth.local.enabled, auth.ldap.enabled, auth.oidc.enabled, auth.jwt_secret | Login methods and JWT signing |
session | session.jwt_expiry_hours, session.refresh_token_days, session.access_token_hours | Token lifetimes |
security | security.rate_limit.requests_per_minute, security.rate_limit.failed_login_limit | Rate limiting and lockout |
collection | collection.max_concurrent, collection.retry_attempts, collection.timeout_seconds | Config-backup collection defaults |
integrations | integrations.netbox.verify_ssl, integrations.netbox.default_credential_id | External system connections |
ssh | ssh.proxy_enabled, ssh.listen_address, ssh.idle_timeout_minutes, ssh.session_recording | External SSH proxy (port 2222) |
terminal | terminal.browser_enabled, terminal.idle_timeout_minutes, terminal.reconnect_window_seconds | Browser terminal sessions |
plugins | plugins.enabled | Plugin system master switch |
jumpbox / tunnels / tls | jumpbox.enabled, tunnels.enabled, tls.sans | Connectivity and certificates |
ai | ai.provider_config, ai.terminal_mode_enabled, ai.config_changes_enabled | AI provider and feature gates |
license | license.enterprise_key, license.validation_url | License key and validation endpoint |
NetStacks does not use a single “feature toggles” screen. Instead, individual boolean settings act as switches — for example plugins.enabled, jumpbox.enabled, tunnels.enabled, terminal.browser_enabled, and ai.terminal_mode_enabled. Flip them in the relevant Settings category or via the API.
License
The single-user NetStacks Terminal is free and open source — it has no license key and no license screen. Only the multi-user Controller uses a license, configured under Settings → License.
To activate, paste your enterprise license key into the field and click Activate License. The Controller verifies the key with the NetStacks license server, stores the resulting record, and shows the current tier, seat usage, and status. Two endpoints back this screen:
POST /api/admin/license/activate— submit a license key.GET /api/admin/license/status— read the current tier, seat counts, expiration, and validation status.
If no license has been activated, the status endpoint returns a pending state so the UI can show the activation form. Only team and enterprise tiers are accepted by the Controller. The Controller revalidates the license with the license server in the background roughly once a day; if it cannot reach the server, the license moves to a grace_period status rather than failing immediately.
License validation is a Controller-to-license-server check. The NetStacks Terminal and Local Agent contain no telemetry and no license check — see Source Code & Cryptography.
HA & Session Settings
The HA / Clustering category exposes a small set of high-availability options. The heavy lifting — the advertise address, Valkey connection, Sentinel topology, and node coordination — is configured with environment variables in the Controller's Docker configuration. The panel surfaces an ha.advertise_addr settings field, but the running Controller reads the ADVERTISE_ADDRenvironment variable for share links and inter-instance communication.
| Knob | Where it lives | Notes |
|---|---|---|
| Advertise address | ADVERTISE_ADDR (env var) | Public URL of this instance; used for share links and inter-instance communication. Unset = auto-detect (https://{host}:{port}). The HA / Clustering panel also shows an ha.advertise_addr field, but the running Controller reads the env var. |
| Reconnect window | terminal.reconnect_window_seconds (DB setting) | How long a disconnected session can be resumed across instances. |
| Idle timeout | terminal.idle_timeout_minutes (DB setting) | Close sessions after inactivity. |
| Valkey (session store) | VALKEY_URL (env var) | Required for HA, session sharing, and cross-instance state. |
| Valkey Sentinel | VALKEY_SENTINEL_URLS, VALKEY_SENTINEL_MASTER (env vars) | Automated failover for the Valkey tier. |
For the full multi-instance topology — Nginx load balancing, external PostgreSQL, Valkey Sentinel, and TLS — see HA Deployment.
Connected Status Popover
The Connected indicator in the top-right of the Admin UI is interactive. Clicking it opens a popover summarizing Controller health, drawn largely from GET /api/admin/system/info and the health endpoints.
What it shows
- Version — the running Controller version.
- Uptime — seconds since the process started.
- Database size — current PostgreSQL database size.
- Users and Devices — total counts in the organization.
- Enabled plugins — plugins currently in the
enabledstate. - HA status — standalone vs. high-availability and Valkey connectivity, surfaced from the HA status endpoint.
The same data is available programmatically at GET /api/admin/system/info, and HA/health details at GET /api/admin/health and GET /api/admin/ha-status.
Step-by-Step Guide
Changing a General Setting
- Open the Admin UI and go to Settings → General.
- Edit a field — for example the application name, base URL, timezone, or date format.
- Click the inline Save control. The change is written immediately; there is no separate apply step for individual keys.
Activating a Controller License
- Go to Settings → License.
- Paste your enterprise or team license key into the key field.
- Click Activate License. The Controller verifies the key with the license server and stores the result.
- On success, the status dashboard shows your tier, seat usage, and expiration.
Enabling LDAP or OIDC
- Go to Settings → Authentication.
- Toggle
auth.ldap.enabledorauth.oidc.enabledand fill in the provider fields (server URL, base DN, client ID, etc.). - Save. For the full provider walkthrough, see Authentication.
Managing Plugins
- Ensure
plugins.enabledis on under Settings → Plugins. - Use the Plugins panel to enable, disable, and configure individual plugins (e.g. Alerts, Incidents).
- See Plugin System for details.
Settings updates, license activations, and plugin state changes are recorded in the audit log with user attribution and a timestamp. Use it to answer “who changed what, and when.”
Code Examples
Admin settings endpoints live under /api/admin/settings and require an authenticated request (JWT bearer token) from a user whose role includes system.manage.
List All Settings (Grouped by Category)
curl https://controller.example.com/api/admin/settings \
-H "Authorization: Bearer $TOKEN"Response (truncated):
{
"settings": {
"auth": [
{
"key": "auth.ldap.enabled",
"value": false,
"category": "auth",
"description": "Enable LDAP/Active Directory authentication",
"is_secret": false
},
{
"key": "auth.oidc.provider_url",
"value": "",
"category": "auth",
"description": "OIDC provider issuer URL (e.g., https://company.okta.com)",
"is_secret": false
}
],
"session": [
{
"key": "session.jwt_expiry_hours",
"value": 24,
"category": "session",
"description": "JWT token expiry in hours",
"is_secret": false
}
]
}
}Get a Single Setting (Admin API)
curl https://controller.example.com/api/admin/settings/session.jwt_expiry_hours \
-H "Authorization: Bearer $TOKEN"Response (wrapped):
{
"setting": {
"key": "session.jwt_expiry_hours",
"value": 24,
"category": "session",
"description": "JWT token expiry in hours",
"is_secret": false
}
}Update Settings (Admin API)
The admin PUT takes a JSON object with a value field. It upserts — creating the key if it does not exist.
# Enable LDAP
curl -X PUT https://controller.example.com/api/admin/settings/auth.ldap.enabled \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"value": true}'
# Set JWT expiry to 12 hours
curl -X PUT https://controller.example.com/api/admin/settings/session.jwt_expiry_hours \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"value": 12}'
# Set the items-per-page display default
curl -X PUT https://controller.example.com/api/admin/settings/general.items_per_page \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"value": 50}'Terminal-Compatible API (Raw Values)
The /api/settings/{key} endpoints return and accept raw JSON values — no wrapper object. PUT upserts and returns 204 No Content.
# GET returns the raw value
curl https://controller.example.com/api/settings/general.timezone \
-H "Authorization: Bearer $TOKEN"
# => "UTC"
# PUT a raw JSON body (not { "value": ... })
curl -X PUT https://controller.example.com/api/settings/general.timezone \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '"America/New_York"'Check License Status
curl https://controller.example.com/api/admin/license/status \
-H "Authorization: Bearer $TOKEN"Response:
{
"tier": "enterprise",
"seats_total": 50,
"seats_used": 12,
"expires_at": "2026-12-31T23:59:59Z",
"status": "valid",
"warning_level": "none",
"last_validated_at": "2026-06-15T08:00:00Z",
"active_sessions": []
}Activate a License
curl -X POST https://controller.example.com/api/admin/license/activate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"license_key": "XXXX-XXXX-XXXX-XXXX-XXXX"}'System Info (Connected Status Data)
curl https://controller.example.com/api/admin/system/info \
-H "Authorization: Bearer $TOKEN"Response:
{
"version": "0.2.40",
"database_size": "184 MB",
"user_count": 18,
"device_count": 642,
"uptime_seconds": 432000,
"plugins_enabled": ["alerts", "incidents"]
}Questions & Answers
- What permission do I need to edit system settings?
- The
system.managepermission. It also covers license activation, database backup/restore, and plugin management. Add it to a role under Roles & Permissions and assign that role to the user. - Does NetStacks send email / have SMTP settings?
- No. The Controller does not ship an SMTP/email configuration. There is no
smtp.*category and no “send test email” flow. Alerts and notifications are delivered through the alerting plugins — see the Alert Pipeline. - Where do I configure high availability?
- A few session knobs live in Settings → HA / Clustering (
terminal.reconnect_window_seconds,terminal.idle_timeout_minutes). The advertise address, Valkey connection, and Sentinel topology are set with environment variables (ADVERTISE_ADDR,VALKEY_URL,VALKEY_SENTINEL_URLS,VALKEY_SENTINEL_MASTER). Full details are on the HA Deployment page. - How do I turn a feature on or off?
- Flip the corresponding boolean setting in the relevant category — for example
plugins.enabled,jumpbox.enabled,tunnels.enabled,terminal.browser_enabled, orai.terminal_mode_enabled. There is no single feature-toggle dashboard. - Why does a GET for my setting return 404?
- Settings are created on first write. If a key has never been
PUT, aGETreturns404 Not Found. Write the value once (admin or terminal API) and subsequent reads will succeed. - What is the difference between the admin and terminal settings APIs?
- The admin API (
/api/admin/settings) returns objects withkey/value/categorymetadata and takes{"value": ...}on PUT. The terminal-compatible API (/api/settings/{key}) returns and accepts the raw JSON value with no wrapper. - Can I disable strict SSH host-key checking via settings?
- No. The
ssh.host_key_checkingkey is accepted for backward compatibility but ignored — strict host-key checking is always enforced.
Troubleshooting
Setting Change Did Not Apply
- Confirm the write succeeded by reading the key back:
curl .../api/admin/settings/auth.ldap.enabled - Refresh the Admin UI — the page may have cached the previous value.
- Verify your token's role includes
system.manage; without it, writes are rejected. - For changes that affect long-lived processes, restart the Controller container:
docker compose restart controller
License Will Not Activate
- Check the key is pasted without surrounding whitespace or line breaks.
- Only
teamandenterprisetiers are accepted by the Controller; other tiers are rejected. - Expired keys are refused at activation time. Confirm the expiry on your key.
- A
502from the activate endpoint means the Controller could not reach the license server — check outbound HTTPS from the Controller host.
License Shows “grace_period”
- This means the daily background revalidation could not confirm the license (server unreachable or validation failed). Restore outbound connectivity and the next cycle will return it to
valid. - Dev licenses without a server ID are skipped during revalidation by design.
Setting Not Found (404)
- The key has never been written. PUT it once to create it (both APIs upsert on write).
- Keys are case-sensitive and dot-separated — double-check spelling, e.g.
session.jwt_expiry_hoursnotsession.jwt_expiry.
Plugin Will Not Enable
- Confirm
plugins.enabledistrueunder Settings → Plugins. - Check Controller logs for the plugin's startup output:
docker compose logs controller - See Plugin System for plugin-specific requirements.
Related Features
- User Management — Create and manage the accounts governed by these settings.
- Roles & Permissions — Grant
system.manageto control who can edit settings. - Authentication (LDAP/OIDC) — Configure the
auth.*provider settings in depth. - Audit Logs — Track every settings change with user attribution and timestamps.
- HA Deployment — Configure Valkey, Sentinel, and multi-instance Controllers.
- Plugin System — Enable and configure plugins gated by
plugins.enabled. - LLM Configuration — Configure AI providers behind the
ai.*settings. - Source Code & Cryptography — Why the single-user product has no license check or telemetry.