Home / Docs / Configuration

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.

Config file location

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

~/.servonaut/config.json
{
  "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:

KeyTypeDefaultDescription
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.

example
{
  "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:

FieldRequiredTypeDefaultDescription
nameYesstringUnique server identifier shown in the TUI server list.
hostYesstringHostname or IP address.
usernameNostring"root"SSH username (e.g. ubuntu, ec2-user).
ssh_keyNostring""Path to SSH private key. Falls back to top-level default_key when empty.
portNointeger22SSH port.
providerNostring""Provider label (e.g. "DigitalOcean", "Hetzner").
groupNostring""Optional grouping label.
tagsNoobject{}Arbitrary key/value pairs for filtering. E.g. {"env":"prod","role":"db"}.
extra_ssh_optionsNoarray[]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.

KeyTypeDefaultDescription
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

config.json
{
  "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.

KeyTypeDefaultDescription
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.

config.json
{
  "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:

KeyTypeDefaultDescription
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.

KeyTypeDefaultDescription
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.

KeyTypeDefaultDescription
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.
Secrets syntax

Never hard-code API keys in config.json. Use environment variable or file references:

"anthropic_api_key": "$ANTHROPIC_API_KEY" // or from a file: "anthropic_api_key": "file:~/.secrets/anthropic_key"

Environment references also resolve from ~/.secrets/servonaut.env, which Servonaut loads automatically on startup.

Servonaut AI — no key needed on paid plans

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.

KeyTypeDefaultDescription
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.
config.json — readonly mode example
{
  "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:

KeyTypeDefaultDescription
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.
Listener authentication

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).

Secrets stay referenced, not synced

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.

config.json
{
  "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.

Documentation