Configuration
The host loads <root>/config/moongate.toml with ConfigHelper.Load. A missing
file and its parent directory are created with defaults. Existing files are
deserialized and validated without being rewritten; omitted properties keep
their model defaults. Invalid TOML, invalid settings and filesystem errors fail
startup. Changes take effect at the next start; there is no configuration reload.
Complete default configuration
Section titled “Complete default configuration”TOML keys use snake_case. Keep mode before the first table header:
mode = "standalone" # Validated; selects no services yet.
[shard]shard_name = "Moongate"
[network]game_port = 2593listen_address = "0.0.0.0"enable_ping_server = true # Reserved: currently not consumed by the host.
[api]enabled = falselisten_address = "0.0.0.0"port = 2594auto_generate_certificate = falsecertificate_dns_names = ["localhost"]certificate_ip_addresses = ["127.0.0.1", "::1"]certificate_path = ""certificate_password_environment_variable = "MOONGATE_API_CERTIFICATE_PASSWORD"trusted_root_paths = []peers = []
[ultima]ultima_path = "ChangeMe" # Replace with your client data directory.
[persistence]auto_sync_schema = false
[persistence.accounts]connection_string = "postgres://moongate:moongate@localhost:5432/auth"
[persistence.realm]connection_string = "postgres://moongate:moongate@localhost:5432/world"
[world_save]enabled = true # Enables periodic saves; manual/final saves remain available.interval_seconds = 300
[diagnostics]enabled = trueinterval_seconds = 5log_metrics = false
[scripting]bootstrap_file = "init.lua" # Relative to <root>/scripts.max_instructions_per_resume = 150000max_instructions_per_chunk = 10000000hook_interval = 1000write_definitions = truemax_string_length = 16777216Both databases must already exist and accept connections before normal startup,
including login-only or game-only processes and hosts without persistence entities.
The defaults use local development credentials moongate / moongate; an existing
configuration file is not rewritten. For deployment, set each connection_string
to a secret-provider environment reference such as $MOONGATE_ACCOUNTS_DATABASE
or $MOONGATE_REALM_DATABASE. Merely exporting those variables does not override
a literal URI in the TOML file.
MoongatePersistenceService opens each database and runs SELECT 1. Each success
logs Postgres connection successful with the target and endpoint, without credentials.
A connection or ping failure throws and prevents other services from starting.
Moongate does not create missing databases; schema and migration checks run after
the connection checks. See PostgreSQL persistence.
Settings and validation
Section titled “Settings and validation”| Setting | Meaning and limits |
|---|---|
mode |
login, game or standalone; default standalone. Empty and unknown values fail. Maps to ServerMode, with Standalone = Login | Game. Selects no services yet; see Implementation status. |
shard.shard_name |
Shard display metadata; does not implement realm discovery or a server list by itself. |
network.game_port |
TCP listener port; use a distinct port for each local instance. |
network.listen_address |
IP literal, not a DNS hostname. 0.0.0.0 makes the host enumerate local unicast addresses and create endpoints for them, including IPv6 addresses; it is not a single wildcard listener. Use a specific IP to restrict binding. |
network.enable_ping_server |
Serialized setting with no current runtime consumer. It does not disable the registered UO ping handler. |
api.enabled |
Enables the internal MessagePack/mTLS listener; default false. Disabled APIs log a warning and leave handlers unfrozen; certificate I/O occurs only if generation is explicitly enabled. |
api.listen_address |
IPv4/IPv6 literal; default 0.0.0.0 binds one IPv4 wildcard listener. Unlike the game listener, it does not enumerate interfaces. |
api.port |
TCP port from 1 through 65535; default 2594. |
api.auto_generate_certificate |
Default false. Creates a missing PFX and exports its public .pem copy, even with enabled = false. Existing PFX files are never replaced. |
api.certificate_dns_names |
DNS SANs for generation; default ["localhost"]. No URLs or wildcards. |
api.certificate_ip_addresses |
IP SANs for generation; default ["127.0.0.1", "::1"]. No scope identifiers; at least one DNS name or IP is required across both arrays. |
api.certificate_path |
Local PKCS#12/PFX file containing the server leaf certificate and private key. |
api.certificate_password_environment_variable |
Name of the environment variable containing the PFX password. If named but unset, startup fails. An empty name permits an unencrypted PFX. Never put the password itself in TOML. |
api.trusted_root_paths |
When enabled, a nonempty array of trusted private CA certificates or explicitly trusted self-signed peer certificates (PEM or DER). Relative certificate/root paths resolve under <root>/config, independent of working directory. |
api.peers |
Nonempty array of allowed certificate identities; see the example below. Each fingerprint is unique ignoring case. |
api.peers.certificate_sha256 |
Exactly 64 hexadecimal characters identifying the peer’s leaf certificate; no colons. |
api.peers.peer_id |
Nonblank local identity for this peer. Multiple certificates may map to one identity during rotation. |
api.peers.allowed_operations |
["*"] grants all registered operations, including future additions. Otherwise use integer IDs from 1 through 65535. Empty or omitted denies all incoming operations; the wildcard must appear alone. |
ultima.ultima_path |
Existing, readable client data directory. Path and environment expansion apply; relative paths use the process working directory. |
persistence.auto_sync_schema |
Defaults to false. Normal startup checks versioned SQL history; when false it also fails if registered entities require DDL. Generate and review SQL, then apply it with the separate migration runner. Enable only as an explicit development convenience. |
persistence.accounts.connection_string |
Accounts/login PostgreSQL URI, or $NAME / ${NAME} environment reference. Resolved only when registered entities use Accounts. |
persistence.realm.connection_string |
This realm’s PostgreSQL URI, or $NAME / ${NAME} environment reference. Resolved only when registered entities use Realm. |
world_save.enabled |
Starts periodic autosaving when true. Does not disable explicit saves or the eligible final shutdown save. |
world_save.interval_seconds |
Positive integer seconds, validated even when autosaving is disabled. |
diagnostics.enabled |
Starts the periodic diagnostic collector when true. |
diagnostics.interval_seconds |
Positive integer seconds; must fit the timer range (at most 4,294,967 seconds). |
diagnostics.log_metrics |
Logs periodic collected metrics when true. |
scripting.bootstrap_file |
Nonblank path resolved within the scripts root; must satisfy the script path restrictions. Missing file is a warning, execution failure aborts startup. |
scripting.max_instructions_per_resume |
Positive instruction budget for one coroutine resume. |
scripting.max_instructions_per_chunk |
Positive instruction budget for top-level chunk execution. |
scripting.hook_interval |
Positive instruction-check interval, no greater than either instruction budget. |
scripting.write_definitions |
Generates definitions.lua and .luarc.json for editor support. |
scripting.max_string_length |
Positive maximum result length enforced by string.rep, measured in UTF-16 characters; not a global Lua memory limit. |
Full API validation applies when api.enabled is true. Certificate provisioning
settings are also validated when api.auto_generate_certificate is true. Invalid API configuration,
missing/unreadable certificates, a local leaf outside its validity window, an explicit
EKU excluding server authentication, a missing private key, a wrong password or an
occupied port fail startup;
services already started are stopped in reverse order. With both options false, incomplete API
settings are ignored. Provisioning with the listener disabled does not require trust roots or peers. There is no plaintext fallback.
Game-loop queue limits, timer-wheel resolution and packet dispatch limits use C#
option objects rather than additional TOML sections. The hosted API uses the
library’s default ApiOptions limits and timeouts. See
Game loop and timers, Packets and the
internal API library.
Enable the internal API server
Section titled “Enable the internal API server”API hosting ships from 0.4.0 and automatic certificate generation from 0.5.0.
-
Provision certificates using the API certificate guide. It covers automatic self-signed generation with the port closed, public certificate exchange, passwords, Docker and renewal. The example below uses an externally issued PFX and private CA root. The server needs
serverAuthusage and a DNS name matching the client’s TLS target host; clients needclientAuth. Mount certificates read-only where possible and allow the runtime user to read them. -
Replace the generated
[api]section with this example. Substitute the client leaf’s SHA-256 fingerprint for the illustrative value:[api]enabled = truelisten_address = "0.0.0.0"port = 2594certificate_path = "tls/server.pfx"certificate_password_environment_variable = "MOONGATE_API_CERTIFICATE_PASSWORD"trusted_root_paths = ["tls/root.pem"][[api.peers]]certificate_sha256 = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"peer_id = "admin-console"allowed_operations = ["*"]Use
["*"]for a fully trusted peer, or an explicit list such as[100, 200]to restrict its operations.[]and omission keep all operations denied. -
Inject
MOONGATE_API_CERTIFICATE_PASSWORDfrom your credential provider into the server environment and restart. A successful bind logsAPI listener started at ...with the actual endpoint and contract/handler counts. The default disabled state logsAPI server is disabledwith activation guidance.
The host registers IApiServerService as a singleton. It starts at priority 110,
after game packet services, and drains/disposes the listener before they stop.
Endpoint is the actual bound endpoint while accepting connections, otherwise
null. A stopped host service is terminal: start a new host to reload configuration
or certificate permissions.
Register typed handlers in Program.cs’s RegisterServices callback or a plugin’s
Register(Container) method, before startup:
// using Moongate.Server.Extensions;// IncrementHandler implements IApiHandler<IncrementRequest, IncrementResponse>.container.RegisterApiHandler<IncrementHandler>();The complete typed handler example
shows these request/response types. Plugin registration runs before the API registry
freezes at startup; the same registry is used regardless of registration order.
Handler service dependencies that require startup must start before priority 110.
API handlers execute outside the game loop; explicitly marshal world changes to
IGameLoopService as described in Game loop and timers.
The listener speaks MessagePack over mutual TLS/TCP, not HTTP. No built-in login, realm discovery or administration operations are registered yet. A listener with zero handlers can authenticate configured peers but cannot serve application requests. See Docker for private-network deployment.
Command line and root directory
Section titled “Command line and root directory”Inspect the installed executable with --help. From a checkout:
dotnet run --project src/Moongate.Server -c Release -- --helpdotnet run --project src/Moongate.Server -c Release -- \ --root-directory /absolute/path/to/moongate-data --pid-file-name moongate.pid| Option | Default | Current behavior |
|---|---|---|
--root-directory <path> |
Unset | Overrides MOONGATE_ROOT; otherwise the executable directory is used |
--pid-file-name <name> |
moongate.pid |
PID filename under the chosen root; use a plain filename |
--log-level <level> |
Information |
Parsed into server arguments, but currently not applied to the Serilog level policy |
--log-to-file |
true |
File logging is enabled; the generated parser only accepts this as a presence flag |
--log-packets |
false |
Sets the argument to true; currently no packet-tracing consumer |
--show-header |
true |
Shows the startup banner; presence flag |
--persistence-schema <mode> |
None |
preview prints draft PostgreSQL DDL; generate writes a draft file. The old apply mode directs you to Moongate.MigrationRunner |
--migration-target <target> |
Unset | Required by generate: auth or world |
--migration-output <path> |
Unset | Required by generate: new NNNN_description.sql file; refuses overwrite |
--version |
— | Prints executable version |
-h, --help |
— | Prints usage |
Although help displays <bool> for the default-true options, the current CLI
does not accept --show-header false, --show-header=false or corresponding
file-logging forms. There is no CLI switch to turn these two options off yet.
Root precedence is command line → MOONGATE_ROOT → executable directory.
The chosen root expands home/environment references and becomes an absolute path;
a relative root starts from the working directory. Prefer explicit absolute paths
in service managers and containers. Docker sets MOONGATE_ROOT=/data by default.
The schema command loads plugin persistence registrations but does not acquire the normal PID, start listeners/services, or generate runtime files. Stop the affected runtime before applying reviewed DDL. See First start for PID ownership, logs and troubleshooting, PostgreSQL persistence for connection, schema and world-save semantics, and Lua scripting for budgets and sandbox boundaries.
Versioned SQL is applied by the isolated migration-runner/Moongate.MigrationRunner
executable using status|apply --target auth|world. In released artifacts its default
root is the parent server directory; --root-directory and MOONGATE_ROOT override
it. See Generate, review and apply.
