Running a server
The web portal
On this page
A page for running the server from a browser: its status, maintenance mode, the players online, the console, operators, the allowlist, bans, game rules, server settings, and plugins with their settings and their own pages. It is served by the management port (the game's JSON-RPC management protocol) and speaks that protocol itself, so everything the protocol can do, the page can do.
The portal is off by default. While it is off the management port behaves exactly as the game's does,
and /portal does not exist.
Turning it on
The portal runs on the management server the game already has, so that has to be on in
server.properties. The management server uses TLS by default, which needs a PKCS#12 keystore:
management-server-enabled=true
management-server-host=localhost
management-server-port=25576
management-server-tls-enabled=true
management-server-tls-keystore=portal.p12
management-server-tls-keystore-password=...
The port here is only an example: the default, 0, picks a free port at each start, and 25575 is the
RCON port's default, so choose one that is free. The keystore password can also come from the
MINECRAFT_MANAGEMENT_TLS_KEYSTORE_PASSWORD environment variable, which wins over the property. With
TLS on and no keystore, the management server does not start.
For a portal used only from the server's own machine, TLS can be turned off instead:
management-server-enabled=true
management-server-host=localhost
management-server-tls-enabled=false
Then turn the portal on in rustmc.yml:
portal:
enabled: true
Start the server. It prints web portal at followed by the page's address, for example
https://localhost:25576/portal/.
The page is built into the server, so there is nothing else to install.
Settings
Under portal: in rustmc.yml:
| Key | Default | Meaning |
|---|---|---|
portal.enabled | false | Serves the portal on the management port. management-server-enabled must be true too |
portal.url | "" | The page's public address when it is reached through a TLS tunnel or proxy, as https://host/portal. It is what /portal links open, and the browser origin the portal accepts. Empty means the management port on localhost |
portal.session-minutes | 60 | How long a session lasts without being used, from 1 to 10080 |
portal.link-minutes | 5 | How long a /portal link may wait before it is used, from 1 to 60 |
portal.allow-insecure | false | Serves the portal without TLS on an address other machines can reach |
portal.proxy-header | "" | Behind a tunnel or proxy on this machine, the header it puts the visitor's address in. Empty believes no header |
Signing in
- Operators of level 3 or more type
/portalin game. It answers with a clickable link that works once, withinportal.link-minutes. Opening it signs the browser in. A player who may not use the portal is told so and gets no link. - The owner can sign in on the page with the management secret (
management-server-secretinserver.properties). The page swaps the secret for a session and does not keep it.
Who may sign in is checked when the link is made, when it is used, and again on every message the
page sends. A session ends after portal.session-minutes without use, when you sign out, or as soon
as its player no longer may use the portal (for example, on being deopped). An ended session closes
any connection it had open.
The link's code travels after #, so it never appears in a request line, a server log or a
Referer, and the page takes it off the address bar as soon as it is used. The session is kept in
the browser tab's session storage, so closing the tab forgets it.
Reaching it from outside
Serve it over TLS, one of two ways:
- The server's own TLS. Use a PKCS#12 keystore with a certificate for your domain (for example
from Let's Encrypt, converted with
openssl pkcs12 -export). Bindmanagement-server-hostto an address others can reach, open the port, and setportal.url: https://your.domain:25576/portal(with your own domain and port) so/portallinks point there. - A TLS tunnel or proxy (Cloudflare Tunnel, Caddy, nginx) in front of a localhost port. Keep
management-server-host=localhost, point the tunnel at it, and setportal.urlto the public address. The portal accepts that address as the page's origin. Setportal.proxy-headerto the header the proxy puts the visitor's address in:CF-Connecting-IPfor a Cloudflare Tunnel,X-Real-IPorX-Forwarded-Forfor nginx or Caddy (ofX-Forwarded-For, the last entry is used). Without it every visitor appears to come from localhost, so everybody shares one sign-in limit. Name only a header your proxy always sets: a visitor can send any header the proxy passes on. The header is believed only on connections from this machine.
If TLS is off and the management port binds an address other machines can reach, the portal is not
served and the console says why. The management port itself still starts as usual. Setting
portal.allow-insecure: true serves it anyway, with sign-in links and sessions crossing the network
in plain text.
Who may do what
Each method the portal calls needs a permission node, asked of your permissions plugin through the
permission.node hook. With no permissions plugin, the operator level decides, at the level of the
command that does the same in game:
| Node | Default level | Covers |
|---|---|---|
portal.use | 3 | Signing in at all |
portal.status | 3 | The status screen |
portal.maintenance | 3 | Opening and closing the server for maintenance, and its texts |
portal.players | 3 | The player list and kicking |
portal.console | 4 | The console, its log and running commands |
portal.operators.view / portal.operators | 3 / 4 | Seeing / changing operators |
portal.allowlist | 3 | The allowlist |
portal.bans | 3 | Bans and IP bans |
portal.gamerules | 3 | Game rules |
portal.settings | 3 | Server settings, except the operator permission level |
portal.server | 3 for the server's status and system messages, 4 for everything else | The server itself: stop, save, autosave, the operator permission level |
portal.plugins | 3 | The plugin list, and which plugin pages exist |
portal.plugin.<plugin>.settings | 3 | Reading and changing one plugin's settings |
portal.page.<plugin>.<page> | 3 | One plugin's page and its actions |
A management method not named in this table needs level 4, under portal.server, so anything the
protocol gains later is the owner's until the server places it.
With a permissions plugin, staff get exactly the screens their groups give them. A player who is not
an operator can sign in if they are granted the /portal command and portal.use. The page shows only
the screens and plugin pages a user may use, and the server refuses everything else. The owner,
signed in with the management secret, may do everything.
What it protects
- A browser may open the WebSocket with a portal session only from the page's own address or
portal.url. The management secret is accepted from browsers on the origins listed inmanagement-server-allowed-origins, as in the game. - Sign-in is limited to 10 attempts per address in 5 minutes. A refused attempt does not count, so waiting always works. At most 4096 addresses are tracked at once; past that, a new address is refused until older ones age out.
- Every change made through the portal is appended to
portal-audit.logbesideops.json, one JSON line each: when (milliseconds since 1970), who (player:<name>,owner, orplugin:<name>for a change a plugin made on someone's behalf), which method, and its parameters. Anything named like a password, token, secret or key is hidden, and so is the new value of a plugin setting marked secret. Sign-ins are logged with the address they came from. Reads are not logged. - Plugin pages are data the page draws, never markup or script. A page of more than 200 components or 256 KiB is refused.
- Pages are sent with
Content-Security-Policy(default-src 'self',frame-ancestors 'none'),X-Frame-Options: DENY,X-Content-Type-Options: nosniffandReferrer-Policy: no-referrer. Sign-in answers are never cached.
What it adds to the protocol
While the portal is on, management connections also get these methods. They cannot be sent inside a batch.
| Method | What |
|---|---|
rustmc:whoami | Who is signed in, and which screens they may see |
rustmc:status | Tick rate, MSPT, players, worlds and caches |
rustmc:maintenance | Maintenance mode's state and texts |
rustmc:maintenance/set | Changes message, motd and enabled |
rustmc:plugins | Every plugin: name, version, origin, whether it is running, how many settings it has |
rustmc:plugin/settings | One plugin's settings, as a form |
rustmc:plugin/settings/set | Changes one plugin setting |
rustmc:plugin/pages | The plugin pages the caller may open |
rustmc:plugin/page | One plugin page |
rustmc:plugin/action | Runs an action on a plugin page |
rustmc:console/log | The last lines lines of the server's console (200 unless asked) |
rustmc:console/run | Runs a console command and answers with its reply lines |
Commands run from the portal, and plugin settings changed there, are also printed on the console with who did it.
From docs/portal.md in the RustMC repository at bbd81209ad.