Configuration
The server reads one TOML file, given with --config. The file name doesn't matter.
The smallest valid configuration is one line:
toml
rooms = "managed"Everything else is optional. The three setup guides each start from a complete example: Coordinator only, Behind a reverse proxy and Self-contained.
How the file is read
- Strictly. A misspelled or unknown setting stops the server at startup, with its line and column, rather than being ignored.
- At startup. Changes apply when the server restarts. A reload applies
name,max_connected_devicesand the[access]rules without one, and certificate files are reloaded as they change. - Top-level settings first. In TOML, settings that belong to no section, such as
nameandrooms, have to come before the first[section].
Where files go
| Setting | Default | Meaning |
|---|---|---|
data_dir | "data" | The directory for everything the server creates and keeps. A relative path is relative to the configuration file. |
identity | "coordinator.key" | The server's private identity key. |
state | "state.redb" | Members, roles, invitations and managed rooms. |
identity and state, like every other path the server creates, are relative to data_dir unless they're absolute. The only paths relative to the configuration file instead are data_dir itself and certificate files. See The data directory for what to keep and back up.
Top-level settings
| Setting | Default | Meaning |
|---|---|---|
name | none | The community's name, shown in the app. 1 to 64 characters, without leading or trailing spaces. |
rooms | required | "managed", or a list of rooms. See Rooms. |
max_connected_devices | unlimited | The most devices connected at once. Further devices are told the server is full. |
Rooms
Choose one of two kinds of room list.
Managed rooms are created, renamed and deleted while the server runs, with moin-server admin rooms or by administrators in the app. They're stored in the data directory and survive restarts. A new managed list starts empty.
toml
rooms = "managed"Configured rooms are listed in the file and can't be changed while the server runs:
toml
[[rooms]]
room_id = "00000000000000000000000001"
name = "Lobby"
max_members = 32
[[rooms]]
room_id = "00000000000000000000000002"
name = "Games"| Setting | Meaning |
|---|---|
room_id | The room's permanent ID. Keep it when you rename the room. |
name | 1 to 64 characters. |
max_members | Optional. When the room has this many people, new arrivals are turned away. |
A room ID is 26 characters long, using the digits and lowercase letters 0123456789abcdefghjkmnpqrstvwxyz (no i, l, o or u). The first character must be 0 to 7. Counting up from 00000000000000000000000001 is fine. Each ID can appear once.
List at least one room. You can switch between the two kinds. While rooms are configured, a managed list from earlier is kept, untouched, and comes back when you switch to managed again.
[access]
Who may join. See Members and access for how this works in use.
| Setting | Default | Meaning |
|---|---|---|
mode | "open" | "open" lets anyone join. "protected" lets in members only. |
password_env | none | The environment variable holding the password that makes a device a member. |
password | none | The password itself, if you'd rather keep it in the file. |
invites_enabled | false | Whether invitation links make devices members. |
bootstrap_secret_env | none | The environment variable holding a one-time secret for claiming the first administrator role. |
bootstrap_secret | none | The secret itself. |
Set password or password_env, not both, and likewise for the bootstrap secret. Each is 1 to 1024 bytes. If a named environment variable isn't set, the server refuses to start.
[management]
| Setting | Default | Meaning |
|---|---|---|
socket | "run/admin.sock" | The socket moin-server admin connects to. Relative to data_dir. |
Only available on Linux and macOS.
[log]
| Setting | Default | Meaning |
|---|---|---|
level | "info" | "error", "warn", "info", "debug" or "trace". |
format | "text" | "text", or "json" for one JSON object per line. |
See Logs for what each level includes and how to filter further.
[server]
Give the server a public hostname. Without this section the server is a coordinator only: people join by its ID, and every setting below is ignored.
| Setting | Default | Meaning |
|---|---|---|
origin | required | The address people type, such as "https://chat.example.com". Always https://, with a port only if HTTPS isn't on 443. No path. |
http_bind | — | Serve plain HTTP on this address, for a reverse proxy in front. |
https_bind | — | Serve HTTPS on this address, with the server's own certificate. |
max_connections | 1024 | The most open connections to the HTTP(S) listener, 1 to 65536. See Limits. |
Set exactly one of http_bind and https_bind, as an address and port such as "0.0.0.0:443" or "127.0.0.1:8080".
The hostname in origin has to resolve to addresses where the coordinator's UDP port is reachable: the app connects to those addresses directly.
[server.coordinator]
Required with [server]. The UDP port members' connections arrive on.
| Setting | Default | Meaning |
|---|---|---|
bind | required | The local address and port, such as "0.0.0.0:443". |
public_port | required | The port reachable from outside. The same as bind's port unless your router forwards a different one. |
public_addresses | — | The IP addresses where public_port is reachable, such as ["203.0.113.7", "2001:db8::1"]: up to 32, without ports. |
The server tells devices which addresses it's reachable at. Without public_addresses it looks up origin's hostname for them when it starts and then every minute; if the name doesn't resolve yet, the server starts anyway and keeps trying. Set public_addresses when you know the addresses, for example when the DNS record is created just before the server starts. Private addresses are fine for a server on your own network.
Certificates
https_bind needs a certificate. With http_bind, a certificate is only needed for address probing, and has to be a manual one. Choose one of these sections.
[server.certificates.acme] gets and renews a certificate automatically, proving control of the hostname over TCP 443. Port 443 must reach this server directly, not a proxy that ends TLS, and origin must be a hostname rather than an IP address.
| Setting | Default | Meaning |
|---|---|---|
contact | required | A list of "mailto:" addresses for the certificate authority. |
directory | required | The certificate authority. Let's Encrypt is "https://acme-v02.api.letsencrypt.org/directory", or "https://acme-staging-v02.api.letsencrypt.org/directory" for testing. |
cache | "acme" | Where the certificate and account key are kept. Relative to data_dir. |
[server.certificates.manual] uses certificate files you provide.
| Setting | Default | Meaning |
|---|---|---|
cert | required | The full certificate chain, in PEM format. Relative to the configuration file. |
key | required | Its private key, in PEM format. Relative to the configuration file. |
reload_seconds | 60 | How often to check the files for a new certificate, 1 to 86400. |
A renewed certificate is picked up without a restart. If the new files are invalid or don't match, the server keeps the previous certificate, logs an error, and checks again later.
[server.relay]
Run a relay on the HTTP(S) listener. It's advertised to members automatically. Remove the section to turn the relay off.
| Setting | Meaning |
|---|---|
max_connections | The most devices connected to the relay at once, 1 to 65536. |
bytes_per_second | Each relay connection's bandwidth, at least 10. |
burst_bytes | How far a connection can briefly exceed that, at least bytes_per_second. |
All three are required. 1024, 1048576 (1 MiB/s) and 2097152 (2 MiB) are reasonable starting points. Behind a reverse proxy, the proxy has to allow WebSocket upgrades and long-lived connections.
[server.relay.qad]
Address probing: a separate UDP service that tells devices their public address, helping them connect to each other directly. Optional; the relay works without it. It needs a certificate.
| Setting | Meaning |
|---|---|
bind | The local UDP address and port, such as "0.0.0.0:7842". |
public_port | The port reachable from outside. |
Both have to differ from [server.coordinator]'s. Address probing has no connection limit of its own; see Self-contained.
[server.pkarr]
Run a discovery service, where members' devices publish their current addresses so others can find them. It's served at /pkarr on the HTTP(S) listener and advertised to members automatically. Remove the section to turn it off.
| Setting | Default | Meaning |
|---|---|---|
capacity | required | The most device records kept, 1 to 1,000,000. |
retention_seconds | required | How long a record is kept after its last update, 600 to 31,536,000 (a year). |
requests_per_second | required | Requests answered per second across all clients, 1 to 100,000. |
max_in_flight | required | Requests handled at the same time, 1 to 1024. |
store | "pkarr.redb" | Where records are kept. Relative to data_dir. |
10000, 604800 (a week), 100 and 16 suit most communities. Old records make room for new ones only once they've expired; a record that's still being refreshed is never pushed out. Records hold public addresses, not secrets.
[server.relays] and [server.discovery]
Which relay and discovery servers members' devices use, besides this server's own.
| Setting | Default | Meaning |
|---|---|---|
include_defaults | true | Include the public relay or discovery servers Moin uses by default, run by n0, the makers of iroh. |
servers | [] | Further servers to use. |
The defaults are on even when these sections are left out. Set include_defaults = false in both to stop depending on the public servers.
Relay entries take a url and an optional quic_port, that relay's address probing port. Discovery entries take a url:
toml
[server.relays]
servers = [{ url = "https://relay.example.net", quic_port = 7842 }]
[server.discovery]
servers = [{ url = "https://discovery.example.net/pkarr" }]Relay URLs are https:// addresses without a path. A relay list is a set of choices for each device, not an order of preference.
No relays at all
With include_defaults = false, no servers and no [server.relay], devices can only connect directly. People behind firewalls that block UDP can't join, and members may fail to reach each other in calls.
Limits
The settings above that protect the server under load, and the fixed limits behind them.
Connected devices. max_connected_devices counts devices connected to the community, in every setup. A device over the limit is told the server is full.
Web connections. [server]'s max_connections counts connections to the HTTP(S) listener: bootstrap requests, discovery, relay connections, and TLS handshakes in progress. It doesn't count members' connections to the community itself. Relay connections can take at most seven eighths of it, so bootstrap and discovery always have room; further relay connections are turned away with 503.
Relay. Besides its own max_connections, the relay caps each connection's bandwidth at bytes_per_second, with bursts up to burst_bytes.
Discovery. When capacity is reached with no expired records to replace, new devices get 507. Over requests_per_second they get 429, and over max_in_flight, 503.
Fixed limits.
- Web requests that aren't relay connections get 30 seconds, and headers up to 16 KiB.
- A discovery record is at most 1072 bytes and has 10 seconds to arrive.
- The bootstrap document, including every relay and discovery server listed, has to fit in 64 KiB. The server checks this at startup.
