Configuration
Natscope runs with zero configuration. Every key below has a working default. Override only what you need.
How settings resolve
Two sources, environment wins:
- A YAML config file, passed with
--config(or-c) or theCONFIG_FILEenvironment variable.CONFIG_FILEtakes precedence over the flag. Natscope fails to start if the path does not exist. - Environment variables, which override anything in the file.
Nested YAML keys map to environment variables with double underscores. logger.level becomes LOGGER__LEVEL, storage.local.dataDir becomes STORAGE__LOCAL__DATA_DIR.
The config file must end in .yaml or .yml, and an unknown or misspelled key fails the load. An environment variable set to an empty string counts as unset. Boolean values accept true/false, yes/no, on/off and y/n in both sources.
A $NAME inside the file is replaced with that environment variable, and a reference to an undefined variable fails the load. Write a literal $ (for example in a password) as $$.
A .env file in the working directory is loaded only when NATSCOPE_DOTENV=1 is set.
natscope --config /etc/natscope/config.yaml
CONFIG_FILE=/etc/natscope/config.yaml natscopeCommented templates for every section live in configs/ in the repository.
Env prefix
Environment keys carry a prefix that is set at build time. Released binaries ship with no prefix, so the names below are used as written. A build with prefix QA reads QA_GRPC_WEB_ADDRESS, QA_LIB_DIR and so on. Run natscope version --full to print the active prefix.
Keys
Listener
| Key | Env | Default | What it does |
|---|---|---|---|
grpcWebAddress | GRPC_WEB_ADDRESS | 127.0.0.1:4280 | Address serving the API and the embedded UI |
allowRemote | ALLOW_REMOTE | false | Permit a non-loopback bind |
allowInsecure | ALLOW_INSECURE | false | Permit a non-loopback bind with no basic auth |
allowedHosts | ALLOWED_HOSTS | unset | Comma-separated host names accepted in the Host header besides localhost and IP addresses |
webAuth.username | WEB_AUTH__USERNAME | unset | HTTP basic auth username for the whole listener |
webAuth.password | WEB_AUTH__PASSWORD | unset | HTTP basic auth password |
One listener speaks Connect, gRPC-Web and gRPC, and serves the SPA. There is no separate gRPC port. See Remote access before binding beyond loopback.
Storage
| Key | Env | Default | What it does |
|---|---|---|---|
storage.local.dataDir | STORAGE__LOCAL__DATA_DIR | ~/.natscope/data | Directory holding natscope.bolt and the file vault |
Secrets
| Key | Env | Default | What it does |
|---|---|---|---|
secrets.backend | SECRETS__BACKEND | auto | Vault backend: auto, keyring or file |
secrets.fileKey | SECRETS__FILE_KEY | unset | AES-256 key for the file vault, 64 hex characters; prefer the env var |
See Secrets.
MCP
| Key | Env | Default | What it does |
|---|---|---|---|
mcp.enabled | MCP__ENABLED | true | Serve the MCP endpoint at /mcp |
mcp.allowWrites | MCP__ALLOW_WRITES | false | Register publish_message, the only tool that writes to NATS |
See AI agents (MCP).
Logging
| Key | Env | Default | Values |
|---|---|---|---|
logger.level | LOGGER__LEVEL | warning in a terminal, info otherwise | error, warning, info, debug, none |
logger.output | LOGGER__OUTPUT | stderr | stdout, stderr |
logger.outputFormat | LOGGER__OUTPUT_FORMAT | console in a terminal, text otherwise | console, text, json |
logger.colorized | LOGGER__COLORIZED | true | Color for console and text, ignored for json |
logger.outputSource | LOGGER__OUTPUT_SOURCE | false | Add file and line to each entry |
"In a terminal" means stderr is a TTY. The --log-level and --log-format flags override both the environment and the config file for those two keys.
Service directories
| Env | Default | What it does |
|---|---|---|
VAR_DIR | /var | Base directory for variable runtime data |
LIB_DIR | <VAR_DIR>/lib/natscope, so /var/lib/natscope | Service instance id file and the TLS certificate cache (<LIB_DIR>/certs) |
When neither variable is set and the system paths are not writable, Natscope falls back to ~/.natscope/var and ~/.natscope/lib on its own. Set either variable and that fallback stops: if Natscope cannot create the directory you named, it fails to start.
Internal HTTP server
Off by default. With no http section in the config, it never starts. Enable it for health, readiness and metrics endpoints under /internal, plus opt-in pprof.
http:
listenAddress: "127.0.0.1:9080"
pprof:
enabled: trueKeep it on loopback (127.0.0.1:port or [::1]:port). These endpoints expose runtime internals and carry no authentication. A non-loopback bind here, including a bare :port, needs both ALLOW_REMOTE=true and ALLOW_INSECURE=true, and pprof stays off on it regardless of configuration.
Memory
| Env | Default | What it does |
|---|---|---|
NATSCOPE_MEMORY_LIMIT_BYTES | 512 MiB | Soft heap limit; a byte count or a 256MiB/1GiB value. GOMEMLIMIT wins when set |
Example
logger:
level: debug
outputFormat: json
grpcWebAddress: "127.0.0.1:4280"
storage:
local:
dataDir: /var/lib/natscope/data
secrets:
backend: fileDocker defaults
The published image sets its own environment, so port mapping works in a container with no extra flags:
GRPC_WEB_ADDRESS=0.0.0.0:4280
ALLOW_REMOTE=true
ALLOW_INSECURE=true
SECRETS__BACKEND=file
STORAGE__LOCAL__DATA_DIR=/data
LIB_DIR=/data/lib
VAR_DIR=/data/varThe container binds all its interfaces so -p works. Publish on 127.0.0.1 to keep it host-local, or set WEB_AUTH__USERNAME and WEB_AUTH__PASSWORD when it is reachable from elsewhere.