Files
natsdotnet/Documentation/Configuration/Overview.md
Joseph Doherty e553db6d40 docs: add Authentication, Clustering, JetStream, Monitoring overviews; update existing docs
New files:
- Documentation/Authentication/Overview.md — all 7 auth mechanisms with real source
  snippets (NKey/JWT/username-password/token/TLS mapping), nonce generation, account
  system, permissions, JWT permission templates
- Documentation/Clustering/Overview.md — route TCP handshake, in-process subscription
  propagation, gateway/leaf node stubs, honest gaps list
- Documentation/JetStream/Overview.md — API surface (4 handled subjects), streams,
  consumers, storage (MemStore/FileStore), in-process RAFT, mirror/source, gaps list
- Documentation/Monitoring/Overview.md — all 12 endpoints with real field tables,
  Go compatibility notes

Updated files:
- GettingStarted/Architecture.md — 14-subdirectory tree, real NatsClient/NatsServer
  field snippets, 9 new Go reference rows, Channel write queue design choice
- GettingStarted/Setup.md — xUnit 3, 100 test files grouped by area
- Operations/Overview.md — 99 test files, accurate Program.cs snippet, limitations
  section renamed to "Known Gaps vs Go Reference" with 7 real gaps
- Server/Overview.md — grouped fields, TLS/WS accept path, lame-duck mode, POSIX signals
- Configuration/Overview.md — 14 subsystem option tables, 24-row CLI table, LogOverrides
- Server/Client.md — Channel write queue, 4-task RunAsync, CommandMatrix, real fields

All docs verified against codebase 2026-02-23; 713 tests pass.
2026-02-23 10:14:18 -05:00

21 KiB

Configuration Overview

NatsOptions is the single configuration object passed to NatsServer at construction time. The host application (Program.cs) also reads CLI arguments to populate it before server startup.

Go reference: golang/nats-server/server/opts.go


NatsOptions Class

NatsOptions is a plain mutable class with no validation logic — values are consumed directly by NatsServer and NatsClient.

namespace NATS.Server;

public sealed class NatsOptions
{
    public string Host { get; set; } = "0.0.0.0";
    public int Port { get; set; } = 4222;
    public string? ServerName { get; set; }
    public int MaxPayload { get; set; } = 1024 * 1024; // 1MB
    public int MaxControlLine { get; set; } = 4096;
    public int MaxConnections { get; set; } = 65536;
    public TimeSpan PingInterval { get; set; } = TimeSpan.FromMinutes(2);
    public int MaxPingsOut { get; set; } = 2;
}

// NatsOptions contains 150+ fields organized into subsystem groups; the snippet shows the core network options.

Option reference

The table below covers the core network options documented in the snippet above. For the full set of option groups, see the subsystem tables that follow.

Option Type Default Description
Host string "0.0.0.0" Bind address for the TCP listener. Use "127.0.0.1" to restrict to loopback.
Port int 4222 Client listen port. Standard NATS client port.
ServerName string? null (auto: nats-dotnet-{MachineName}) Server name sent in the INFO message. When null, the server constructs a name from Environment.MachineName.
MaxPayload int 1048576 (1 MB) Maximum allowed message payload in bytes. Clients that publish larger payloads are rejected.
MaxControlLine int 4096 Maximum protocol control line size in bytes. Matches the Go server default.
MaxConnections int 65536 Maximum concurrent client connections the server will accept.
PingInterval TimeSpan 2 minutes Interval between server-initiated PING messages to connected clients.
MaxPingsOut int 2 Number of outstanding PINGs without a PONG response before the server disconnects a client.

Subscription limits

Option Type Default Description
MaxSubs int 0 (unlimited) Maximum subscriptions allowed per client connection. 0 disables the limit.
MaxSubTokens int 0 (unlimited) Maximum number of tokens (dot-separated segments) allowed in a subject. 0 disables the limit.

Monitoring

Option Type Default Description
MonitorPort int 0 (disabled) HTTP monitoring port. Set to 8222 for the standard NATS monitoring port.
MonitorHost string "0.0.0.0" Bind address for the HTTP monitoring listener.
MonitorBasePath string? null Optional URL path prefix for all monitoring endpoints (e.g., "/nats").
MonitorHttpsPort int 0 (disabled) HTTPS monitoring port. Requires TLS configuration to be set.

Lifecycle

Option Type Default Description
MaxConnections int 65536 Maximum concurrent client connections.
MaxPayload int 1048576 Maximum message payload in bytes.
MaxPending long 67108864 (64 MB) Maximum bytes buffered per client before the server applies back-pressure. Matches Go MAX_PENDING_SIZE.
WriteDeadline TimeSpan 10 seconds Deadline for a single write operation to a client socket. Slow clients that cannot consume within this window are disconnected.
LameDuckDuration TimeSpan 2 minutes How long the server remains in lame-duck mode, draining existing clients before shutting down.
LameDuckGracePeriod TimeSpan 10 seconds Grace period at the start of lame-duck mode before the server begins rejecting new connections.

File paths

Option Type Default Description
ConfigFile string? null Path to the NATS config file loaded at startup via -c.
PidFile string? null Path where the server writes its process ID.
PortsFileDir string? null Directory where the server writes a JSON file listing its bound ports.

Logging

Option Type Default Description
Debug bool false Enables debug-level log output. Sets Serilog minimum level to Debug.
Trace bool false Enables trace-level (verbose) log output. Sets Serilog minimum level to Verbose, overriding Debug.
TraceVerbose bool false Enables verbose protocol tracing including message payload content.
Logtime bool true Includes timestamps in log output.
LogtimeUTC bool false Uses UTC timestamps instead of local time when Logtime is true.
LogFile string? null Path to a log file. When set, the Serilog file sink is activated alongside the console sink.
LogSizeLimit long 0 (unlimited) Maximum log file size in bytes before rotation. 0 disables size-based rotation.
LogMaxFiles int 0 (unlimited) Number of rotated log files to retain. 0 retains all files.
Syslog bool false Writes logs to the local syslog daemon.
RemoteSyslog string? null UDP endpoint for remote syslog (e.g., "udp://logs.example.com:514"). Activates the UDP syslog sink.
LogOverrides Dictionary<string, string>? null Per-namespace minimum level overrides applied to Serilog (e.g., "NATS.Server.NatsClient" -> "Warning").

Authentication

Option Type Default Description
Username string? null Single-user password auth: username.
Password string? null Single-user password auth: password.
Authorization string? null Single shared token auth. Equivalent to token in the Go config.
Users IReadOnlyList<User>? null Multi-user list with per-user passwords and permissions.
NKeys IReadOnlyList<NKeyUser>? null NKey-based user list. Each entry carries a public NKey and optional permissions.
NoAuthUser string? null Username of the user to authenticate unauthenticated connections as. Must exist in Users.
AuthTimeout TimeSpan 2 seconds Time allowed for a client to complete the auth handshake.

JWT / Operator mode

Option Type Default Description
TrustedKeys string[]? null Operator public NKeys that are permitted to sign account JWTs.
AccountResolver IAccountResolver? null Pluggable resolver used to look up account JWTs by account public key.

TLS

Option Type Default Description
TlsCert string? null Path to the server TLS certificate file (PEM).
TlsKey string? null Path to the server TLS private key file (PEM).
TlsCaCert string? null Path to the CA certificate file used to verify client certificates.
TlsVerify bool false Requires clients to present a valid certificate signed by the CA.
TlsMap bool false Maps the TLS client certificate subject to a NATS username for auth.
TlsTimeout TimeSpan 2 seconds Deadline for completing the TLS handshake.
TlsHandshakeFirst bool false Performs the TLS handshake before the NATS INFO/CONNECT exchange.
TlsHandshakeFirstFallback TimeSpan 50 ms Time to wait for a TLS client hello before falling back to plain-text when TlsHandshakeFirst is true.
AllowNonTls bool false Accepts non-TLS connections alongside TLS connections.
TlsRateLimit long 0 (unlimited) Maximum new TLS handshakes per second. 0 disables rate limiting.
TlsPinnedCerts HashSet<string>? null Set of SHA-256 certificate fingerprints that are permitted. Connections presenting other certs are rejected.
TlsMinVersion SslProtocols Tls12 Minimum TLS protocol version accepted.

OCSP stapling

Option Type Default Description
OcspConfig OcspConfig? null OCSP stapling settings. When null, stapling is disabled. The OcspConfig type exposes Mode (Auto, Always, Must, Never) and OverrideUrls.
OcspPeerVerify bool false Requires OCSP staples from connecting clients when mutual TLS is enabled.

Clustering

Option Type Default Description
Cluster ClusterOptions? null Cluster listener and route configuration. When null, clustering is disabled. ClusterOptions exposes Name, Host ("0.0.0.0"), Port (6222), and Routes (list of seed URLs).
Gateway GatewayOptions? null Gateway bridge to other clusters. GatewayOptions exposes Name, Host, and Port.
LeafNode LeafNodeOptions? null Leaf node listener. LeafNodeOptions exposes Host and Port.

JetStream

Option Type Default Description
JetStream JetStreamOptions? null Enables and configures JetStream persistence. When null, JetStream is disabled. JetStreamOptions exposes StoreDir (base directory for file-backed streams), MaxMemoryStore (bytes, 0 = unlimited), and MaxFileStore (bytes, 0 = unlimited).

MQTT

Option Type Default Description
Mqtt MqttOptions? null MQTT protocol configuration. Config is parsed and stored but no MQTT listener is started yet. MqttOptions exposes network (Host, Port), auth (Username, Password, Token, NoAuthUser), TLS, and JetStream integration fields (JsDomain, StreamReplicas, AckWait).

WebSocket

Option Type Default Description
WebSocket WebSocketOptions new() WebSocket transport configuration. Always present; the listener is inactive when Port is -1 (the default). WebSocketOptions exposes Host, Port, NoTls, SameOrigin, AllowedOrigins, Compression, HandshakeTimeout, per-connection auth fields, and TLS cert paths.

Advanced

Option Type Default Description
NoHeaderSupport bool false Disables NATS header support. Clients are informed via the INFO message; HPUB/HMSG commands are rejected.
DisableSublistCache bool false Disables the SubList match cache. Useful in benchmarks to isolate raw matching cost.
NoSystemAccount bool false Suppresses creation of the built-in $SYS account used for system events.
SystemAccount string? null Name of the account to use as the system account instead of the built-in default.
MaxClosedClients int 10000 Number of recently closed client records retained for monitoring (/connz?closed=true).
ConnectErrorReports int 3600 How often (in attempts) connection errors to routes/gateways are logged.
ReconnectErrorReports int 1 How often reconnect errors are logged. 1 logs every attempt.
MaxTracedMsgLen int 0 (unlimited) Truncation length for message payloads in trace-level logs. 0 logs the full payload.
Tags Dictionary<string, string>? null Arbitrary key-value tags exposed via the /varz monitoring endpoint.
ClientAdvertise string? null Alternative host:port advertised to cluster peers for client connections (NAT traversal).
SubjectMappings Dictionary<string, string>? null Subject transform rules mapping source patterns to destination templates.
InCmdLine HashSet<string> [] Tracks which property names were set via CLI flags. Used during config reload to prevent file-based values from overwriting CLI-supplied ones. Not a user-settable option.

How ServerName is resolved

NatsServer constructs the ServerInfo sent to each client at connection time. If ServerName is null, it uses nats-dotnet-{Environment.MachineName}:

_serverInfo = new ServerInfo
{
    ServerId = Guid.NewGuid().ToString("N")[..20].ToUpperInvariant(),
    ServerName = options.ServerName ?? $"nats-dotnet-{Environment.MachineName}",
    Version = NatsProtocol.Version,
    Host = options.Host,
    Port = options.Port,
    MaxPayload = options.MaxPayload,
};

CLI Arguments

Program.cs parses command-line arguments in two passes before creating NatsServer. The first pass scans for -c to load a config file as the base NatsOptions. The second pass applies all remaining flags on top of the loaded options. Every flag that is processed is recorded in options.InCmdLine so that config-file reloads cannot overwrite values that were explicitly supplied on the command line.

Flag Alias Field Example
-c ConfigFile (load only) -c /etc/nats/server.conf
-p --port Port -p 14222
-a --addr Host -a 127.0.0.1
-n --name ServerName -n my-server
-m --http_port MonitorPort -m 8222
--http_base_path MonitorBasePath --http_base_path /nats
--https_port MonitorHttpsPort --https_port 8443
--pid PidFile --pid /var/run/nats.pid
--ports_file_dir PortsFileDir --ports_file_dir /tmp
--tlscert TlsCert --tlscert server.pem
--tlskey TlsKey --tlskey server-key.pem
--tlscacert TlsCaCert --tlscacert ca.pem
--tlsverify TlsVerify --tlsverify
-D --debug Debug -D
-V / -T --trace Trace -V
-DV Debug + Trace -DV
-l --log / --log_file LogFile -l /var/log/nats.log
--log_size_limit LogSizeLimit --log_size_limit 104857600
--log_max_files LogMaxFiles --log_max_files 5
--logtime Logtime --logtime false
--logtime_utc LogtimeUTC --logtime_utc
--syslog Syslog --syslog
--remote_syslog RemoteSyslog --remote_syslog udp://logs.example.com:514
--log_level_override LogOverrides --log_level_override NATS.Server.NatsClient=Warning
--service Windows Service mode --service

The -c flag is consumed in the first pass and silently skipped in the second pass. Unrecognized flags are silently ignored. There is no --help output.

The InCmdLine set is used after startup to establish reload precedence. When a config-file reload is triggered (e.g., via SIGHUP), ConfigReloader.MergeCliOverrides copies the CLI-supplied field values back over the reloaded options, ensuring flags like -p or -D cannot be reverted by a config change.

for (int i = 0; i < args.Length; i++)
{
    switch (args[i])
    {
        case "-p" or "--port" when i + 1 < args.Length:
            options.Port = int.Parse(args[++i]);
            options.InCmdLine.Add("Port");
            break;
        case "-a" or "--addr" when i + 1 < args.Length:
            options.Host = args[++i];
            options.InCmdLine.Add("Host");
            break;
        case "-n" or "--name" when i + 1 < args.Length:
            options.ServerName = args[++i];
            options.InCmdLine.Add("ServerName");
            break;
    }
}

Protocol Constants

NatsProtocol defines wire-level constants that mirror the Go server's defaults. These values are used by the parser and the INFO response:

Constant Value Description
MaxControlLineSize 4096 Maximum bytes in a protocol control line
MaxPayloadSize 1048576 Maximum message payload in bytes (1 MB)
DefaultPort 4222 Standard NATS client port
Version "0.1.0" Server version string sent in INFO
ProtoVersion 1 NATS protocol version number sent in INFO
public static class NatsProtocol
{
    public const int MaxControlLineSize = 4096;
    public const int MaxPayloadSize = 1024 * 1024; // 1MB
    public const int DefaultPort = 4222;
    public const string Version = "0.1.0";
    public const int ProtoVersion = 1;
}

MaxControlLine in NatsOptions and MaxControlLineSize in NatsProtocol carry the same value. NatsOptions.MaxPayload is used as the per-server runtime limit passed into NatsParser; NatsProtocol.MaxPayloadSize is the compile-time default.


Logging Configuration

Debug and Trace flags

NatsOptions exposes two boolean flags that control the Serilog minimum log level. Debug sets the minimum level to Debug; Trace sets it to Verbose (Serilog's finest level, matching NATS protocol tracing). When both are present, Trace wins because Verbose is finer than Debug. Neither flag changes log output format — only the minimum severity threshold.

TraceVerbose is a separate flag that enables payload content in protocol traces. It is not wired to a Serilog level; components that check it emit additional Verbose-level log entries that include message body bytes.

LogOverrides dictionary

LogOverrides is a Dictionary<string, string>? on NatsOptions that maps .NET logger category name prefixes to Serilog level names (Verbose, Debug, Information, Warning, Error, Fatal). Each entry becomes a MinimumLevel.Override(ns, level) call in the Serilog configuration:

if (options.LogOverrides is not null)
{
    foreach (var (ns, level) in options.LogOverrides)
    {
        if (Enum.TryParse<Serilog.Events.LogEventLevel>(level, true, out var serilogLevel))
            logConfig.MinimumLevel.Override(ns, serilogLevel);
    }
}

This maps directly to Serilog's per-category filtering, which is applied before the global minimum level check. A useful override pattern is silencing the high-volume per-client category while keeping server-level events visible:

--log_level_override NATS.Server.NatsClient=Warning

The --log_level_override CLI flag sets a single entry in LogOverrides using key=value format. Multiple flags may be supplied to add multiple overrides.

Serilog setup

Logging uses Serilog with the console sink, configured in Program.cs before any other code runs:

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .Enrich.FromLogContext()
    .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
    .CreateLogger();

The output template produces lines like:

[14:32:01 INF] Listening on 0.0.0.0:4222
[14:32:01 DBG] Client 1 connected from 127.0.0.1:54321

ILoggerFactory injection

Program.cs wraps the Serilog logger in a SerilogLoggerFactory and passes it to NatsServer:

using var loggerFactory = new Serilog.Extensions.Logging.SerilogLoggerFactory(Log.Logger);
var server = new NatsServer(options, loggerFactory);

NatsServer stores the factory and uses it to create two kinds of loggers:

  • ILogger<NatsServer> — obtained via loggerFactory.CreateLogger<NatsServer>(), used for server-level events (listening, client connect/disconnect).
  • ILogger with named category — obtained via loggerFactory.CreateLogger($"NATS.Server.NatsClient[{clientId}]"), created per connection. This gives each client a distinct category in log output so messages can be correlated by client ID without structured log properties.
// In NatsServer.StartAsync — one logger per accepted connection
var clientLogger = _loggerFactory.CreateLogger($"NATS.Server.NatsClient[{clientId}]");
var client = new NatsClient(clientId, socket, _options, _serverInfo, clientLogger);

NatsClient takes ILogger (not ILogger<NatsClient>) so it can accept the pre-named logger instance rather than deriving its category from its own type.

Log flushing

Log.CloseAndFlush() runs in the finally block of Program.cs after the server stops, ensuring buffered log entries are written before the process exits:

try
{
    await server.StartAsync(cts.Token);
}
catch (OperationCanceledException) { }
finally
{
    Log.CloseAndFlush();
}