20 KiB
Configuration
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:
- Existing
.env(GATEWAY_TOKEN) - Environment variable
OPENCLAW_GATEWAY_TOKEN ~/.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 onboardwrites different tokens to systemd andopenclaw.json) - Bootstraps
paired.jsonanddevice-auth.jsonwith 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, andsessions_spawntogateway.tools.allowwhen 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:
| Mode | Bind | Description |
|---|---|---|
| Localhost | 127.0.0.1 |
Only accessible from this machine. Safest option. |
| Tailscale IP | 0.0.0.0 |
Accessible from your Tailscale network over the machine's tailnet IP. Sets CORS + CSP for that IP. |
| Tailscale Serve | 127.0.0.1 |
Keeps Nerve loopback-only and exposes it through a Tailscale Serve HTTPS hostname when available. |
| Network (LAN) | 0.0.0.0 |
Accessible from your local network. Prompts for your LAN IP. Sets CORS + CSP for that IP. |
| Custom | Manual | Full 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 transcriptionREPLICATE_API_TOKEN, enables Qwen TTS via Replicate (warns ifffmpegis 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
| Flag | Behavior |
|---|---|
| (none) | Full interactive wizard. If .env exists, asks whether to update or start fresh. |
--check |
Validates all config values, tests gateway connectivity, and exits. Non-destructive. |
--defaults |
Auto-detects gateway token, applies defaults for everything else, writes .env. No prompts. |
--defaults --access-mode tailscale-ip |
Non-interactive setup for direct tailnet IP access. |
--defaults --access-mode tailscale-serve |
Non-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
| Variable | Default | Description |
|---|---|---|
PORT |
3080 |
HTTP server port |
SSL_PORT |
3443 |
HTTPS server port (requires certificates at certs/cert.pem and certs/key.pem) |
HOST |
127.0.0.1 |
Bind address. Set to 0.0.0.0 for network access — see warning below |
⚠️ Network exposure: Setting
HOST=0.0.0.0exposes 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)
| Variable | Default | Required | Description |
|---|---|---|---|
GATEWAY_TOKEN |
— | Yes | Authentication token for the OpenClaw gateway. The setup wizard auto-detects this. See note below |
GATEWAY_URL |
http://127.0.0.1:18789 |
No | Gateway HTTP endpoint URL |
NERVE_PUBLIC_ORIGIN |
(empty) | No | Explicit 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:
- The connection is from a local loopback address (
127.0.0.1or::1). When Nerve is behind a trusted reverse proxy, proxy-aware client IP handling can preserve that loopback detection (seeTRUSTED_PROXIES). - 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_TOKENis also accepted as a fallback forGATEWAY_TOKEN.Token detection order: The setup wizard finds the gateway token from: (1) systemd service file (
OPENCLAW_GATEWAY_TOKENenv var in the unit), (2)~/.openclaw/openclaw.json, (3)OPENCLAW_GATEWAY_TOKENshell env var. The systemd source takes priority because the gateway process reads the env var over the config file — a known issue whereopenclaw onboardwrites different tokens to each location.
Agent Identity
| Variable | Default | Description |
|---|---|---|
AGENT_NAME |
Agent |
Display name shown in the UI header and server info |
AGENT_NAME=Friday
API Keys (Optional)
| Variable | Description |
|---|---|
OPENAI_API_KEY |
Enables OpenAI TTS (multiple voices) and Whisper audio transcription |
REPLICATE_API_TOKEN |
Enables Replicate-hosted TTS models (e.g. Qwen TTS). Requires ffmpeg for WAV→MP3 |
MIMO_API_KEY |
Enables 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):
- OpenAI — if
OPENAI_API_KEYis set - Replicate — if
REPLICATE_API_TOKENis set - 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)
| Variable | Default | Description |
|---|---|---|
STT_PROVIDER |
local |
STT provider: local (whisper.cpp, no API key needed) or openai (requires OPENAI_API_KEY) |
WHISPER_MODEL |
base |
Local 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/models |
Directory for downloaded whisper model files |
NERVE_LANGUAGE |
en |
Preferred voice language (ISO 639-1). Legacy LANGUAGE is still accepted but deprecated |
EDGE_VOICE_GENDER |
female |
Edge 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:
LANGUAGEis still read for backwards compatibility, but new writes useNERVE_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
| Variable | Default | Description |
|---|---|---|
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_HOSTS |
localhost,127.0.0.1,::1 |
Additional WebSocket proxy allowed hostnames, comma-separated |
TRUSTED_PROXIES |
127.0.0.1,::1,::ffff:127.0.0.1 |
IP 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.
| Variable | Default | Description |
|---|---|---|
NERVE_AUTH |
false |
Enable 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_TTL |
2592000000 (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.
| Variable | Default | Description |
|---|---|---|
OPENAI_BASE_URL |
https://api.openai.com/v1 |
OpenAI-compatible API base URL |
REPLICATE_BASE_URL |
https://api.replicate.com/v1 |
Replicate API base URL |
OPENAI_BASE_URL=https://api.openai.com/v1
REPLICATE_BASE_URL=https://api.replicate.com/v1
Codex Integration
| Variable | Default | Description |
|---|---|---|
CODEX_DIR |
.codex |
Directory for Codex integration files |
File Paths
| Variable | Default | Description |
|---|---|---|
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.md |
Path 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.json |
Persistent cumulative token usage data |
NERVE_VOICE_PHRASES_PATH |
~/.nerve/voice-phrases.json |
Override location for per-language voice phrase overrides |
NERVE_WATCH_WORKSPACE_RECURSIVE |
true |
Enables 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
| Variable | Default | Description |
|---|---|---|
TTS_CACHE_TTL_MS |
3600000 (1 hour) |
Time-to-live for cached TTS audio in milliseconds |
TTS_CACHE_MAX |
200 |
Maximum 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.
| Path | Purpose |
|---|---|
~/.nerve/updater/last-good.json |
Snapshot of the last successful state (git ref, version, env hash) |
~/.nerve/updater/last-run.json |
Result metadata from the most recent update attempt |
~/.nerve/updater/snapshots/<ts>/.env |
Timestamped .env backups (mode 0600) |
~/.nerve/updater/nerve-update.lock |
PID lock file (prevents concurrent updates) |
Development
| Variable | Description |
|---|---|
NODE_ENV |
Set 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
| Field | Type | Default | Description |
|---|---|---|---|
columns |
array |
(see below) | Column definitions (1--10). Each: { key, title, wipLimit?, visible } |
defaults.status |
string |
"todo" |
Default status for new tasks |
defaults.priority |
string |
"normal" |
Default priority for new tasks |
reviewRequired |
boolean |
true |
Whether completed tasks must go through review before done |
allowDoneDragBypass |
boolean |
false |
Allow dragging tasks directly to done (skipping review) |
quickViewLimit |
number |
5 |
Max tasks shown in workspace quick view (1--50) |
proposalPolicy |
string |
"confirm" |
How agent proposals are handled: "confirm" (manual review) or "auto" (apply immediately) |
defaultModel |
string |
(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:
| Field | Type | Required | Description |
|---|---|---|---|
key |
string |
Yes | Status key: backlog, todo, in-progress, review, done, cancelled |
title |
string |
Yes | Display label (1--100 chars) |
wipLimit |
number |
No | Work-in-progress limit (≥0). 0 or omitted means unlimited |
visible |
boolean |
Yes | Whether 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. Onlocalhostthis 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