diff --git a/src/NATS.Server/Configuration/ConfigProcessor.cs b/src/NATS.Server/Configuration/ConfigProcessor.cs
new file mode 100644
index 0000000..88b36ae
--- /dev/null
+++ b/src/NATS.Server/Configuration/ConfigProcessor.cs
@@ -0,0 +1,685 @@
+// Port of Go server/opts.go processConfigFileLine — maps parsed config dictionaries
+// to NatsOptions. Reference: golang/nats-server/server/opts.go lines 1050-1400.
+
+using System.Globalization;
+using System.Text.RegularExpressions;
+using NATS.Server.Auth;
+
+namespace NATS.Server.Configuration;
+
+///
+/// Maps a parsed NATS configuration dictionary (produced by )
+/// into a fully populated instance. Collects all validation
+/// errors rather than failing on the first one.
+///
+public static class ConfigProcessor
+{
+ ///
+ /// Parses a configuration file and returns the populated options.
+ ///
+ public static NatsOptions ProcessConfigFile(string filePath)
+ {
+ var config = NatsConfParser.ParseFile(filePath);
+ var opts = new NatsOptions { ConfigFile = filePath };
+ ApplyConfig(config, opts);
+ return opts;
+ }
+
+ ///
+ /// Parses configuration text (not from a file) and returns the populated options.
+ ///
+ public static NatsOptions ProcessConfig(string configText)
+ {
+ var config = NatsConfParser.Parse(configText);
+ var opts = new NatsOptions();
+ ApplyConfig(config, opts);
+ return opts;
+ }
+
+ ///
+ /// Applies a parsed configuration dictionary to existing options.
+ /// Throws if any validation errors are collected.
+ ///
+ public static void ApplyConfig(Dictionary config, NatsOptions opts)
+ {
+ var errors = new List();
+
+ foreach (var (key, value) in config)
+ {
+ try
+ {
+ ProcessKey(key, value, opts, errors);
+ }
+ catch (Exception ex)
+ {
+ errors.Add($"Error processing '{key}': {ex.Message}");
+ }
+ }
+
+ if (errors.Count > 0)
+ {
+ throw new ConfigProcessorException("Configuration errors", errors);
+ }
+ }
+
+ private static void ProcessKey(string key, object? value, NatsOptions opts, List errors)
+ {
+ // Keys are already case-insensitive from the parser (OrdinalIgnoreCase dictionaries),
+ // but we normalize here for the switch statement.
+ switch (key.ToLowerInvariant())
+ {
+ case "listen":
+ ParseListen(value, opts);
+ break;
+ case "port":
+ opts.Port = ToInt(value);
+ break;
+ case "host" or "net":
+ opts.Host = ToString(value);
+ break;
+ case "server_name":
+ var name = ToString(value);
+ if (name.Contains(' '))
+ errors.Add("server_name cannot contain spaces");
+ else
+ opts.ServerName = name;
+ break;
+ case "client_advertise":
+ opts.ClientAdvertise = ToString(value);
+ break;
+
+ // Logging
+ case "debug":
+ opts.Debug = ToBool(value);
+ break;
+ case "trace":
+ opts.Trace = ToBool(value);
+ break;
+ case "trace_verbose":
+ opts.TraceVerbose = ToBool(value);
+ if (opts.TraceVerbose)
+ opts.Trace = true;
+ break;
+ case "logtime":
+ opts.Logtime = ToBool(value);
+ break;
+ case "logtime_utc":
+ opts.LogtimeUTC = ToBool(value);
+ break;
+ case "logfile" or "log_file":
+ opts.LogFile = ToString(value);
+ break;
+ case "log_size_limit":
+ opts.LogSizeLimit = ToLong(value);
+ break;
+ case "log_max_num":
+ opts.LogMaxFiles = ToInt(value);
+ break;
+ case "syslog":
+ opts.Syslog = ToBool(value);
+ break;
+ case "remote_syslog":
+ opts.RemoteSyslog = ToString(value);
+ break;
+
+ // Limits
+ case "max_payload":
+ opts.MaxPayload = ToInt(value);
+ break;
+ case "max_control_line":
+ opts.MaxControlLine = ToInt(value);
+ break;
+ case "max_connections" or "max_conn":
+ opts.MaxConnections = ToInt(value);
+ break;
+ case "max_pending":
+ opts.MaxPending = ToLong(value);
+ break;
+ case "max_subs" or "max_subscriptions":
+ opts.MaxSubs = ToInt(value);
+ break;
+ case "max_sub_tokens" or "max_subscription_tokens":
+ var tokens = ToInt(value);
+ if (tokens > 256)
+ errors.Add("max_sub_tokens cannot exceed 256");
+ else
+ opts.MaxSubTokens = tokens;
+ break;
+ case "max_traced_msg_len":
+ opts.MaxTracedMsgLen = ToInt(value);
+ break;
+ case "max_closed_clients":
+ opts.MaxClosedClients = ToInt(value);
+ break;
+ case "disable_sublist_cache" or "no_sublist_cache":
+ opts.DisableSublistCache = ToBool(value);
+ break;
+ case "write_deadline":
+ opts.WriteDeadline = ParseDuration(value);
+ break;
+
+ // Ping
+ case "ping_interval":
+ opts.PingInterval = ParseDuration(value);
+ break;
+ case "ping_max" or "ping_max_out":
+ opts.MaxPingsOut = ToInt(value);
+ break;
+
+ // Monitoring
+ case "http_port" or "monitor_port":
+ opts.MonitorPort = ToInt(value);
+ break;
+ case "https_port":
+ opts.MonitorHttpsPort = ToInt(value);
+ break;
+ case "http":
+ ParseMonitorListen(value, opts, isHttps: false);
+ break;
+ case "https":
+ ParseMonitorListen(value, opts, isHttps: true);
+ break;
+ case "http_base_path":
+ opts.MonitorBasePath = ToString(value);
+ break;
+
+ // Lifecycle
+ case "lame_duck_duration":
+ opts.LameDuckDuration = ParseDuration(value);
+ break;
+ case "lame_duck_grace_period":
+ opts.LameDuckGracePeriod = ParseDuration(value);
+ break;
+
+ // Files
+ case "pidfile" or "pid_file":
+ opts.PidFile = ToString(value);
+ break;
+ case "ports_file_dir":
+ opts.PortsFileDir = ToString(value);
+ break;
+
+ // Auth
+ case "authorization":
+ if (value is Dictionary authDict)
+ ParseAuthorization(authDict, opts, errors);
+ break;
+ case "no_auth_user":
+ opts.NoAuthUser = ToString(value);
+ break;
+
+ // TLS
+ case "tls":
+ if (value is Dictionary tlsDict)
+ ParseTls(tlsDict, opts, errors);
+ break;
+ case "allow_non_tls":
+ opts.AllowNonTls = ToBool(value);
+ break;
+
+ // Tags
+ case "server_tags":
+ if (value is Dictionary tagsDict)
+ ParseTags(tagsDict, opts);
+ break;
+
+ // Profiling
+ case "prof_port":
+ opts.ProfPort = ToInt(value);
+ break;
+
+ // System account
+ case "system_account":
+ opts.SystemAccount = ToString(value);
+ break;
+ case "no_system_account":
+ opts.NoSystemAccount = ToBool(value);
+ break;
+ case "no_header_support":
+ opts.NoHeaderSupport = ToBool(value);
+ break;
+ case "connect_error_reports":
+ opts.ConnectErrorReports = ToInt(value);
+ break;
+ case "reconnect_error_reports":
+ opts.ReconnectErrorReports = ToInt(value);
+ break;
+
+ // Unknown keys silently ignored (cluster, jetstream, gateway, leafnode, etc.)
+ default:
+ break;
+ }
+ }
+
+ // ─── Listen parsing ────────────────────────────────────────────
+
+ ///
+ /// Parses a "listen" value that can be:
+ ///
+ /// - ":4222" — port only
+ /// - "0.0.0.0:4222" — host + port
+ /// - "4222" — bare number (port only)
+ /// - 4222 — integer (port only)
+ ///
+ ///
+ private static void ParseListen(object? value, NatsOptions opts)
+ {
+ var (host, port) = ParseHostPort(value);
+ if (host is not null)
+ opts.Host = host;
+ if (port is not null)
+ opts.Port = port.Value;
+ }
+
+ ///
+ /// Parses a monitor listen value. For "http" the port goes to MonitorPort;
+ /// for "https" the port goes to MonitorHttpsPort.
+ ///
+ private static void ParseMonitorListen(object? value, NatsOptions opts, bool isHttps)
+ {
+ var (host, port) = ParseHostPort(value);
+ if (host is not null)
+ opts.MonitorHost = host;
+ if (port is not null)
+ {
+ if (isHttps)
+ opts.MonitorHttpsPort = port.Value;
+ else
+ opts.MonitorPort = port.Value;
+ }
+ }
+
+ ///
+ /// Shared host:port parsing logic.
+ ///
+ private static (string? Host, int? Port) ParseHostPort(object? value)
+ {
+ if (value is long l)
+ return (null, (int)l);
+
+ var str = ToString(value);
+
+ // Try bare integer
+ if (int.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out var barePort))
+ return (null, barePort);
+
+ // Check for host:port
+ var colonIdx = str.LastIndexOf(':');
+ if (colonIdx >= 0)
+ {
+ var hostPart = str[..colonIdx];
+ var portPart = str[(colonIdx + 1)..];
+ if (int.TryParse(portPart, NumberStyles.Integer, CultureInfo.InvariantCulture, out var p))
+ {
+ var host = hostPart.Length > 0 ? hostPart : null;
+ return (host, p);
+ }
+ }
+
+ throw new FormatException($"Cannot parse listen value: '{str}'");
+ }
+
+ // ─── Duration parsing ──────────────────────────────────────────
+
+ ///
+ /// Parses a duration value. Accepts:
+ ///
+ /// - A string with unit suffix: "30s", "2m", "1h", "500ms"
+ /// - A number (long/double) treated as seconds
+ ///
+ ///
+ internal static TimeSpan ParseDuration(object? value)
+ {
+ return value switch
+ {
+ long seconds => TimeSpan.FromSeconds(seconds),
+ double seconds => TimeSpan.FromSeconds(seconds),
+ string s => ParseDurationString(s),
+ _ => throw new FormatException($"Cannot parse duration from {value?.GetType().Name ?? "null"}"),
+ };
+ }
+
+ private static readonly Regex DurationPattern = new(
+ @"^(-?\d+(?:\.\d+)?)\s*(ms|s|m|h)$",
+ RegexOptions.Compiled | RegexOptions.IgnoreCase);
+
+ private static TimeSpan ParseDurationString(string s)
+ {
+ var match = DurationPattern.Match(s);
+ if (!match.Success)
+ throw new FormatException($"Cannot parse duration: '{s}'");
+
+ var amount = double.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
+ var unit = match.Groups[2].Value.ToLowerInvariant();
+
+ return unit switch
+ {
+ "ms" => TimeSpan.FromMilliseconds(amount),
+ "s" => TimeSpan.FromSeconds(amount),
+ "m" => TimeSpan.FromMinutes(amount),
+ "h" => TimeSpan.FromHours(amount),
+ _ => throw new FormatException($"Unknown duration unit: '{unit}'"),
+ };
+ }
+
+ // ─── Authorization parsing ─────────────────────────────────────
+
+ private static void ParseAuthorization(Dictionary dict, NatsOptions opts, List errors)
+ {
+ foreach (var (key, value) in dict)
+ {
+ switch (key.ToLowerInvariant())
+ {
+ case "user" or "username":
+ opts.Username = ToString(value);
+ break;
+ case "pass" or "password":
+ opts.Password = ToString(value);
+ break;
+ case "token":
+ opts.Authorization = ToString(value);
+ break;
+ case "timeout":
+ opts.AuthTimeout = value switch
+ {
+ long l => TimeSpan.FromSeconds(l),
+ double d => TimeSpan.FromSeconds(d),
+ string s => ParseDuration(s),
+ _ => throw new FormatException($"Invalid auth timeout type: {value?.GetType().Name}"),
+ };
+ break;
+ case "users":
+ if (value is List