The web UI cannot reach the server

Symptom

The surface loads, draws a full console, and the connection indicator in the header reads Demo (offline) — or Disconnected. Faders move on screen and nothing happens to the audio.

Demo (offline) is a deliberate feature: with no server reachable, the surface falls back to a simulated board so the controls can be learned. It is not an error message, which is why it is easy to miss.

The default arrangement

browser ──▶ nginx :8443 (TLS, HTTP/2, the console's own local CA) ──▶ openmixer-server 127.0.0.1:8800
                │
                └── nginx :8880 (plain) — no API here, only a 302 to :8443 and the trust page

nginx terminates TLS and HTTP/2 with a certificate the console issues itself; the upstream to the server stays plain HTTP/1.1 on loopback (nginx does not proxy h2 upstream — the multiplex is a browser↔nginx fact). nginx proxies /api/* (the REST entity API, including its ?watch=1 SSE streams), /manual, /health, /telemetry and /patchbay/* from the :8443 origin to the server. There is no WebSocket and no /ws — the surface's live intake is /api SSE, over the same HTTPS connection as everything else. There is also no separate /config discovery endpoint — the console's own port is /api/net/binding, an entity like any other.

The :8880 listener is not a second copy of the API: it answers only the redirect to https://<host>:8443/ and the trust page that serves the root certificate to a browser that does not trust it yet — a browser needs a plain channel to fetch the thing that would give it trust in the first place. A 302 from :8880 is healthy — treat it as one, not as a failure.

Check in this order

1. Is the server running?

systemctl --user status openmixer-server
curl -s http://127.0.0.1:8800/health

Nothing on the loopback engine port means this page is done and the problem is the service. Check the journal. (The port here is whatever web.port resolves to for this console — see step 3; 8800 is this console's current value.)

2. Is nginx running and serving the HTTPS origin?

sudo systemctl status nginx
sudo nginx -t
curl -sk -o /dev/null -w '%{http_code}\n' https://127.0.0.1:8443/health   # through the proxy, expect 200
curl -sI http://127.0.0.1:8880/                                          # expect 302 to :8443 — this is healthy

If the loopback engine answers /health but https://127.0.0.1:8443/health does not, the drop-in is the problem — either nginx did not reload after an upgrade replaced it, the certificate is missing (nginx refuses to start the TLS listener with no leaf/key), or the upstream no longer matches the server's port.

Do not conclude the drop-in is broken from a 302 on :8880 — that plain listener never serves the API; it only redirects and serves the trust page.

grep -n listen /etc/nginx/conf.d/openmixer-web-ui.conf   # the two plain-listener paths
cat /etc/openmixer/nginx/upstream.conf                   # the port nginx proxies to
sudo openmixer-apply-port

The upstream file is generated, not edited: %post writes it at install; after that, the ONE step is sudo openmixer-apply-port — it regenerates the upstream from web.port in /etc/openmixer/config.json (or the persisted Setup value, see step 3), moves the SELinux port label, and reloads nginx. No unit does any part of this: openmixer-server.service is a --user unit and cannot write /etc/openmixer or call semanage, so systemctl reload nginx alone reloads the OLD upstream. If the file names the wrong port after running it, the config file is where to fix it.

3. Did someone move the server's port?

The server's port is runtime configuration. The layers a shell script can see, and the order openmixer-apply-port walks (via openmixer-config-port.sh):

port of <state-dir>/network-settings.json  >  OPENMIXER_WEB_PORT  >  web.port of config.json  >  built-in default

A value persisted through Setup → Network — a flat {host, port} document at <state-dir>/network-settings.json — outranks everything else, which is the classic way for a rig to end up with a server on a port the nginx upstream does not know about.

curl -sk https://127.0.0.1:8443/api/net/binding # what the server thinks its port is, through the proxy
cat /etc/openmixer/config.json                  # the baseline layer, "web": {"port": ...}
cat ~/.local/state/openmixer/network-settings.json 2>/dev/null   # the persisted Setup layer, if any

If the persisted port is wrong, fix it from Setup → Network — or stop the server, delete that file, and start again to fall back to the config-file/default layers.

Whatever the server's port ends up as, sudo openmixer-apply-port derives the nginx upstream from the same layers the server itself resolves, persisted Setup value included — it reads <state-dir>/network-settings.json in the operator's own session, the same file Setup → Network writes. Run without that session (a root shell with a different $HOME), and with neither --state-dir nor OPENMIXER_STATE_DIR, it prints a warning and falls through to config.json, which is the deployment's own baseline for exactly that caller — pass --state-dir <console user's XDG state dir> to honour a port set from Setup → Network. Under SELinux there is a second half to it: the new port needs http_port_t, because the console's httpd_can_network_relay grant only reaches labelled ports — openmixer-apply-port labels the resolved port and drops whichever port it labelled last, so a port moved more than once does not leave stale labels behind.

4. Firewall

sudo firewall-cmd --list-ports

8880 and 8443 should both be open — the package opens them at install. The loopback engine port (8800 above, 8080 by default) does not need to be, and should not be: nothing proxies to it from outside the host, and it carries no TLS.

5. Certificate trust

If nginx serves /health locally (step 2) but a browser on another device shows a certificate warning or refuses to connect, the device has not installed the console's root certificate. Fetch it from the plain listener and install it per platform (Firefox and iOS need a second step beyond "import" — see the console's own trust page):

curl -s http://<rig>:8880/trust/root.crt -o openmixer-root.crt

6. Is it the right host?

Opening the surface by IP from a tablet works; opening it from a machine that cannot route to the rig does not. Check that the browser and the rig are on the same network and that you are using the rig's address, not localhost.

A red lamp during a restart is normal — up to 30 seconds

After a console restart (a deploy, an upgrade) the connection indicator goes red for up to 30 seconds while it retries — nginx briefly has nowhere to proxy to while the server process comes back up — and then clears on its own, with no reload: the surface keeps trying to reconnect on its own, waiting a little longer between attempts each time rather than giving up. If it does not come back on its own within a minute or so, reload the page and work through the steps above.

Quick end-to-end test

From the machine running the browser:

curl -sk https://<rig>:8443/health
curl -sk https://<rig>:8443/api/net/binding

Both answering 200 means the surface should connect. If it still does not, reload the page — a stale tab holds a stale connection — and check the browser console.