Configuration

April 3, 2026 · View on GitHub

Nerve is configured via a .env file in the project root. All variables have sensible defaults — only GATEWAY_TOKEN is strictly required.


Setup Wizard

The interactive setup wizard is the recommended way to configure Nerve:

npm run setup               # Interactive setup (6 steps)
npm run setup -- --check    # Validate existing config & test gateway
npm run setup -- --defaults # Non-interactive with auto-detected values
npm run setup -- --help     # Show help

Wizard Steps

The wizard walks through 6 sections:

1. Gateway Connection

Connects Nerve to your OpenClaw gateway. The wizard auto-detects the gateway token from:

  1. Existing .env (GATEWAY_TOKEN)
  2. Environment variable OPENCLAW_GATEWAY_TOKEN
  3. ~/.openclaw/openclaw.json (auto-detected)

Tests the connection before proceeding. If the gateway is unreachable, setup stops so you can fix the gateway or token first. On current OpenClaw builds, the wizard also:

  • Reads the real gateway token from the systemd service file (works around a known bug where openclaw onboard writes different tokens to systemd and openclaw.json)
  • Bootstraps paired.json and device-auth.json with full operator scopes if they don't exist yet
  • Pre-pairs Nerve's device identity in the normal setup path so it can connect without manual approval (openclaw devices approve)
  • Adds cron, gateway, and sessions_spawn to gateway.tools.allow when they are missing
  • Restarts the gateway to apply changes

2. Agent Identity

Sets the AGENT_NAME displayed in the UI.

3. Access Mode

Determines how you'll access Nerve. The wizard auto-configures HOST, ALLOWED_ORIGINS, WS_ALLOWED_HOSTS, and CSP_CONNECT_EXTRA based on your choice:

ModeBindDescription
Localhost127.0.0.1Only accessible from this machine. Safest option.
Tailscale IP0.0.0.0Accessible from your Tailscale network over the machine's tailnet IP. Sets CORS + CSP for that IP.
Tailscale Serve127.0.0.1Keeps Nerve loopback-only and exposes it through a Tailscale Serve HTTPS hostname when available.
Network (LAN)0.0.0.0Accessible from your local network. Prompts for your LAN IP. Sets CORS + CSP for that IP.
CustomManualFull manual control: custom port, bind address, HTTPS certificate generation, CORS.

HTTPS (Network and Custom modes): The wizard can offer self-signed certificate generation via openssl and configure SSL_PORT for non-localhost access.

4. Authentication

If you choose a network-exposed mode, the wizard prompts you to enable auth and either:

  • set a password, or
  • reuse the gateway token as the password fallback

For localhost-only installs, auth can stay off.

5. TTS Configuration (Optional)

Prompts for optional API keys:

  • OPENAI_API_KEY, enables OpenAI TTS + Whisper transcription
  • REPLICATE_API_TOKEN, enables Qwen TTS via Replicate (warns if ffmpeg is missing)

Edge TTS always works without any keys. Xiaomi MiMo can be enabled later by setting MIMO_API_KEY manually or saving it from Settings, Audio.

6. Advanced Settings (Optional)

Custom file paths for MEMORY_PATH, MEMORY_DIR, SESSIONS_DIR. Most users skip this.

Modes Summary

FlagBehavior
(none)Full interactive wizard. If .env exists, asks whether to update or start fresh.
--checkValidates all config values, tests gateway connectivity, and exits. Non-destructive.
--defaultsAuto-detects gateway token, applies defaults for everything else, writes .env. No prompts.
--defaults --access-mode tailscale-ipNon-interactive setup for direct tailnet IP access.
--defaults --access-mode tailscale-serveNon-interactive setup for loopback + Tailscale Serve HTTPS access.

The wizard backs up existing .env files as .env.backup or .env.backup.YYYY-MM-DD before overwriting and applies chmod 600 to both .env and backup files.


Environment Variables

Server

VariableDefaultDescription
PORT3080HTTP server port
SSL_PORT3443HTTPS server port (requires certificates at certs/cert.pem and certs/key.pem)
HOST127.0.0.1Bind address. Set to 0.0.0.0 for network access — see warning below

⚠️ Network exposure: Setting HOST=0.0.0.0 exposes all endpoints to the network. Enable authentication (NERVE_AUTH=true) and set a password via the setup wizard before binding to a non-loopback address. Without auth, anyone with network access can read/write agent memory, modify config files, and control sessions. See Security for the full threat model.

PORT=3080
SSL_PORT=3443
HOST=127.0.0.1

Gateway (Required)

VariableDefaultRequiredDescription
GATEWAY_TOKENYesAuthentication token for the OpenClaw gateway. The setup wizard auto-detects this. See note below
GATEWAY_URLhttp://127.0.0.1:18789NoGateway HTTP endpoint URL
NERVE_PUBLIC_ORIGIN(empty)NoExplicit browser-facing Nerve origin used when server-side gateway RPC fallback must open its own WebSocket to OpenClaw. Useful for reverse-proxy, cloud, and hybrid deployments.
GATEWAY_TOKEN=your-token-here
GATEWAY_URL=http://127.0.0.1:18789

# Optional for reverse-proxy / cloud / hybrid installs
NERVE_PUBLIC_ORIGIN=https://nerve.example.com

For non-interactive installs that should talk to a remote gateway, pass the URL directly to the installer:

curl -fsSL https://raw.githubusercontent.com/daggerhashimoto/openclaw-nerve/master/install.sh \
  | bash -s -- --gateway-url https://gw.example.com --gateway-token <token> --skip-setup

If remote workspace panels (Files, Memory, Config, Skills) fail with origin not allowed while chat still works, set NERVE_PUBLIC_ORIGIN to the exact browser origin and add that same origin to gateway.controlUi.allowedOrigins on the gateway.

Token Injection

Nerve performs server-side token injection. When a connection is established through the WebSocket proxy, Nerve automatically injects the configured GATEWAY_TOKEN into the connection request if the client is considered trusted.

Trust is granted if:

  1. The connection is from a local loopback address (127.0.0.1 or ::1). When Nerve is behind a trusted reverse proxy, proxy-aware client IP handling can preserve that loopback detection (see TRUSTED_PROXIES).
  2. OR, the connection has a valid authenticated session (NERVE_AUTH=true).

This allows the browser UI to connect without having to manually enter or store the gateway token in the browser's persistent storage. If a connection is not trusted (e.g., remote access without authentication), the token field in the UI must be filled manually.

Note: OPENCLAW_GATEWAY_TOKEN is also accepted as a fallback for GATEWAY_TOKEN.

Token detection order: The setup wizard finds the gateway token from: (1) systemd service file (OPENCLAW_GATEWAY_TOKEN env var in the unit), (2) ~/.openclaw/openclaw.json, (3) OPENCLAW_GATEWAY_TOKEN shell env var. The systemd source takes priority because the gateway process reads the env var over the config file — a known issue where openclaw onboard writes different tokens to each location.

Agent Identity

VariableDefaultDescription
AGENT_NAMEAgentDisplay name shown in the UI header and server info
AGENT_NAME=Friday

API Keys (Optional)

VariableDescription
OPENAI_API_KEYEnables OpenAI TTS (multiple voices) and Whisper audio transcription
REPLICATE_API_TOKENEnables Replicate-hosted TTS models (e.g. Qwen TTS). Requires ffmpeg for WAV→MP3
MIMO_API_KEYEnables Xiaomi MiMo TTS when the Xiaomi provider is selected in Settings, Audio
OPENAI_API_KEY=sk-...
REPLICATE_API_TOKEN=r8_...
MIMO_API_KEY=sk-mimo-...

TTS provider fallback chain (when no explicit provider is requested):

  1. OpenAI — if OPENAI_API_KEY is set
  2. Replicate — if REPLICATE_API_TOKEN is set
  3. Edge TTS — always available, no API key needed (default for new installs)

Xiaomi MiMo is available as an explicit provider option when MIMO_API_KEY is set. It is not part of the automatic fallback chain.

Speech-to-Text (STT)

VariableDefaultDescription
STT_PROVIDERlocalSTT provider: local (whisper.cpp, no API key needed) or openai (requires OPENAI_API_KEY)
WHISPER_MODELbaseLocal whisper model: tiny (75 MB), base (142 MB), or small (466 MB) — multilingual variants. English-only variants (tiny.en, base.en, small.en) are also available.
WHISPER_MODEL_DIR~/.nerve/modelsDirectory for downloaded whisper model files
NERVE_LANGUAGEenPreferred voice language (ISO 639-1). Legacy LANGUAGE is still accepted but deprecated
EDGE_VOICE_GENDERfemaleEdge TTS voice gender: female or male
# Use local speech-to-text (no API key needed)
STT_PROVIDER=local
WHISPER_MODEL=base
NERVE_LANGUAGE=en

Nerve uses explicit language selection (NERVE_LANGUAGE) for voice flows; there is no user-facing auto-detect language mode.

Local STT requires ffmpeg for audio format conversion (webm/ogg → 16kHz mono WAV). The installer handles this automatically. Models are downloaded from HuggingFace on first use.

Migration note: LANGUAGE is still read for backwards compatibility, but new writes use NERVE_LANGUAGE.

Voice phrase overrides (stop/cancel/wake words) are stored at ~/.nerve/voice-phrases.json and generated on first save from the UI.

Network & Security

VariableDefaultDescription
ALLOWED_ORIGINS(localhost only)Additional CORS origins, comma-separated. Normalised via URL constructor; "null" origins are rejected
CSP_CONNECT_EXTRA(none)Additional CSP connect-src entries, space-separated. Only http://, https://, ws://, wss:// schemes accepted. Semicolons and newlines are stripped to prevent directive injection
WS_ALLOWED_HOSTSlocalhost,127.0.0.1,::1Additional WebSocket proxy allowed hostnames, comma-separated
TRUSTED_PROXIES127.0.0.1,::1,::ffff:127.0.0.1IP addresses trusted to set X-Forwarded-For / X-Real-IP headers, comma-separated
# Tailscale example
ALLOWED_ORIGINS=http://100.64.0.5:3080
CSP_CONNECT_EXTRA=http://100.64.0.5:3080 ws://100.64.0.5:3080
WS_ALLOWED_HOSTS=100.64.0.5

# Behind nginx reverse proxy
TRUSTED_PROXIES=127.0.0.1,::1,10.0.0.1

If you are retrofitting Tailscale onto an existing install, see Add Tailscale to an Existing Nerve Install.

Authentication

Nerve includes a built-in authentication layer that protects all API endpoints, WebSocket connections, and SSE streams with a session cookie. Auth is opt-in for localhost users and auto-prompted during setup when binding to a network interface.

VariableDefaultDescription
NERVE_AUTHfalseEnable authentication. Set to true to require a password for access
NERVE_PASSWORD_HASH(empty)scrypt hash of the password. Generated by the setup wizard
NERVE_SESSION_SECRET(auto-generated)32-byte hex string for HMAC-SHA256 cookie signing. Auto-generated during setup. If not set, an ephemeral secret is generated at startup (sessions won't survive restarts)
NERVE_SESSION_TTL2592000000 (30 days)Session lifetime in milliseconds
NERVE_AUTH=true
NERVE_PASSWORD_HASH=<generated-by-setup>
NERVE_SESSION_SECRET=<generated-by-setup>

NERVE_ALLOW_INSECURE

When HOST=0.0.0.0 and NERVE_AUTH=false, the server refuses to start to prevent accidentally exposing all endpoints without authentication. Set NERVE_ALLOW_INSECURE=true to override this safety check. Not recommended for production.

NERVE_ALLOW_INSECURE=true

Quick enable (with gateway token as password):

NERVE_AUTH=true
NERVE_SESSION_SECRET=$(openssl rand -hex 32)
# No NERVE_PASSWORD_HASH needed — your GATEWAY_TOKEN works as the password

Behavior:

  • When NERVE_AUTH=false (default): No authentication, all endpoints are open
  • When NERVE_AUTH=true: All /api/* routes (except auth and health) require a valid session cookie
  • The session cookie is HttpOnly, SameSite=Strict, and port-suffixed (nerve_session_3080)
  • WebSocket upgrade requests are also authenticated
  • If no password hash is set, the gateway token is accepted as a fallback password

Setup wizard: When the access mode is set to a non-localhost option, the wizard prompts to set a password and auto-generates the session secret.

API Base URLs

Override these for proxies, self-hosted endpoints, or API-compatible alternatives.

VariableDefaultDescription
OPENAI_BASE_URLhttps://api.openai.com/v1OpenAI-compatible API base URL
REPLICATE_BASE_URLhttps://api.replicate.com/v1Replicate API base URL
OPENAI_BASE_URL=https://api.openai.com/v1
REPLICATE_BASE_URL=https://api.replicate.com/v1

Codex Integration

VariableDefaultDescription
CODEX_DIR.codexDirectory for Codex integration files

File Paths

VariableDefaultDescription
FILE_BROWSER_ROOT"" (disabled)If set, overrides the file browser root directory for all sessions. In this mode, file-browser agentId scoping is bypassed, default exclusion rules are disabled, and delete operations are permanent (no .trash recovery).
MEMORY_PATH~/.openclaw/workspace/MEMORY.mdPath to the main agent's long-term memory file
MEMORY_DIR~/.openclaw/workspace/memory/Directory for the main agent's daily memory files (YYYY-MM-DD.md)
SESSIONS_DIR~/.openclaw/agents/main/sessions/Session transcript directory (scanned for token usage)
USAGE_FILE~/.openclaw/token-usage.jsonPersistent cumulative token usage data
NERVE_VOICE_PHRASES_PATH~/.nerve/voice-phrases.jsonOverride location for per-language voice phrase overrides
NERVE_WATCH_WORKSPACE_RECURSIVEtrueEnables recursive fs.watch for full workspace file.changed SSE events outside MEMORY.md and memory/. Set this to false to disable full-workspace watching if you hit Linux inotify ENOSPC watcher exhaustion. Memory watchers stay enabled for discovered agent workspaces even when this is false.
FILE_BROWSER_ROOT=/home/user
MEMORY_PATH=/custom/path/MEMORY.md
MEMORY_DIR=/custom/path/memory/
SESSIONS_DIR=/custom/path/sessions/
NERVE_VOICE_PHRASES_PATH=/custom/path/voice-phrases.json
NERVE_WATCH_WORKSPACE_RECURSIVE=false

TTS Cache

VariableDefaultDescription
TTS_CACHE_TTL_MS3600000 (1 hour)Time-to-live for cached TTS audio in milliseconds
TTS_CACHE_MAX200Maximum number of cached TTS entries (in-memory LRU)
TTS_CACHE_TTL_MS=7200000
TTS_CACHE_MAX=500

Updater State

The updater stores state in ~/.nerve/updater/. These are not configurable via env vars — they're managed automatically by npm run update.

PathPurpose
~/.nerve/updater/last-good.jsonSnapshot of the last successful state (git ref, version, env hash)
~/.nerve/updater/last-run.jsonResult metadata from the most recent update attempt
~/.nerve/updater/snapshots/<ts>/.envTimestamped .env backups (mode 0600)
~/.nerve/updater/nerve-update.lockPID lock file (prevents concurrent updates)

Development

VariableDescription
NODE_ENVSet to development to enable the POST /api/events/test debug endpoint and verbose error logging

Kanban

Kanban board configuration is stored in the runtime data file (${NERVE_DATA_DIR:-~/.nerve}/kanban/tasks.json), not in .env. Manage it via the REST API:

# Read current config
curl http://localhost:3080/api/kanban/config

# Update config
curl -X PUT http://localhost:3080/api/kanban/config \
  -H 'Content-Type: application/json' \
  -d '{"proposalPolicy":"auto","quickViewLimit":10}'

Board Configuration

FieldTypeDefaultDescription
columnsarray(see below)Column definitions (1--10). Each: { key, title, wipLimit?, visible }
defaults.statusstring"todo"Default status for new tasks
defaults.prioritystring"normal"Default priority for new tasks
reviewRequiredbooleantrueWhether completed tasks must go through review before done
allowDoneDragBypassbooleanfalseAllow dragging tasks directly to done (skipping review)
quickViewLimitnumber5Max tasks shown in workspace quick view (1--50)
proposalPolicystring"confirm"How agent proposals are handled: "confirm" (manual review) or "auto" (apply immediately)
defaultModelstring(none)Default model for agent execution (max 100 chars). If unset, execution falls back to OpenClaw's configured default model

Column Schema

Each column in the columns array:

FieldTypeRequiredDescription
keystringYesStatus key: backlog, todo, in-progress, review, done, cancelled
titlestringYesDisplay label (1--100 chars)
wipLimitnumberNoWork-in-progress limit (≥0). 0 or omitted means unlimited
visiblebooleanYesWhether the column is shown on the board

Default Columns

[
  { "key": "backlog",     "title": "Backlog",     "visible": true },
  { "key": "todo",        "title": "To Do",       "visible": true },
  { "key": "in-progress", "title": "In Progress", "visible": true },
  { "key": "review",      "title": "Review",      "visible": true },
  { "key": "done",        "title": "Done",        "visible": true },
  { "key": "cancelled",   "title": "Cancelled",   "visible": false }
]

HTTPS

Nerve automatically starts an HTTPS server on SSL_PORT when certificates exist at:

certs/cert.pem    # Certificate
certs/key.pem     # Private key

Generate self-signed certificates:

mkdir -p certs
openssl req -x509 -newkey rsa:2048 \
  -keyout certs/key.pem -out certs/cert.pem \
  -days 365 -nodes -subj '/CN=localhost'

Or use the setup wizard's Custom access mode, which generates them automatically if openssl is available.

Why HTTPS? Browser microphone access (getUserMedia) requires a secure context. On localhost this works over HTTP, but network access requires HTTPS.


Minimal .env Example

GATEWAY_TOKEN=abc123def456

Everything else uses defaults. This is sufficient for local-only usage.

Full .env Example

# Gateway (required)
GATEWAY_TOKEN=abc123def456
GATEWAY_URL=http://127.0.0.1:18789

# Server
PORT=3080
SSL_PORT=3443
HOST=0.0.0.0
AGENT_NAME=Friday

# Authentication (recommended when HOST=0.0.0.0)
NERVE_AUTH=true
NERVE_PASSWORD_HASH=<generated-by-setup>
NERVE_SESSION_SECRET=<generated-by-setup>
NERVE_SESSION_TTL=2592000000

# API Keys
OPENAI_API_KEY=sk-...
REPLICATE_API_TOKEN=r8_...
MIMO_API_KEY=sk-mimo-...

# Speech / Language
STT_PROVIDER=local
WHISPER_MODEL=base
NERVE_LANGUAGE=en
EDGE_VOICE_GENDER=female

# Network (Tailscale example)
ALLOWED_ORIGINS=http://100.64.0.5:3080
CSP_CONNECT_EXTRA=http://100.64.0.5:3080 ws://100.64.0.5:3080
WS_ALLOWED_HOSTS=100.64.0.5

# TTS Cache
TTS_CACHE_TTL_MS=3600000
TTS_CACHE_MAX=200

# Custom Paths (optional)
MEMORY_PATH=/home/user/.openclaw/workspace/MEMORY.md
MEMORY_DIR=/home/user/.openclaw/workspace/memory
SESSIONS_DIR=/home/user/.openclaw/agents/main/sessions