m-uising-box panel

Deep dive · 2026-09-05

How m-ui hot-reloads users without restarting every connection

Adding a user should not disconnect the other two hundred. m-ui gets there with three reload tiers, chosen by what actually changed, and a couple of details about how sing-box identifies users.

Three tiers, smallest first

What changedWhat happensWho notices
User: added, removed, enabled, disabled, credentials, line assignmentReloadUsers — rebuild each inbound's user list from the database and swap it into the running inbound; re-apply per-user speed and device limitsOnly the user whose credentials went away (they are disconnected on purpose)
Upstream: added, edited, removedReloadUpstreams — render the outbound set, add new outbounds, replace changed ones, remove stale ones; routing rules reference outbounds by name, so they stay as they are. Renaming an upstream changes what the rules reference, and that case is promoted to ReloadAll automaticallyConnections through a replaced outbound reconnect; everything else stays
Certificate: renewed or overwritten in placeNo reload at all — sing-box watches the certificate files and swaps them itself; only a changed certificate path triggers ReloadAllNobody
Line: protocol, port, TLS, transport, options, deploymentReloadAll — render the whole config, stop the box, start the new one; if it fails to start, start the previous config againAll connections on that server reconnect (a few seconds)

The panel picks the tier from the change itself, so an operator never has to decide "is this safe to apply now?". Nodes apply pushed snapshots with the same logic: a snapshot that only differs in users triggers only tier one on every node.

How sing-box identifies a user

Inside a sing-box inbound, users are a list of credentials. The name attached to each entry is a label used for logging and traffic attribution; the credential is what actually matches a connection. Two consequences shape m-ui's design:

What "swap the user table" costs

Rebuilding a user list is a database read plus a call into the inbound's user manager. It runs in milliseconds for hundreds of users, and it runs inside the panel process, so there is no config file, no signal and no race with a supervisor restarting the core.

Why upstream changes do not restart either

sing-box's outbound manager can add and remove outbounds at run time. m-ui diffs the rendered outbound set against what is running (by tag and rendered options), applies only the difference, and updates the routing rules that map lines to exits. Switching a line from direct to an upstream, or rotating an upstream's credentials, therefore never touches the inbounds.

When a restart is unavoidable

Listeners cannot change protocol or port in place, so a line edit restarts the data plane. Two guards keep that from being risky:

  1. The new configuration was already parsed by sing-box inside the save transaction; if it did not parse, the save failed and nothing restarted.
  2. If it parses but does not start — typically a port that another process took in the meantime — the previous configuration is started again and the panel shows the error. The server is never left without a data plane.

Port conflicts with the panel, other lines and other processes are also rejected at save time, which is why the restart path rarely has to fall back at all.

Related: why the core is embedded · how nodes receive the same changes · runner/ on GitHub