Configuration
How to configure Servonaut — in the app for day-to-day changes, and through
~/.servonaut/config.json for automation, headless machines, and full control.
Servonaut creates ~/.servonaut/config.json on first run with sensible defaults.
Edit it with any text editor — changes take effect the next time you launch the TUI.
When you upgrade Servonaut, older config files are migrated automatically, so a hand-edited
config keeps working across releases.
Changing settings in the app
You rarely need to hand-edit JSON. Most settings on this page have an in-app home,
and the TUI writes your changes back to config.json for you:
- Settings (🔧) — scan rules, AI provider selection and per-provider API keys, AbuseIPDB key, OVH credentials, and Memory Sync per-feature settings.
- Custom Servers (💻) — add, edit, and remove non-AWS servers, including per-server extra SSH options.
- SSH Keys (🔑) — set the default SSH key and per-instance key mappings,
check SSH agent status, and auto-discover keys from
~/.ssh. - Account → Login — sign in to servonaut.dev; this is all the setup the hosted Servonaut AI and relay features need.
The reference below documents every key in the file — useful when you provision machines with scripts, manage dotfiles, or run Servonaut on headless boxes where the TUI never opens.
Full example config
{
"default_key": "~/.ssh/id_ed25519",
"default_username": "ec2-user",
"terminal_emulator": "auto",
"aws": {
"default_region": "us-east-1"
},
"custom_servers": [
{
"name": "prod-web-1",
"host": "192.168.1.100",
"username": "deploy",
"ssh_key": "~/.ssh/id_ed25519",
"port": 22,
"provider": "Hetzner",
"tags": {"env": "production", "role": "web"}
}
],
"ai_provider": {
"provider": "anthropic",
"anthropic_api_key": "$ANTHROPIC_API_KEY",
"openai_api_key": "$OPENAI_API_KEY",
"gemini_api_key": "$GEMINI_API_KEY",
"ollama_api_key": "",
"model": "",
"base_url": ""
},
"ovh": {
"enabled": true,
"endpoint": "ovh-eu",
"application_key": "$OVH_APPLICATION_KEY",
"application_secret": "$OVH_APPLICATION_SECRET",
"consumer_key": "$OVH_CONSUMER_KEY",
"cloud_project_ids": ["$OVH_PROJECT_ID"]
},
"hetzner": {
"enabled": true,
"api_token": "$HCLOUD_TOKEN",
"default_image": "ubuntu-22.04",
"default_server_type": "cx23",
"default_location": "fsn1",
"require_ssh_keys_on_create": true
},
"mcp": {
"guard_level": "standard",
"audit_path": "~/.servonaut/mcp_audit.jsonl",
"max_output_lines": 500
},
"relay": {
"heartbeat_interval": 30,
"ai_tool_auto_approve": "standard"
},
"log_viewer_default_paths": [
"/var/log/nginx/access.log",
"/var/log/nginx/error.log",
"/var/log/auth.log"
]
}
AWS settings
AWS needs no Servonaut-specific credentials — the unified instance list reads your standard
AWS CLI configuration (~/.aws/credentials and ~/.aws/config) and
scans every region for EC2 instances. Results are cached so the list opens instantly;
press R in the instance list to force a refresh from AWS.
The aws block tunes that behaviour:
| Key | Type | Default | Description |
|---|---|---|---|
aws.enabled |
boolean | true |
Toggles AWS EC2 discovery in the unified instance list. |
aws.default_region |
string | "us-east-1" |
Region used for AWS operations that need a single region. Instance discovery still covers all regions. |
aws.cache_ttl_seconds |
integer | 3600 |
How long cached instance data is considered fresh. The cache is served immediately and refreshed in the background when stale. |
The aws block also accepts advanced keys for S3-compatible object storage
(including custom endpoints such as OVH Object Storage) and cross-account role assumption.
{
"aws": {
"enabled": true,
"default_region": "eu-west-1",
"cache_ttl_seconds": 3600
}
}
Custom servers
Custom servers let you manage any machine — DigitalOcean, bare metal, on-prem VMs —
alongside your cloud instances, with full SSH, SCP, and log-viewer support.
The easiest way to add one is the Custom Servers (💻) screen in the TUI,
which walks you through every field below, including per-server extra SSH options as a
multi-line input. For scripted setups, edit the custom_servers array directly:
| Field | Required | Type | Default | Description |
|---|---|---|---|---|
name | Yes | string | — | Unique server identifier shown in the TUI server list. |
host | Yes | string | — | Hostname or IP address. |
username | No | string | "root" | SSH username (e.g. ubuntu, ec2-user). |
ssh_key | No | string | "" | Path to SSH private key. Falls back to top-level default_key when empty. |
port | No | integer | 22 | SSH port. |
provider | No | string | "" | Provider label (e.g. "DigitalOcean", "Hetzner"). |
group | No | string | "" | Optional grouping label. |
tags | No | object | {} | Arbitrary key/value pairs for filtering. E.g. {"env":"prod","role":"db"}. |
extra_ssh_options | No | array | [] | Extra -o KEY=VALUE pairs for SSH/SCP. Useful for legacy hosts needing e.g. HostKeyAlgorithms=+ssh-rsa. |
SSH defaults
Manage SSH keys in the app from the SSH Keys (🔑) screen: set the default key,
map keys to individual instances, check your SSH agent status, and auto-discover keys from
~/.ssh. In the file, Servonaut reads its key and username defaults from top-level
fields on the main config (below); connection tuning — keepalives and timeouts — lives in a
dedicated ssh block.
| Key | Type | Default | Description |
|---|---|---|---|
default_key |
string | "" |
Default SSH private-key path used when a server has no ssh_key set. |
default_username |
string | "ec2-user" |
Fallback SSH username when a server or provider does not define its own. |
terminal_emulator |
string | "auto" |
Terminal emulator used when launching SSH sessions from the TUI. "auto" picks the first installed terminal Servonaut recognises. |
instance_keys |
object | {} |
Per-instance SSH-key overrides. Keys are instance IDs; values are key paths. |
connection_profiles |
array | [] |
Named SSH connection profiles for bastion / ProxyJump setups. Each entry takes name, bastion_host, bastion_user, bastion_key, username, proxy_command, ssh_port, and extra_ssh_options. |
connection_rules |
array | [] |
Rules that apply a connection_profiles entry to matching instances. Each rule takes name, match_conditions, and profile_name. |
Bastion / ProxyJump example
{
"default_key": "~/.ssh/id_ed25519",
"connection_profiles": [
{
"name": "via-bastion",
"bastion_host": "bastion.example.com",
"bastion_user": "ec2-user",
"ssh_port": 22
}
],
"connection_rules": [
{
"name": "private-subnet-via-bastion",
"match_conditions": {"name_contains": "private"},
"profile_name": "via-bastion"
}
]
}
Connection keepalives
Long-running, quiet SSH sessions — an agent or MCP tool that opens a connection and then
waits on a slow command, a held-open log tail, a bastion hop through an idle firewall — used
to get silently reaped by the network in between. The ssh block sends keepalives
on every SSH and SCP connection (including both bastion hops) so those sessions stay up. The
settings are emitted as standard OpenSSH options:
-o ServerAliveInterval=30 -o ServerAliveCountMax=5 -o TCPKeepAlive=yes -o ConnectTimeout=15.
The block is additive — there's no config migration, and an absent block means these defaults.
| Key | Type | Default | Description |
|---|---|---|---|
ssh.server_alive_interval |
integer | 30 |
Seconds between application-layer keepalive probes (ServerAliveInterval). 0 disables app-layer keepalives entirely. |
ssh.server_alive_count_max |
integer | 5 |
How many unanswered probes before the link is declared dead (ServerAliveCountMax). With the defaults, the client waits 30 × 5 = 150 s of silence before giving up. |
ssh.tcp_keepalive |
boolean | true |
Sends TCP-level keepalives (TCPKeepAlive=yes) in addition to the application-layer probes. |
ssh.connect_timeout |
integer | 15 |
Seconds to wait for the initial connection before failing (ConnectTimeout). 0 uses the OS default, which can hang for minutes on an unreachable host. |
Per-host extra_ssh_options (on a custom server or a connection profile) still
layer on top of these, so you can refine or override the keepalive behaviour for an individual
host.
{
"ssh": {
"server_alive_interval": 30,
"server_alive_count_max": 5,
"tcp_keepalive": true,
"connect_timeout": 15
}
}
OVH Cloud settings
Enable OVH integration to merge OVH Public Cloud, VPS, and dedicated servers into the unified
instance list, and to unlock the OVH Manage screen (lifecycle toolbar, region-first create wizard)
plus the OVH sidebar sections for SSH keys, DNS zones, IPs, block storage, and billing.
The recommended setup path is the guided wizard — run servonaut --setup-ovh and it
captures credentials for either the classic 3-key flow or an OAuth2 service account.
You can also enter credentials on the Settings screen, or set the keys manually:
| Key | Type | Default | Description |
|---|---|---|---|
ovh.enabled |
boolean | false |
Toggles OVH discovery and the OVH Manager screen. |
ovh.endpoint |
string | "ovh-eu" |
OVH API endpoint. Common values: ovh-eu, ovh-us, ovh-ca. |
ovh.application_key |
string | "" |
OVH application key (classic 3-key auth). |
ovh.application_secret |
string | "" |
OVH application secret. Supports $ENV_VAR and file: prefixes. |
ovh.consumer_key |
string | "" |
OVH consumer key. Supports $ENV_VAR and file: prefixes. |
ovh.client_id |
string | "" |
OAuth2 service-account client ID (alternative to the 3-key flow). |
ovh.client_secret |
string | "" |
OAuth2 service-account client secret. Supports $ENV_VAR and file: prefixes. |
ovh.cloud_project_ids |
array | [] |
Public Cloud project IDs to include in discovery and account-scoped MCP tools. |
ovh.include_dedicated |
boolean | true |
Fetch OVH dedicated servers. |
ovh.include_vps |
boolean | true |
Fetch OVH VPS instances. |
ovh.include_cloud |
boolean | true |
Fetch OVH Public Cloud instances. |
ovh.default_ssh_key |
string | "" |
Default SSH key path used when SSH-ing into OVH-discovered instances. |
ovh.default_username |
string | "" |
Override default SSH username. Empty means auto-pick by provider type. |
Full per-screen UX reference lives on the OVH Cloud docs page.
Hetzner Cloud settings
Enable Hetzner Cloud as a first-class provider: discovered servers join the unified instance
list, and the Hetzner Manage screen gives you a full-lifecycle toolbar (create, power on/off,
shutdown, reboot, delete) plus a project SSH-key registry. The token resolves from
hetzner.api_token, then $HCLOUD_TOKEN, then ~/.config/hcloud/token —
the same chain used by the official hcloud CLI and the Terraform provider.
Run servonaut hetzner test-connection to verify the chain resolves and the API
is reachable.
| Key | Type | Default | Description |
|---|---|---|---|
hetzner.enabled |
boolean | false |
Toggles Hetzner discovery and the Hetzner Manager screen. |
hetzner.api_token |
string | "" |
Hetzner Cloud API token (Read & Write). Supports $ENV_VAR and file: prefixes; leave empty to fall through to $HCLOUD_TOKEN / the hcloud token file. |
hetzner.default_image |
string | "ubuntu-22.04" |
Default OS image for the create wizard. |
hetzner.default_server_type |
string | "cx23" |
Default server type. Hetzner deprecates types per location — keep this in sync with what's available in your default location. Browse the catalogue with servonaut hetzner server-types. |
hetzner.default_location |
string | "fsn1" |
Default datacentre code. Available: "fsn1", "nbg1", "hel1", "ash", "hil". |
hetzner.default_username |
string | "root" |
SSH username for Hetzner-created instances. Hetzner images ship with root as the only pre-provisioned account. |
hetzner.default_hetzner_ssh_key |
string | "" |
Hetzner-side SSH key name or numeric ID — must already be registered with Hetzner Cloud. NOT a local file path. |
hetzner.default_local_ssh_key |
string | "" |
Local file path used for ssh -i when connecting INTO created servers. Falls back to default_key when empty. |
hetzner.require_ssh_keys_on_create |
boolean | true |
When true, refuses to create a server with no SSH keys. Hetzner would otherwise spawn one with a random root password the CLI can't recover, leaving a billed unreachable box. |
hetzner.cost_alert_threshold |
number | 0.0 |
Optional monthly EUR ceiling. 0.0 disables the alert. |
Full per-screen UX reference lives on the Hetzner Cloud docs page.
AI provider settings
Pick and configure your AI provider in the app: the first-run picker offers the choice when you
first open the AI chat, the Settings (🔧) screen holds the provider selector and
per-provider API keys, and the chat panel header (toggle with F2) lets you switch
provider per-session. For scripts, override per invocation with --ai-provider <name>
(or the SERVONAUT_AI_PROVIDER environment variable), and run
servonaut ai provider reset to clear the saved preference so the first-run picker
shows again.
In the file, the ai_provider block holds one shared provider selector
plus per-provider API-key slots, so you can keep multiple providers configured at once and switch
between them at runtime. Servonaut AI on Solo and Teams plans is the zero-config path — no key needed.
| Key | Type | Default | Description |
|---|---|---|---|
ai_provider.provider |
string | "openai" |
Active provider. One of: "openai", "anthropic", "gemini", "ollama", "servonaut". |
ai_provider.model |
string | "" |
Override the provider's default model. Empty string means "use provider default". See each provider's docs for current model names. |
ai_provider.base_url |
string | "" |
Override the provider's base URL. Empty means "use provider default". Used most often with Ollama (e.g. http://localhost:11434 for local; Ollama Cloud base URL for cloud). |
ai_provider.anthropic_api_key |
string | "" |
Anthropic API key. Supports $ENV_VAR and file:/path/to/key. |
ai_provider.openai_api_key |
string | "" |
OpenAI API key. Supports $ENV_VAR and file: prefixes. |
ai_provider.gemini_api_key |
string | "" |
Google AI Studio API key (used for Gemini models). |
ai_provider.ollama_api_key |
string | "" |
Ollama Cloud API key. Leave empty for a local Ollama install — local instances need no key. |
ai_provider.max_tokens |
integer | 4096 |
Maximum tokens to request per response. |
ai_provider.temperature |
number | 0.3 |
Sampling temperature. |
ai_provider.provider_preference |
string / null | null |
Explicit provider preference set by the user (e.g. via the first-run picker). Falls back to provider when null. |
ai_provider.local_fallback_provider |
string / null | null |
Provider name to fall back to when Servonaut AI is unavailable. null disables automatic fallback (privacy-preserving default). Set to "ollama" for on-device prompts or any other supported provider name. |
Never hard-code API keys in config.json. Use environment variable or file references:
Environment references also resolve from ~/.secrets/servonaut.env, which
Servonaut loads automatically on startup.
Solo and Teams subscribers don't need to set any of the keys above. After signing in —
TUI Account → Login, or servonaut login on headless boxes — the chat
panel uses Servonaut AI automatically. Model selection and availability are handled for you,
within your plan's included AI allowance; your quota and top-up balance show inline in the
chat panel, or run servonaut ai quota from a script.
MCP server settings
The mcp block governs what AI agents may do on your fleet when you connect them
through Servonaut's MCP server (servonaut --mcp, or one-command install into a
coding agent with servonaut --mcp-install claude|cursor|windsurf|opencode|vscode|all).
The same guard tiers also bound what the relay listener will auto-approve for hosted AI chats
(see Relay settings). Regardless of guard level, a built-in
blocklist rejects destructive shell patterns, and every tool call is written to a local
audit log you can review at any time.
| Key | Type | Default | Description |
|---|---|---|---|
mcp.guard_level |
string | "standard" |
Controls what MCP tools are allowed.
"readonly" — read-only operations only (listing, status, log reading, introspection).
"standard" — read + safe writes (curated safe-command allowlist, power management).
"dangerous" — full access including destructive operations such as creating
and deleting servers and file transfer. Dangerous-tier tools additionally require the
dangerous-AI-tools opt-in on your servonaut.dev account when invoked through
Servonaut AI chat.
|
mcp.command_allowlist |
array | see schema | Shell commands the MCP run_command tool may invoke. Defaults to a curated read-only set (ls, cat, grep, ps, df, du, top, free, uptime, whoami, hostname, uname, date, w, netstat, ss, ip, ifconfig, ping, dig, nslookup, head, tail, wc, sort, find, file, stat). |
mcp.command_blocklist |
array | see schema | Regex patterns rejected even when the guard level would otherwise allow them. Defaults block rm -rf, dd, mkfs, shutdown, reboot, fdisk, parted, halt, fork bombs, and sudo rm. |
mcp.audit_path |
string | "~/.servonaut/mcp_audit.jsonl" |
JSONL audit trail of every MCP tool invocation — timestamp, arguments, success flag, and reason codes. |
mcp.max_output_lines |
integer | 500 |
Hard cap on the number of output lines returned to any single MCP call. |
{
"mcp": {
"guard_level": "readonly",
"audit_path": "~/.servonaut/mcp_audit.jsonl",
"max_output_lines": 200
}
}
Relay settings
The relay is what lets Servonaut AI chats — including web-originated conversations and
headless servonaut ai chat --tools sessions — and your team-mates run tools on
this machine. The listener keeps a secure, outbound-only real-time connection open to your
account; tool calls are dispatched over it, executed locally under your guard settings, and
the results posted back. Your credentials and SSH keys never leave the machine, and every
executed call lands in the same audit log as direct MCP calls.
You don't normally start it by hand: after you sign in (TUI Account → Login), the TUI
auto-starts an in-process listener and the sidebar shows ● connected. On headless
machines, run servonaut connect in the foreground, or manage it as a background
process:
servonaut connect --bg— detach into the background.servonaut connect --status— show local + backend connection status, with a warning if they diverge.servonaut connect --reconnect— stop then start the listener to heal a stale connection.servonaut connect --stop— stop the background listener.servonaut connect --force-bg— take over the connection from a TUI's in-process listener.
The TUI and the background listener coordinate through a lock file so only one of them holds
the connection at a time. The relay block tunes the listener:
| Key | Type | Default | Description |
|---|---|---|---|
relay.base_url |
string | (empty — derived automatically) | API endpoint the listener talks to for heartbeats, stream credentials, and posting tool results. Leave empty to use the default for your account. |
relay.mercure_url |
string | (empty — derived automatically) | URL of the real-time event stream the listener subscribes to. Leave empty — Servonaut derives it from the API base automatically. |
relay.heartbeat_interval |
integer | 30 |
Seconds between keep-alive heartbeats, so your account dashboard and team-mates can see this machine is connected. |
relay.ai_tool_auto_approve |
string | "standard" |
Maximum guard tier the listener auto-approves for AI chat tool
calls when no human is present to confirm:
"readonly", "standard", or
"dangerous". "dangerous" additionally
requires the dangerous-AI-tools opt-in on your account.
Tools above the tier are denied with an explanatory message the
AI can relay back to you.
See Tools in headless chat.
|
servonaut connect authenticates with your stored
servonaut login session and refreshes credentials
automatically — no environment variables required. Setting both
SERVONAUT_RELAY_TOKEN and SERVONAUT_USER_ID
overrides the session (CI / legacy mode). If both are set with a stale
token, the listener silently retries and never reaches
"Waiting for commands" — unset them unless you are
deliberately running in CI mode. For self-hosted or staging targets,
SERVONAUT_API_URL and SERVONAUT_MCP_URL
override the endpoints Servonaut talks to.
Syncing config across machines
On Solo and Teams plans, the Sync Config (🔄) screen keeps
config.json in step across your machines using encrypted snapshots:
Pull Latest, Push New, Restore, Rename, and Delete. Snapshots are encrypted in your
client with a passphrase only you know before they are uploaded — the passphrase never
leaves your machine, and Servonaut's servers only ever store ciphertext they cannot read.
When restoring on a new machine, the dialog asks you to enter your existing
passphrase (it isn't creating a new one).
Config snapshots carry whatever is in config.json — another reason to use
$ENV_VAR and file: references instead of pasting raw keys:
the references travel safely, and each machine resolves its own secrets locally.
Log viewer paths
Configure the default log paths the log viewer offers when you open it for a server
(press L in the viewer to switch between them). These are resolved on the
remote host, not locally. The field name is log_viewer_default_paths; the
shipped default covers common web-server, database, and system logs.
{
"log_viewer_default_paths": [
"/var/log/nginx/access.log",
"/var/log/nginx/error.log",
"/var/log/syslog",
"/var/log/auth.log",
"/var/log/mysql/error.log"
]
}
Related keys let you add custom paths and tune discovery and output limits:
log_viewer_custom_paths, log_viewer_scan_directories,
log_viewer_scan_max_depth, log_viewer_max_lines, and
log_viewer_tail_lines.