Skip to content

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.

The welcome text shown when a character enters the world lives in a separate data/motd.toml file, with its own variable and validation rules.

TOML keys use snake_case. Keep mode before the first table header.

The server owns mode and the sections [shard], [network], [redis], [persistence], [realm_directory], [world_save], [sql_backup], [diagnostics] and [scripting]. Plugins own the others: [ultima] belongs to the Ultima plugin and [admin_api] to the Administration plugin. A new root written by mgctl holds the server’s sections (and [admin_api] when it sets up a certificate); at the first start each plugin appends its missing section, with the defaults, to the end of the file. See Add a config section.

mode = "standalone" # Runs login and game services together.
[shard]
shard_name = "Moongate"
[network]
login_port = 2593
game_port = 2595
listen_address = "0.0.0.0"
enable_ping_server = true # Answers UDP pings on ping_port.
ping_port = 12000
[network.encryption]
mode = "Disabled"
client_version = ""
[redis]
connection_string = "$MOONGATE_REDIS_CONNECTION_STRING"
handoff_secret = "$MOONGATE_HANDOFF_SECRET"
# Administration plugin.
[admin_api]
enabled = false
# Use "*" or "0.0.0.0" for all IPv4 interfaces (TLS required).
listen_address = "127.0.0.1"
port = 2590
session_lifetime_minutes = 30
# The longest a session lives from its login, renewals included (AdminSession.Refresh); never below the lifetime.
session_max_age_minutes = 480
max_receive_message_bytes = 65536
max_concurrent_calls = 64
allow_insecure_loopback = false
certificate_path = ""
certificate_password = ""
# Ultima plugin.
[ultima]
ultima_path = "ChangeMe" # Replace with your client data directory.
show_motd_gump = true # Native Updates scroll; false uses private journal messages.
[ultima.localization]
language = "eng" # Reads <root>/data/messages/eng.toml and eng/*.toml.
[ultima.line_of_sight]
max_distance = 25 # Farthest cells along X or Y a point can see.
[ultima.world]
view_range = 18 # How far players see mobiles and items, in cells along X or Y.
seconds_per_uo_minute = 5 # Real seconds a game minute lasts: 5 makes a game day last 2 real hours.
day_light = 0 # Light level of the day, from 0 (brightest) to 31.
night_light = 12 # Light level of the night, from 0 (brightest) to 31.
dungeon_light = 26 # Light level inside a dungeon region, from 0 (brightest) to 31.
jail_light = 9 # Light level inside a jail region, from 0 (brightest) to 31.
lamp_post_light = 6 # Light level from which the town lamp posts are lit, from 0 to 31.
season_rotation = false # true: the maps change season every days_per_season game days.
days_per_season = 12 # Game days a season lasts when they rotate, from 1 to 365.
pathfinding_range = 38 # How far apart the two ends of a path search may be, in tiles, from 8 to 64.
pathfinding_max_nodes = 1000 # Places a path search looks at before it gives up, from 50 to 20000.
[ultima.items]
backpack_template = "backpack_2" # Item template of the backpack of new characters and spawned NPCs.
gold_template = "gold_coin" # Item template of gold coins.
[ultima.starting_items]
best_skills = 3 # How many of the highest skills pick skill sets.
[ultima.characters]
max_per_account = 7 # Characters an account may hold: 1, 5, 6 or 7.
deletion_delay_hours = 24 # Hours before a deleted character may be removed.
[ultima.npcs]
think_interval_ms = 500 # Milliseconds between two thinks of an NPC near a player.
sense_range = 8 # Cells within which an NPC's script senses another mobile.
[ultima.regeneration]
hits_seconds = 11.0 # Seconds between two hit points coming back.
stamina_seconds = 7.0 # Seconds between two points of stamina coming back.
mana_seconds = 7.0 # The same for mana, with no intelligence and no Meditation.
hunger_enabled = true # Players get hungry; starving, they get no hit points back.
thirst_enabled = true # Players get thirsty; parched, they get no stamina back.
hunger_minutes = 5 # Minutes between two points of hunger, and of thirst, lost by a player.
fatigue_enabled = true # Running and moving overloaded cost a player stamina.
[ultima.skills]
total_cap = 700 # Points the skills of a player add up to at most.
gain_enabled = true # A skill, and a stat, may rise when a skill is tried.
stat_max = 100 # The most a player's strength, dexterity or intelligence reaches by use.
stat_cap = 225 # The most the three stats of a player add up to.
stat_gain_minutes = 10 # Minutes a stat waits after it was tried before it is tried again.
[ultima.combat]
global_attack_speed = 1.0 # Divides the delay between swings: 2 swings twice as often.
attack_stamina = 0 # Stamina a swing costs a player; 0 costs nothing.
npc_damage_rate = 1.0 # Divides the damage an NPC does to a player.
max_range = 1 # Tiles a melee swing reaches.
combatant_seconds = 60 # Seconds a fighter keeps its target without swinging.
display_damage_numbers = true # The damage shows over the one hit.
blood_enabled = true # A hit that does damage leaves blood on the ground.
blood_pieces = 2 # Most pieces around the one under the victim, from 0 to 8.
blood_seconds = 5 # Seconds a piece lies on the ground, from 1 to 60.
item_wear_enabled = true # Weapons and armor wear out in a fight.
archery_stand_still_seconds = 1.0 # Seconds a player must have stood still before it shoots; 0 for none.
special_moves = false # The AOS weapon special moves: the client selects one, the next hit does it.
resistances = false # The five resistances reduce the damage, as the AOS era does.
[ultima.crime]
criminal_seconds = 120 # How long a mobile stays a criminal after its last criminal act.
guards_enabled = true # Saying "guards" in a guarded region brings a guard beside a criminal.
guard_template = "guard" # The mobile template of a guard that is called.
archer_guard_template = "archerguard" # The one called in Ilshenar and Malas.
guard_seconds = 40 # How long a called guard stays.
loot_right_seconds = 120 # Seconds a creature's corpse is lootable with no crime only by those who earned it; 0 turns it off.
[ultima.murder]
short_term_hours = 8 # Hours before a short-term murder is forgotten.
long_term_hours = 40 # Hours before a reported kill is forgotten.
report_delay_seconds = 4 # Seconds after a death before the victim is asked to report.
recently_reported_minutes = 10 # Minutes before the same victim can report the same killer again.
aggressor_seconds = 120 # Seconds an act keeps its attacker in the aggressor lists.
[ultima.spawns]
initial_fill = true # The first spawn of each region after the start fills it to its max.
[ultima.jail]
fine_gold = 500 # Gold coins taken from a prisoner when its jail sentence ends; 0 takes nothing.
max_days = 30 # The longest sentence the jail gump accepts, in real days.
[ultima.help]
stuck_wait_seconds = 5 # The seconds a character must stand still before "I am stuck" moves it.
stuck_cooldown_minutes = 10 # The minutes before a player can use "I am stuck" again; 0 allows it at once.
page_cooldown_seconds = 60 # The seconds between two requests of one player to the game masters; 0 allows it at once.
page_history_days = 30 # The days a closed request is kept before it is deleted at startup.
[ultima.schedule]
time_zone = "" # The IANA time zone of the hours in data/schedule.toml, such as Europe/Rome; empty is the zone of the system.
[ultima.bulletin_boards]
expire_days = 7 # A thread of a bulletin board goes this many days after its last reply; 0 keeps it.
max_messages = 50 # The messages a board holds; its oldest thread goes when it is full.
thread_seconds = 120 # The wait between two new threads of one character on a board.
reply_seconds = 30 # The wait between two posts of one character on a board.
[ultima.bank]
max_items = 125 # Items a bank box holds, bags included; 0 for no limit.
max_withdraw = 60000 # Coins a banker hands out at one time.
min_check = 5000 # The smallest bank check a banker writes.
max_check = 1000000 # The largest.
[ultima.party]
max_members = 10 # Players a party holds, invitations waiting included, from 2 to 10.
invite_timeout_seconds = 30 # Seconds an invitation waits for an answer.
invite_range = 8 # Tiles away the invited player may stand.
chat_max_length = 128 # Characters of a chat message; a longer one is cut.
[ultima.pets]
max_followers = 5 # Followers a player may have, from 1 to 50: the slots of the creatures it tamed that are in the world, and of the one it rides.
loyalty_drain_minutes = 60 # Minutes between two drains of the loyalty of the pets in the world, from 1 to 1440.
loyalty_drain = 10 # Loyalty a pet loses at each drain, from 1 to 100; at 0 it goes wild.
food_gain = 10 # Loyalty a pet gains for each item of food it eats, from 1 to 100.
obey_gain = 1 # Loyalty a pet gains whenever it obeys an order, from 0 to 100.
disobey_loss = 3 # Loyalty a pet loses when it refuses one, from 0 to 100.
bonding_days = 7 # Days between the first food of its owner and the food that bonds a pet, from 0 to 365.
hire_pay_minutes = 30 # Minutes of one period a hireling is paid for, from 1 to 1440: its gold pays one period of its daily pay at the end of each.
[ultima.stable]
max_pets = 10 # Pets a player may leave with the stablemasters, from 1 to 50.
fee = 30 # Gold a pet costs when it is stabled, from the backpack and then the bank; 0 makes it free.
[ultima.vendors]
uncommon_multiplier = 1.25 # What a vendor pays for an uncommon item, as a multiple of the price of its shop, from 1 to 100.
rare_multiplier = 1.5 # The same for a rare item.
epic_multiplier = 2.0 # The same for an epic item.
legendary_multiplier = 3.0 # The same for a legendary item. A common item is paid the price of the shop.
[persistence]
auto_sync_schema = false
auto_generate_migrations = false # true: a development start writes and applies additive SQL; needs migrations_directory.
migrations_directory = "${MOONGATE_ROOT}/migrations"
auto_apply_migrations = false # true: a start adds the bundled core SQL and applies what is pending.
[persistence.accounts]
connection_string = "postgres://moongate:moongate@localhost:5432/auth"
[persistence.realm]
connection_string = "postgres://moongate:moongate@localhost:5432/world"
[realm_directory]
realm_id = ""
name = ""
server_index = 0
advertised_address = ""
advertised_port = 0
minimum_account_type = "regular"
heartbeat_interval_seconds = 5
lease_duration_seconds = 15
max_realms = 128
[world_save]
enabled = true # Enables periodic saves; manual/final saves remain available.
interval_seconds = 300
[sql_backup]
enabled = false # Scheduled SQL backups; the sql_backup command works either way.
interval_minutes = 1440
directory = "backups" # Relative to the server root.
keep = 5 # Copies kept for each database.
[diagnostics]
enabled = true
interval_seconds = 5
log_metrics = false
[scripting]
bootstrap_file = "init.lua" # Relative to <root>/scripts.
max_instructions_per_resume = 150000
max_instructions_per_chunk = 10000000
hook_interval = 1000
write_definitions = true
max_string_length = 16777216

Only the databases for the active role must already exist and accept connections: Accounts for login, Realm for game, and both for standalone. 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 active database and runs SELECT 1. The Redis service also checks its connection before accepting clients. Each PostgreSQL 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.

Setting Meaning and limits
mode login, game or standalone; default standalone. Login runs account authentication, a login packet listener and realm directory; Game runs world services and publishes its realm to Redis; Standalone runs both roles and publishes its local realm to Redis. See TOML value types.
shard.shard_name Shard display metadata; used as the standalone list name when it fits the 32-character ASCII wire limit. Otherwise the local list name defaults to Moongate.
network.login_port Login TCP listener port; default 2593. Used in login and standalone modes.
network.game_port Game TCP listener port; default 2595. Used in game and standalone modes. Standalone rejects equal login and game ports.
network.listen_address IP literal, not a DNS hostname. 0.0.0.0 makes the host enumerate local unicast addresses and create an endpoint for each active role on every address, including IPv6 addresses; it is not a single wildcard listener. Standalone therefore starts two listeners per address. Use a specific IP to restrict binding.
network.enable_ping_server Default true. Starts the UDP ping server in every mode: it sends each datagram of at most 64 bytes back to its sender, so a client can measure the latency of the shard; a larger datagram gets no answer, and neither does one that comes from the ping port itself (the echo of another ping server). It binds network.ping_port on the addresses of network.listen_address. An address or port that cannot be bound is logged as a warning and the server still starts. It does not affect the UO ping packet handler on the game connection.
network.ping_port UDP port of the ping server; default 12000, the port ModernUO uses. Must be between 1 and 65535 when the ping server is enabled.
network.encryption.mode Disabled (default), Optional or Required; applies to both UO listeners. See UO client encryption.
network.encryption.client_version Raw POL wire version used to derive login keys and select the game cipher family. Required for Optional and Required; ignored when Disabled. Default empty.
ultima.ultima_path Existing, readable client data directory. Path and environment expansion apply; relative paths use the process working directory. It must contain tiledata.mul, the map and statics files of every map in data/maps.toml, and MultiCollection.uop or multi.idx with multi.mul; the server stops at startup when one is missing.
ultima.show_motd_gump Defaults to true: show the MOTD in the native Updates scroll on each entry. false keeps private Unicode journal messages. Restart to apply.
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.auto_generate_migrations Defaults to false. A development convenience: a start writes additive SQL from the registered entities into migrations_directory and applies it. It requires an explicit migrations_directory and is exclusive with auto_sync_schema: either one stops the start. Keep it off in deployment. See Generate, review and apply.
persistence.migrations_directory Optional unless auto_generate_migrations is on. The directory of the reviewed SQL; environment variables and ~ are expanded. Without it the migrations are looked for in migrations of the root. mgctl init writes the absolute path into the new config.
persistence.auto_apply_migrations Defaults to false. When true, before the schema check a start does what mgctl init and mgctl migrate apply do for the migrations: it adds to the migrations directory the core SQL this release ships and the root lacks, replacing nothing, then applies the pending reviewed SQL of the databases this process uses, the SQL of the plugin bundles in plugins/ included. It never writes SQL from the entities. The database role of the connection must be allowed to change the schema, and mgctl must be beside the server, as in every distribution. A file of the root with the number of a shipped one and another name or content stops the start. It conflicts with auto_sync_schema. See Apply at startup.
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.
redis.connection_string Shared Redis endpoint and password in StackExchange.Redis format, or a $NAME / ${NAME} environment reference. Required by every runtime role.
redis.handoff_secret Separate cluster-wide proof secret or environment reference. Required by every runtime role; at least 32 bytes after UTF-8 encoding. Never reuse the Redis password.
realm_directory.realm_id Stable ID for a game realm and its Redis lease/ticket namespace. Standalone defaults to local; give each independently running realm a distinct ID.
realm_directory.name, server_index ASCII list name (at most 32 characters) and unique index (0–65535). Standalone defaults to the shard name and index zero; set a distinct index for each realm sharing Redis.
realm_directory.advertised_address, advertised_port Client-facing IPv4 literal and port. Required in game mode; standalone defaults to loopback and network.game_port. 0xA8 carries the address; 0x8C carries the selected realm port.
realm_directory.minimum_account_type Lowest account level allowed to see the realm; regular, game_master or administrator. See TOML value types.
realm_directory.heartbeat_interval_seconds, lease_duration_seconds, max_realms Defaults 5, 15 and 128. Lease duration must exceed two heartbeats; the Redis-backed directory caps realms at 128.
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.
sql_backup.enabled Runs a SQL backup on the schedule when true. The sql_backup command works either way. See Database backups.
sql_backup.interval_minutes Minutes between two scheduled backups, from 1 to 71582; default 1440. The first one runs a full interval after startup. Validated even when disabled.
sql_backup.directory Where the files go; default backups. A relative path is resolved against the server root; environment variables are expanded.
sql_backup.keep Files kept for each database, 1 or more; default 5.
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.
ultima.localization.language Code of ASCII letters naming the texts file data/messages/<language>.toml and the directory data/messages/<language>/ of toml files; default eng. Shipped: eng, ita, ger, fre, spa, por, pol, cze. The English texts must also exist: a message missing from the chosen language falls back to English. Used in game and standalone modes. See Localization.
ultima.line_of_sight.max_distance From 1 to 255; default 25. The farthest a point can see along X or Y, as ModernUO; farther points are never in sight. Used in game and standalone modes.
ultima.world.view_range From 5 to 24; default 18, as ModernUO and POL. How far players see mobiles and ground items along X or Y; the client’s 0xC8 request is answered with it. Used in game and standalone modes.
ultima.world.seconds_per_uo_minute From 1 to 3600; default 5, as ModernUO: a game day lasts 2 real hours. The time of day is counted from ModernUO’s world start and needs no save; each map runs 320 game minutes after the previous one (Felucca, Trammel, Ilshenar, Malas, Tokuno, Ter Mur), and the time moves one minute later every 16 tiles east.
ultima.world.day_light, ultima.world.night_light From 0 (brightest) to 31; defaults 0 and 12, as ModernUO. Night from 00:00 to 03:59, brightening until 06:00, day until 21:59, darkening until midnight. Players are sent their light at login and, when it changes, every 5 seconds; .globallight overrides it for everyone.
ultima.world.dungeon_light, ultima.world.jail_light From 0 (brightest) to 31; defaults 26 and 9, as ModernUO. The light inside a region of type dungeon or jail, whatever the time of day; sent as soon as a player walks in or out. .globallight still wins, so a game master can see.
ultima.world.lamp_post_light From 0 to 31; default 6. Every 30 seconds the town lamp posts placed by .decorate (kinds LampPost1 to LampPost3) are lit where the light of the time of day (or .globallight) is at least this level, and doused where it is below; with the default night of 12 that is about 23:00 to 05:00, game time. Regions do not count.
ultima.world.season_rotation, ultima.world.days_per_season false and 12 (1 to 365). When on, every map whose maps.toml season is not desolation goes spring, summer, fall, winter from that season, changing every days_per_season game days of its clock; a region with its own season keeps it. Players get the new season within a minute. See Seasons.
ultima.world.pathfinding_range, ultima.world.pathfinding_max_nodes 38 (8 to 64) and 1000 (50 to 20000), as ModernUO. The bounds of a path search: how far apart its two ends may be along X or Y, and how many places it looks at before it gives up. A search that gives up costs about a millisecond per hundred places
ultima.items.backpack_template, ultima.items.gold_template Item template ids; defaults backpack_2 and gold_coin. Used for the backpack of new characters and spawned NPCs and for the gold of spawned NPCs; the starting gold of new characters is an item of the common set in starting_items.toml. Both must exist in templates/items/, and the gold template must stack, or the game server stops at startup. See Starting items.
ultima.starting_items.best_skills At least 1; default 3, as UOX3 (four with its extended starting skills). How many of a new character’s highest skills pick skill sets.
ultima.characters.max_per_account 1, 5, 6 or 7, the slot counts the client can show; default 7. How many characters an account may hold. The game-login character list shows this many slots, and creating a character beyond it is refused with a popup and a disconnect. A new character goes in the slot the client chose when it is free, otherwise in the first free one. Lowering it keeps existing characters: those beyond the new count are listed in the first free slots, while the rest stay stored but hidden.
ultima.characters.deletion_delay_hours At least 1; default 24. When a player deletes a character (packet 0x83) it is only marked: it leaves the character list, gives up its slot and no longer counts toward max_per_account, and staff can restore it with character restore (into the first free slot). After this many hours it becomes eligible for removal; the job that removes it is not built yet.
ultima.npcs.think_interval_ms From 50 to 60000; default 500, ModernUO’s passive speed. How often an NPC near a player thinks. Only NPCs within the 5×5 sectors around a player have a think timer; the others sleep and cost nothing. See NPC tick.
ultima.npcs.sense_range From 1 to 24; default 8. How near, in cells along X or Y, another mobile must come for an NPC’s mobile script to sense it with on_mobile_in_range. See Mobile scripts.
ultima.regeneration.hits_seconds, stamina_seconds, mana_seconds Each from 0.1 to 3600; defaults 11, 7 and 7, ModernUO’s classic rates. The seconds between two points coming back, one point at a time. Mana is the slowest it gets: intelligence and Meditation shorten it by ModernUO’s classic curve, down to about a tenth of it. The players in the world are checked every second and an NPC while it thinks, so one asleep far from every player does not regenerate; a rate faster than the check gives the points of the whole interval at once, five at most. A mobile’s props regen.hits, regen.mana and regen.stamina (seconds) replace the rate for that mobile.
ultima.regeneration.hunger_enabled, hunger_minutes Default true and 5 (from 1 to 1440), ModernUO’s food decay. Every hunger_minutes, counted for each player from when it entered the world (one timer looks at the players every minute), a player in the world loses a point of hunger, from 20 (full) to 0; game masters and administrators do not, and never starve. At 5 the player reads that it is hungry, at 0 that it starves, and while at 0 its hit points do not come back. Off, hunger neither drops nor stops anything. NPCs are never hungry.
ultima.regeneration.thirst_enabled Default true. Thirst is counted as hunger is, from 20 (quenched) to 0 and at the same pace (hunger_minutes): a player in the world loses a point of each together, and the staff none. At 5 the player reads that it is thirsty, at 0 that it is parched, and while at 0 its stamina does not come back, as in UOX3. Off, thirst neither drops nor stops anything. NPCs are never thirsty.
ultima.regeneration.fatigue_enabled Default true. What moving costs a player, with ModernUO’s numbers. A player carries what it wears and everything inside, the bank left out, and may carry 40 stones and 3.5 a point of strength; its status bar shows both. Carrying more, every step takes 5 points of stamina and one more every 25 stones over, twice as much running; with none left it does not move and reads the client’s “too fatigued to move, because you are carrying too much weight”. Running takes a point every 16 steps, and a point a step below a tenth of the stamina; with none left a player walks but does not run. Lifting is never refused: a player that puts down something while overloaded is told so. The staff and NPCs pay nothing. Off, a step costs nothing and is never refused.
ultima.skills.total_cap, ultima.skills.gain_enabled 700 (from 1 to 100000) and true. The points the skills of a player add up to at most, ModernUO’s 700.0: the nearer the total, the more often a gain lowers another skill whose lock is down, and at the cap a skill rises only when one could be lowered. A total already above a lowered cap is left as it is. With gain_enabled = false no skill rises: the checks still pass and fail. NPCs never gain.
ultima.skills.stat_max, ultima.skills.stat_cap, ultima.skills.stat_gain_minutes 100 (from 1 to 65535), 225 (from 30 to 100000) and 10 (from 0 to 1440; 0 tries a stat at every successful skill). ModernUO’s classic stat gain: the most a stat reaches by use, the most the three add up to, and the minutes between two tries of one stat. Over a lowered stat_cap or stat_max a stat is left as it is, but one locked down still gives a point each time another stat is tried. gain_enabled = false stops these too. NPCs never gain.
ultima.combat.global_attack_speed, npc_damage_rate 1.0 each, above 0 and at most 100. The factor that divides the delay between two swings (2 swings twice as often, 0.5 half as often) and the number that divides the damage an NPC does to a player (2 halves it).
ultima.combat.attack_stamina 0 (from 0 to 100). The stamina a swing costs a player; ModernUO takes none, UOX3 takes 2. NPCs pay nothing.
ultima.combat.max_range, combatant_seconds 1 (from 1 to 24) and 60 (from 1 to 3600). How many tiles a melee swing reaches, and how long a fighter keeps its target without swinging, as ModernUO’s minute.
ultima.combat.archery_stand_still_seconds 1.0 (from 0 to 60). The seconds a player holding a bow or a crossbow must have stood still, since its last step, before it can shoot; a turn does not count. 0 asks for nothing. See Archers
ultima.combat.display_damage_numbers true. The damage of a hit shows over the one hit (packet 0x0B), to the players in the fight.
ultima.combat.blood_enabled, blood_pieces, blood_seconds true, 2 (from 0 to 8) and 5 (from 1 to 60). A hit that does damage leaves a piece of blood under the one hit and from one to blood_pieces around it; each piece goes after blood_seconds, which the check of the ground items every 5 seconds can stretch.
ultima.combat.special_moves false. The AOS-era weapon special moves: a player selects the move of the held weapon with the client’s ability icon and the next hit that lands spends the mana and does it. Off, as the classic ruleset, the selection is read and ignored.
ultima.combat.resistances false. The five resistances (physical, fire, cold, poison, energy) of a mobile and its worn armor reduce the damage it takes, up to 70 percent for a player. Off, as the classic ruleset, armor absorbs a blow and the damage of a spell ignores the resistances.
ultima.combat.item_wear_enabled true. The weapon and the armor of a player lose hits in a fight, see Durability. Off, nothing wears and nothing is destroyed; the pieces keep the hits they have.
ultima.crime.criminal_seconds Default 120 (from 1 to 86400), ModernUO’s and UOX3’s two minutes. A mobile that did a criminal act is a criminal for this long, its name in grey for those who see it; another act starts the time again, and a murderer stays red. The time is saved with a character, so leaving the world does not clear it; an NPC comes back innocent after a restart. A player reads the client’s “You’ve committed a criminal act!!” when it becomes one. A player becomes one by attacking an innocent or by looting an innocent’s corpse; scripts do with mobile.set_criminal, the staff with set criminal.
ultima.murder.* The murder counts, as ModernUO’s. short_term_hours (default 8) and long_term_hours (default 40), from 1 to 8760: a short-term murder and a reported kill are each forgotten after that time, one by one, in real time, offline hours included (ModernUO counts online time), while the player is in the world or when it comes back. report_delay_seconds (default 4), recently_reported_minutes (default 10) and aggressor_seconds (default 120), from 1 to 86400: how long after its death a player is asked to report who attacked it, how long before it can report the same killer again, and how long an act keeps its attacker in the aggressor lists, which also decide who is reportable. See Murder counts.
ultima.crime.archer_guard_template Default archerguard. The mobile template of a guard that is called in Ilshenar and Malas, as ModernUO’s DefaultGuardType: a guard with a bow, which shoots what it goes for from the bow’s range instead of striking from beside it (see guard.lua). Everywhere else the guard of guard_template comes.
ultima.crime.guards_enabled, guard_template, guard_seconds Default true, guard and 40 (from 1 to 86400). A player that says “guards” in a guarded region, by the keyword of its client in any language or by the plain word, calls the guards: for every criminal within 14 tiles that stands in a guarded region itself, the staff left out, an NPC of guard_template appears beside it, on a free tile a step away (on it when none is free), with the teleport effect and sound and says its line (message 30138); a criminal gets one guard at a time. The guard leaves the same way after guard_seconds. A guard kills what it goes for with one blow, and an archer guard shoots it. ModernUO is stricter about who can be called on: there, only who did its crime in that town in the last 15 seconds, and a murderer; here, anyone who is a criminal. A called guard bears the prop guard.summoned: one a stopped server left in the world is removed at the next start. Off, nobody comes.
ultima.crime.loot_right_seconds Default 120 (from 0 to 86400), ModernUO’s two minutes; 0 turns the rule off. For this long after a creature dies only the players that earned the right, and the master of a tamed creature, loot its corpse with no crime; anyone else who takes an item from it is a criminal, and a creature nobody hurt enough is a crime for everyone until the time is over. Staff are free. See Loot rights of a creature.
ultima.spawns.initial_fill Default true. The first spawn of each spawn region after the start fills it to its max at once, so an empty world is full in about 10 minutes; false keeps UOX3’s way, where the first spawn also brings only call NPCs. Used in game and standalone modes. See NPC spawns.
ultima.jail.fine_gold, ultima.jail.max_days Defaults 500 (from 0 to 1,000,000,000) and 30 (from 1 to 3650). The gold taken from a prisoner when its sentence ends, from its backpack and then its bank box, and the longest sentence .jail accepts, in real days; see Jail.
ultima.help.stuck_wait_seconds, ultima.help.stuck_cooldown_minutes Defaults 5 (from 1 to 60) and 10 (from 0 to 1440). The wait and the pause of the “I am stuck” button of the help gump.
ultima.help.page_cooldown_seconds, ultima.help.page_history_days Defaults 60 (from 0 to 3600) and 30 (from 1 to 3650). The seconds a player waits between two requests to the game masters, and the days a closed request is kept before startup deletes it; see Help.
ultima.schedule.time_zone Empty (the zone of the system) or an IANA id such as Europe/Rome. An unknown id stops the startup. A Docker container is in UTC unless it has TZ set or this setting; on Linux the zones need the tzdata package. See Schedule.
ultima.bulletin_boards.expire_days, max_messages, thread_seconds, reply_seconds Defaults 7 (from 0 to 3650; 0 keeps threads forever), 50 (from 1 to 200), 120 and 30 (from 0 to 86400). How long a thread of a bulletin board lasts after its last reply, how many messages a board holds, and how long a character waits between two new threads and between two posts on one board.
ultima.bank.max_items, max_withdraw, min_check, max_check Defaults 125 (from 0 to 10000; 0 for no limit), 60000 (from 1 to 60000), 5000 and 1,000,000 (min_check from 1 to max_check, max_check up to 2,000,000,000). The items a bank box holds, counted with what is inside its bags; the coins a banker hands out for one withdraw; the worth of the checks a banker writes.
ultima.party.max_members, invite_timeout_seconds, invite_range, chat_max_length Defaults 10 (from 2 to 10), 30 (from 1), 8 (from 1) and 128 (from 1). The players a party holds, counting those invited who have not answered; the seconds an invitation waits before it is declined; the tiles away the invited player may stand; the characters of a chat message.
ultima.stable.max_pets, ultima.stable.fee Defaults 10 (from 1 to 50) and 30 (from 0 to 100000). The pets a player may leave with an animal trainer, and what each costs when it is stabled: see the stable.
ultima.pets.max_followers Default 5 (from 1 to 50). The followers a player may have: see animal taming.
ultima.pets.loyalty_drain_minutes, loyalty_drain, food_gain, obey_gain, disobey_loss, bonding_days, hire_pay_minutes Defaults 60 (from 1 to 1440), 10 (1 to 100), 10 (1 to 100), 1 (0 to 100), 3 (0 to 100), 7 (0 to 365) and 30 (1 to 1440). How often and how much a pet’s loyalty falls, what a piece of food gives back and what an obeyed order gives whenever the pet obeys, and what a refusal costs, the days of feeding that bond a pet, and the minutes of a period paid to a hireling: see animal taming and bonding.

The gameplay settings live under [ultima] as sub-tables ([ultima.world], [ultima.characters], …). The starting gold is not a setting: it is an item of the common set in starting_items.toml.

Redis is required at runtime in all three modes, including standalone. redis.connection_string is a StackExchange.Redis configuration string or an environment reference resolved at startup; the Docker example uses redis:6379,password=... on its private bridge. redis.handoff_secret is an independent cluster-wide secret, also supplied through an environment reference. Give the login and every game process the same values. The Docker example reads both from separate Compose secrets; keep the actual values out of TOML and the repository. A Redis connection failure prevents startup. A later Redis outage stops new realm lists and handoffs while existing game sessions continue; pending tickets are lost on Redis restart and game processes republish their leases. Configure Redis with maxmemory-policy noeviction.

Game-loop queue limits, timer-wheel resolution and packet dispatch limits use C# option objects rather than additional TOML sections. See Game loop and timers, Packets and the Docker topology.

Some sections of config/moongate.toml belong to plugins rather than to the server: [ultima] (with its sub-tables) to the Ultima plugin, [admin_api] to the Administration plugin, and any section a disk plugin adds. They are edited like any other section; what differs is who reads them and when.

  1. The server reads the file and keeps its own sections, ignoring the rest.
  2. Each plugin, while it registers, reads its section and checks its values. A value that is not allowed stops the start with an error that names the setting, reported as the cause of Plugin '<id>' failed during registration.
  3. A plugin whose section is missing uses its defaults and appends them to the end of the file. The lines already there, comments included, are not changed. Edit the appended values and restart to change them. If the file is read-only, the server logs a warning and runs with the defaults.

So a new root starts with the server’s sections only, and after the first start the file lists every setting of every installed plugin. Removing a plugin leaves its section in the file; the server ignores it. Two owners cannot share a name: a plugin that claims network, or a name another plugin already took, stops the start.

Inspect the installed executable with --help. From a checkout:

Terminal window
dotnet run --project src/Moongate.Server -c Release -- --help
dotnet 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; with neither set the server refuses to start
--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 mgctl migrate apply
--migration-target <target> Unset Required by generate: auth or world
--migration-output <path> Unset Required by generate: new NNNN_description.sql file; refuses overwrite
--initialize-root false Prepare config/directories/bundled migrations offline, without starting the server
--generate-admin-certificate false With --initialize-root, create or reuse the administration TLS identity and enable [admin_api]
--admin-certificate-hosts <names> Unset Comma-separated DNS/IP SANs in addition to localhost; requires certificate generation
--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. With neither set the root would be the directory holding the binary, and the server refuses to start with exit code 2, naming --root-directory. The same refusal applies when either resolves to that directory, because an upgrade replaces it wholesale and would delete config/, logs/, save/ and world-saves/ with it. 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 mgctl migrate status|apply --target auth|world, a process of its own. In released artifacts its default root is its own directory, the server’s; --root-directory and MOONGATE_ROOT override it. See Generate, review and apply.

[admin_api] configures the embedded gRPC plugin. It is disabled by default and uses server TLS on port 2590 when enabled. Certificate paths resolve relative to the root; password environment references are resolved only for enabled endpoints. Use mgctl certificate setup to generate a passwordless PFX and enable the endpoint offline. See Administration API for roles, permissions, first-admin provisioning and all limits.

MoongateServerConfig.Network.Encryption configures both UO listeners. Mode is the NetworkEncryptionMode enum: Disabled (default), Optional, or Required.

Mode Connections accepted
Disabled Existing plaintext protocol; no encryption middleware is installed.
Optional Valid plaintext login packets, or login packets that decrypt correctly with the configured profile.
Required Rejects plaintext login packets; requires a valid login packet after decryption with the configured profile.

Enabled modes require an explicit POL client version and select one encrypted profile per configuration; they do not try multiple versions. Login keys are derived from that version, while game encryption is selected as OldBlowfish, Blowfish12536, Blowfish, BlowfishTwofish, or Twofish. Versions in the same modern Twofish family share the game cipher, so this setting is not an exact client-version check on the game listener. The special version 2.0.0x is supported. Empty or malformed versions and the no-crypt aliases none, ignition and uorice prevent startup in Optional and Required modes.

Optional first checks the initial login packet with the normal packet parser. If it is not valid plaintext, the server attempts decryption and validates the result; failed validation rejects the connection. It does not fall back to plaintext after failed decryption. For a legacy four-byte login seed, enabled encryption supplies the configured version to the login handler because the client has not sent a version with that seed.

For Enhanced Client 67.0.117.0, use:

[network.encryption]
mode = "Optional"
client_version = "67.0.117.0"

Use the wire version, including the Enhanced Client’s 60 major-version offset. The inspected Enhanced Client executable with file version 4.0.117.0 uses the keys for wire version 67.0.117.0; using 4.0.117.0 or 7.0.117.0 produces different login keys. Keep the original client encryption enabled when testing this configuration. A launcher that removes encryption needs Optional or Disabled. Changing the setting requires a restart; startup reports, for example:

Client encryption: Optional; client 67.0.117.0; login XOR; game Twofish / MD5-XOR

For separate login and game processes, configure the same policy and profile on each and restart both after changes. See Network for handshake and transport details. The feature provides POL protocol interoperability, not TLS or authenticated transport. It does not implement the old Kingdom Reborn AES/E3 negotiation. Automated tests compare against original POL C++ vectors and exercise real TCP framing and compression. An interactive login was made with Enhanced Client 4.0.117 (67.0.117.0); another build still requires a client-side test. See Enhanced Client for what works after the login.

Made with ❤️ by Tom

Buy me a coffee