Production Hardening¶
In a nutshell
OVOS ships several network services, and most bind to 127.0.0.1 by default, but not all of
them do. This page lists every port, its default bind, and whether it has auth or TLS, plus
the rules to follow before you open any of them on a shared network.
Is this page for you?
Looking for systemd units, readiness probes, log locations, backups, or staged upgrades instead? See Production Operations. This page is the security-hardening slice of that material.
Network hardening¶
OVOS ships several network services. Most bind to 127.0.0.1 by default. Some do not. Check
each one before you open a port on a shared network.
| Service | Port | Default bind | Auth? | TLS? |
|---|---|---|---|---|
| Messagebus | 8181 | 127.0.0.1 |
None | Optional (websocket.ssl) |
| GUI WebSocket | 18181 | 127.0.0.1 (older configs may carry 0.0.0.0) |
None | None |
| Skill Settings web UI | 8000 | 0.0.0.0 (all interfaces) |
Basic auth, default ovos/ovos |
None (put a proxy in front for TLS) |
| STT server | 8080 | 0.0.0.0 |
None | None (put a proxy in front for TLS) |
| TTS server | 9666 | 0.0.0.0 |
None | None (put a proxy in front for TLS) |
| Translate server HTTP API | 9686 | 0.0.0.0 |
None | None (put a proxy in front for TLS) |
| Translate server MCP endpoint | 9687 | 127.0.0.1 |
None | None |
| HiveMind listener | 5678 | 0.0.0.0 |
Access key + password (Noise handshake) | Optional (ssl + cert_dir/cert_name in server config) |
The GUI WebSocket has no authentication, origin check, or TLS
Like the bus, the GUI WebSocket ships bound to 127.0.0.1 — but it has no
authentication, no origin check, and no TLS option at all, and anything it receives is
forwarded straight onto the core bus. Widen gui_websocket.host to 0.0.0.0 only if a
remote display genuinely needs network access, and re-check configs from before the
loopback default, which may still carry 0.0.0.0. See
GUI Service: Configuration for the full warning and the
VPN/reverse-proxy alternative.
Rules to follow¶
- Keep every service in the table on
127.0.0.1unless you have a specific, deliberate reason to open it. - Never expose the messagebus or the GUI WebSocket to the internet. Either one gives full control of the assistant, and the GUI socket gives it with no authentication at all (see Security & Trust Model: The bus has no authentication).
- If satellite devices need to reach the bus directly (uncommon; most setups should use
HiveMind instead, see below), bind it to
0.0.0.0only on a network you control, and firewall the port to that network's subnet. For example, withufw, allow only a trusted LAN:
Replace 192.168.1.0/24 with your actual LAN subnet.
- For anything beyond a single trusted LAN (a phone on mobile data, a satellite in another
building, a device you don't administer), use HiveMind instead of
opening the bus. HiveMind gives satellites an authenticated, encrypted channel without
widening the bus itself. See Composable Deployments: Satellites for how a
satellite fits into a wider topology.
- Serving the bus itself over TLS (for the cases above where it does need to leave localhost)
is covered in Bus restart / reconnect behavior: Serving the bus over TLS.
Reverse-proxying the HTTP servers¶
The STT, TTS, translate, and
persona servers are all plain uvicorn apps with no built-in TLS, so
fronting them with a reverse proxy for HTTPS is the normal deployment shape. Doing that has one
sharp edge: uvicorn only honors the proxy's X-Forwarded-Proto header from an address it
already trusts, and by default that's 127.0.0.1 alone. A proxy running anywhere else — another
container, another host — is untrusted, so uvicorn ignores the header and reports the request as
plain http://. If the app or the proxy then redirects based on that (a common HTTPS-enforcement
pattern), the redirect's Location comes back http:// instead of https://, and a client that
follows redirects turns its next request into a GET with no body — silently dropping whatever
the original POST was carrying.
The symptom is an opaque 400 partway through a request sequence, and it shows up only with
clients that follow redirects; driving the same endpoint directly with curl succeeds, which
sends the search in the wrong direction (the proxy, not uvicorn's trust list).
Fix it by telling uvicorn which address to trust, via the FORWARDED_ALLOW_IPS environment
variable (none of these four servers exposes a CLI flag for it; only --host and --port are
their own). Scope it to the actual proxy address — for a proxy running as another container on
the Docker bridge network, that's the bridge gateway IP (commonly 172.17.0.1), not a wildcard.
Each compose stack's network has its own gateway, so a box running several stacks needs a
different value per stack. Read the real one with docker network inspect <network> rather
than assuming the default bridge:
A wildcard (FORWARDED_ALLOW_IPS=*) makes uvicorn trust the header from any source, which
defeats the point of the check on a server reachable from more than one network.
To confirm this is the bug rather than something else, compare the Location header of a
redirect hit directly against the container versus through the proxy: a scheme change
(https:// direct, http:// through the proxy) is the whole fault, and confirms the proxy's
address needs adding to FORWARDED_ALLOW_IPS. A convenient probe on servers with the MCP
extra: GET /mcp always answers with a 307 to /mcp/, so through the proxy a Location
starting https:// means the trust list is right, and http:// means it is not.
Read next: Production Operations Related: Security Model · Satellites · HiveMind Agents