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.
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 vialoggerFactory.CreateLogger<NatsServer>(), used for server-level events (listening, client connect/disconnect).ILoggerwith named category — obtained vialoggerFactory.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();
}