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:

KeyDefaultMeaning
portal.enabledfalseServes 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-minutes60How long a session lasts without being used, from 1 to 10080
portal.link-minutes5How long a /portal link may wait before it is used, from 1 to 60
portal.allow-insecurefalseServes 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 /portal in game. It answers with a clickable link that works once, within portal.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-secret in server.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:

  1. 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). Bind management-server-host to an address others can reach, open the port, and set portal.url: https://your.domain:25576/portal (with your own domain and port) so /portal links point there.
  2. 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 set portal.url to the public address. The portal accepts that address as the page's origin. Set portal.proxy-header to the header the proxy puts the visitor's address in: CF-Connecting-IP for a Cloudflare Tunnel, X-Real-IP or X-Forwarded-For for nginx or Caddy (of X-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:

NodeDefault levelCovers
portal.use3Signing in at all
portal.status3The status screen
portal.maintenance3Opening and closing the server for maintenance, and its texts
portal.players3The player list and kicking
portal.console4The console, its log and running commands
portal.operators.view / portal.operators3 / 4Seeing / changing operators
portal.allowlist3The allowlist
portal.bans3Bans and IP bans
portal.gamerules3Game rules
portal.settings3Server settings, except the operator permission level
portal.server3 for the server's status and system messages, 4 for everything elseThe server itself: stop, save, autosave, the operator permission level
portal.plugins3The plugin list, and which plugin pages exist
portal.plugin.<plugin>.settings3Reading and changing one plugin's settings
portal.page.<plugin>.<page>3One 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 in management-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.log beside ops.json, one JSON line each: when (milliseconds since 1970), who (player:<name>, owner, or plugin:<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: nosniff and Referrer-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.

MethodWhat
rustmc:whoamiWho is signed in, and which screens they may see
rustmc:statusTick rate, MSPT, players, worlds and caches
rustmc:maintenanceMaintenance mode's state and texts
rustmc:maintenance/setChanges message, motd and enabled
rustmc:pluginsEvery plugin: name, version, origin, whether it is running, how many settings it has
rustmc:plugin/settingsOne plugin's settings, as a form
rustmc:plugin/settings/setChanges one plugin setting
rustmc:plugin/pagesThe plugin pages the caller may open
rustmc:plugin/pageOne plugin page
rustmc:plugin/actionRuns an action on a plugin page
rustmc:console/logThe last lines lines of the server's console (200 unless asked)
rustmc:console/runRuns 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.