Skip to content

LXD Web UI

LXD 6 ships a Web UI served by the daemon itself. Muppy can open it for you in one click, from the Host form of an LXD host: it makes the daemon listen on the host's Control IP, obtains a fresh access link from LXD, opens the port to your IP when the network calls for it, and hands the link to your browser.

Requires LXD 6.7 or later

The access link mechanism (the "Initial UI identity") shipped in LXD 6.7; it does not exist on LXD 6.5, nor on LXD 5.21 LTS. On such a host the button refuses with an explicit message. Upgrade on the host with:

sudo snap refresh lxd --channel=6/stable

Who can use it

The button and the LXD Web UI group of fields are reserved to members of the LXD Administrator group (muppy_lxd.group_lxd_admin).

  • Muppy Administrators hold it implicitly — the group is implied by Muppy Administrators, so nothing has to be granted to them.
  • A Muppy User does not hold it. To grant it: Settings → Users & Companies → Users, open the user's form, and on the Muppy field of the Access Rights tab select LXD Administrator (the Muppy levels are a single ladder: Muppy UserLXD AdministratorMuppy Administrators, so choosing LXD Administrator keeps every Muppy User right).

What the group actually grants is worth weighing before handing it out: the access link logs in as ui-admin-initial, a server administrator of that LXD daemon — every project, every instance, every configuration key of the host, including the containers of other tenants. Opening the UI also reconfigures the daemon (core.https_address) and may open 8443 on the host's firewall.

For a non-member, the button is hidden and the LXD Web UI fields do not appear. That is defense in depth only: the refusal lives in the model, so calling open_lxd_ui or lxd_ui_setup over RPC gets an AccessError and nothing runs on the host.

Opening the UI

On the Host form of an LXD host (not of an LXC guest), click LXD GUI in the button box at the top of the form. Muppy then, in one SSH session:

  1. checks the LXD version;
  2. makes the daemon listen on 8443 on the host's Control IP if it does not already, and records the result in LXD HTTPS Address on the LXD tab;
  3. asks LXD for a Web UI access link;
  4. opens 8443/tcp to your IP only — but only when the network calls for it, see the next section;
  5. opens the UI in a new browser tab, already logged in.

The LXD tab also carries a Setup LXD UI access button, which performs step 2 alone (useful to prepare a host, or to re-read the daemon's address). It opens no port — but it applies the same rules as the button above: LXD Administrators only, and it refuses on a public Control IP whose firewall is inactive. Putting the daemon on a public address with nothing filtering it is the exposure; not opening a port does not undo it.

The browser will warn about the certificate

LXD serves the UI with its own self-signed certificate. Your browser will show a security warning the first time you reach a given host; accept it once per host. Publishing the UI behind a trusted certificate is a separate, later feature.

Where LXD listens, and who can reach it

The host's Control IP decides everything: it is the address the daemon is made to listen on (core.https_address), and it is what the URL points at. Not the DNS name, not the Public IP field. The Control IP is the address Muppy demonstrably reaches the host on — on the production fleet it is always either a public address or a Tailscale one, whereas the Public IP field carries LAN or Tailscale values on some hosts.

A host with no Control IP has no LXD Web UI: the button refuses, and the LXD Web UI field stays empty.

From the class of that Control IP, and from whether the host's firewall is active, Muppy decides — before opening any SSH session — what the click does about the firewall:

Control IP Firewall inactive Firewall active
Tailscale (100.64.0.0/10) nothing is opened nothing is opened
Private / LAN (RFC1918, ULA, loopback) nothing is opened rule for your IP (/32)
Public the button refuses rule for your IP (/32)
  • On a Tailscale Control IP the tailnet is the perimeter. Nothing is opened, ever — and nothing could usefully be: the IP Muppy sees you coming from is your Internet egress, which is not the source address the host sees over the tailnet. A rule built from it would be inert.
  • On a private Control IP with an inactive firewall there is nothing to open: the address is not routable from the Internet and UFW is not filtering anything.
  • On a public Control IP with an inactive firewall the button refuses:

    Cannot open the LXD Web UI on host <name>: its Control IP <ip> is public and the firewall is inactive. Activate the firewall (Firewall tab) first.

    Exposing the LXD admin API and its UI to the whole Internet is not something Muppy will do quietly. Activate the host's firewall, then click again.

When the decision is "nothing is opened", Muppy writes nothing at all: no firewall rule, and no pending changes flag on the host.

The link Muppy hands your browser is produced by LXD's lxd init --ui-initial-access-link, and it has properties worth knowing:

  • It logs you in as ui-admin-initial, an LXD identity with server admin permissions. It is shared: every Muppy user who clicks the button on that host lands in the UI as the same LXD identity. Attribution is Muppy-side — each click writes a line in the Muppy server log: LXD Web UI opened on host '<host>' by '<your login>'.
  • It is valid 24 hours. LXD fixes that duration and refuses to change it.
  • Each click issues a new link, and the previous one stops working.
  • The token travels in the URL (https://<control-ip>:8443/?token=…). It therefore lands in your browser history and in anything that records URLs. Do not share the URL, and do not paste it into a ticket or a chat.

Muppy never stores the link: it is returned to the browser inside the request that created it, and nothing writes it to the database or to a log.

Revoking access

Either issue a new link (which invalidates the previous one) or, to revoke every outstanding link at once, delete the identity on the host:

sudo lxc auth identity delete bearer/ui-admin-initial

You can also close the door at the network level by removing the ephemeral firewall rule described below, when there is one.

The ephemeral firewall rule

Muppy never opens 8443 globally. When the table above says a rule is needed, Muppy first checks whether your IP can already reach the port through an existing, active rule (Open LXD or Open all ports/protocols). If it cannot, it adds a rule for your address alone:

Field Value
Rule Open LXD (8443/tcp)
Source your IP as a /32 (a /128 in IPv6)
Destination any
Expiration the access link's expiry (24 hours)
Comment LXD Web UI (<your login>, ephemeral)

The rule is visible on the Host's Firewall Rules tab. Muppy's hourly expired-rules cron removes it once it lapses. Clicking again before then re-uses the same rule and extends its expiration.

Your IP is read from the request that opened the tab (CF-Connecting-IP, else the first element of X-Forwarded-For, else REMOTE_ADDR). If Muppy cannot determine it — for instance when the method is called outside an HTTP request — the firewall step is skipped with a warning in the log, and the UI will simply not answer if 8443 is closed.

Fields on the LXD tab

Field Meaning
LXD Web UI Plain URL of the UI, built from the host's Control IP (https://<control-ip>:8443/ui). Opening it directly shows LXD's login page — use the button to get a link that logs you in.
LXD HTTPS Address Mirror of the daemon's core.https_address as last set by Muppy: where LXD listens. Muppy always binds it to the host's Control IP; only its port is used to build the URL above.

Troubleshooting

"The LXD Web UI needs LXD >= 6.7" — the host runs an older LXD. Upgrade it, then collect the host's facts again so Muppy sees the new version.

"Cannot open the LXD Web UI … its Control IP is public and the firewall is inactive" — activate the host's firewall from the Firewall tab, then click again. Muppy will then open 8443 to your IP alone. (The other way out — putting the host on the tailnet and using its Tailscale address as Control IP — also removes the need for any rule.)

"The LXD Web UI is reserved to members of the 'LXD Administrator' group" — your account is not in that group. See Who can use it: a Muppy administrator grants it from the user's form.

"Host <name> has no Control IP" — Muppy binds LXD to the Control IP and builds the URL from it. Fill the host's Control IP.

"LXD on host … refused to listen on <address>" — LXD can only bind an address that exists on the machine, and Muppy only ever tries the Control IP: there is no fallback on another address, by design. Check that the Control IP recorded in Muppy is really configured on one of the host's interfaces.

The browser lands on LXD's login page instead of the instance list — the link has expired (24 hours), or a newer link was issued in the meantime. Click LXD GUI again.

The tab hangs or times out — you cannot reach the Control IP on 8443. If the host is on the tailnet, check that your machine is connected to it. Otherwise check the Firewall Rules tab: if you clicked from a different network than the rule's source, click the button again from the current one, and check that the host's firewall is active and has no pending changes.