What you expose to the network, and what you let run inside the process
So far this part of the guide has been about watching the bridge you already built. This one is about two responsibilities that only ever come up once, and that most people get partly wrong the first time. First: the Web UI and the API that manage your bridge sit on the same box as the Matter traffic it serves, and the two need different protection, not one blanket rule. Second: a plugin is not a sandboxed app you install and forget — it runs inside HAMH's own process, with HAMH's own permissions, the moment you enable it. Both problems share the same fine print. In Stable 2.0.55, the WebSocket upgrade is the one door that neither Basic Auth nor the IP allowlist ever checks, and it shows up whether the traffic arrives through the admin plane or through a plugin's own API calls.
Reachability and admin security are two separate problems
A Matter controller has to discover your bridge over mDNS and then open an operational session over IPv6 or a similar path. The admin browser you use to configure HAMH works through a completely different set of doors: its HTTP, REST and WebSocket interfaces. Putting the HTTP side behind a reverse proxy does not forward Matter's multicast traffic for you, and getting an mDNS reflector working does not add authentication to the admin interface. Draw “controller to bridge” and “admin to the Web UI and API” as two separate diagrams, because fixing one does not touch the other.
It is a shop with a customer entrance and a loading dock around the back. The customer entrance is where people walk in, and a receptionist is expected to check who they are. The loading dock is where pallets come and go all day, and it never had a receptionist — it has a schedule and a lock instead. Putting a new receptionist on the customer door does nothing for a delivery truck arriving at the dock, and bolting the dock door does nothing for who walks in the front.
In the pinned Stable 2.0.55 release, the Home Assistant add-on mirror sets host_network: true, turns on Ingress, mounts addon_config for persistence, and exposes the app log level, disable-log-colors, mDNS interface and strip-global-IPv6 options in its own Configuration tab. Those are fixed facts about the add-on specifically. A plain Docker or npm install is driven by command-line start options instead, so do not assume the two ways of installing HAMH expose exactly the same fields in exactly the same place.
flowchart LR C["Matter controller
(phone, hub, or app)"] -->|"mDNS discovery,
then an operational session
over IPv6"| H["HAMH bridge process"] A["Admin browser"] -->|"HTTP, REST,
and WebSocket"| H H --> HA["Home Assistant
HTTP / WebSocket"]
| Plane | Traffic | Required capability | Main risk |
|---|---|---|---|
| HA plane | HAMH to Home Assistant, over HTTP / WebSocket | A reachable URL, a valid access token | Leaked secrets, excessive permissions on the token |
| Matter discovery | mDNS multicast | The right LAN interface, IPv4 / IPv6 advertisements | Multi-interface mistakes, VLANs silently blocking it |
| Matter operational | UDP on the port each bridge is set to; a Camera setup may also need TCP | The controller reachable in both directions | Firewalls, OTBR misrouting the return path |
| Admin plane | HTTP, REST, WebSocket | Ingress or a proxy, plus authentication | Unauthorized actions and information disclosure |
Why crossing a VLAN takes more than opening a port
An IPv6 link-local address only works inside the same Layer 2 segment. It is not a routing plan for crossing VLANs — going across subnets needs a routable Unique Local Address (ULA), plus the right firewall and route entries on both sides. mDNS works the same way for a different reason: it announces and queries services over multicast, and most routers will not forward multicast across VLANs on their own. You need an explicit mDNS reflector or gateway policy, and the operational traffic between the controller and the bridge still has to be allowed in both directions once discovery succeeds.
mDNS is shouting across a room. It carries fine to everyone standing in the same room as you, and it carries to nobody in the room next door, no matter how loudly you shout. A routable ULA with a firewall rule is mailing a letter instead — it has a real address on the envelope, so it can cross a hallway, a floor, or a VLAN boundary that a shout never will. Fixing the shouting problem does not fix the mail problem, and buying a bigger megaphone (a stronger Wi-Fi signal) does not either.
On a host with several network cards, letting mDNS advertise on every interface invites the controller to pick the Docker bridge, a virtual interface, the Thread interface, or a global IPv6 address with no return path — and then discovery either fails outright or looks like it worked and then goes nowhere. Three options address this directly, and their names are exact:
mdns-network-interface — limits mDNS to the real LAN interface you name.mdns-strip-global-ipv6 — removes global IPv6 addresses from what mDNS advertises, keeping only the locally reachable ones.mdns-disable-ipv4 — stops advertising IPv4 entirely. Use this only once you have confirmed every controller and the whole path actually has IPv6.An OpenThread Border Router (OTBR) can share a host with HAMH, but the Thread interface is not an ordinary LAN. Beyond not binding mDNS to it, check whether the IPv6 routes OTBR adds pull the cross-VLAN ULA return path into the Thread mesh by mistake. If a request arrives but the reply cannot get back, commissioning may show up as peer unresponsive; the fix is for the network admin to add a more specific LAN ULA route, not to turn IPv6 off.
flowchart TD A["Matter traffic needs
to cross a VLAN boundary"] --> B{"Is there a routable ULA,
not just an IPv6 link-local address?"} B -->|"link-local only"| C["Stop here.
Link-local only works inside
one Layer 2 segment"] B -->|"a routable ULA exists"| D{"Is mDNS forwarded
across that boundary?"} D -->|"no reflector or gateway"| E["Discovery fails, even though
the ULA route is fine"] D -->|"a reflector is in place"| F{"Is the operational port open
in both directions?"} F -->|"no"| G["Discovery may succeed,
then commissioning shows
No Response"] F -->|"yes"| I["Cross-VLAN commissioning
can work"]
Seven steps, in the order that keeps you from debugging two things at once
-
Step 1
Draw the two data paths
List the segments and boundaries for HAMH to HA, the controller hub to HAMH, and the admin browser to HAMH. Mark
<LAN_INTERFACE>, the VLANs, the proxy and OTBR on the diagram — but keep real addresses, hostnames and identifying data out of anything you share. -
Step 2
Verify IPv6 and mDNS first
In Health → Network Diagnostics, read off the available LAN interfaces, the IPv6 types, and the interface currently bound — without changing anything yet. Crossing VLANs needs a routable ULA, mDNS forwarding, and firewall rules in both directions; while all you have is link-local, do not cross VLANs yet.
-
Step 3
Limit mDNS to the right interface
On the add-on, set
mdns_network_interfacein Configuration to the LAN interface you just confirmed on that read-only screen; on a plain deployment, use the matchingmdns-network-interfacestart option. Only turn on strip-global-IPv6 when the global IPv6 return path is unreliable, and only disable IPv4 once you are sure the IPv4 advertisement is unreachable and every controller you have supports IPv6. -
Step 4
Build the smallest firewall rule set
Allow mDNS between the subnets that need it, plus the operational port each HAMH bridge is actually configured with, in both directions. Let admin HTTP in only from the management VLAN or the proxy. Do not assume a fixed list of bridge ports from a document — go by what the Bridges page actually says right now. If you run a bridge dedicated to the Camera Plugin, assess TCP on that same operational port as well.
-
Step 5
Protect the Web UI, the API and the WebSocket
Prefer add-on Ingress or a trusted reverse proxy over exposing HAMH directly. In v2.0.55 the WebSocket path does apply the base path, but the upgrade attaches straight to the raw HTTP server and bypasses HAMH's Basic Auth and IP allowlist entirely — the proxy itself has to authenticate and restrict the upgrade, and the backend must never sit reachable from an untrusted network. On a plain deployment, the path rewrite, the frontend assets, REST and the WebSocket all still have to agree on the same prefix.
-
Step 6
Then add Basic Auth and the allowlist
Supply the credentials through a secret manager or a restricted environment configuration — never in a compose example or your command history. List only the admin sources you actually need in the allowlist. Keep the monitoring requirement in mind: the code lets the health
liveandreadyprobes skip Basic Auth, which is exactly why you still need to restrict who on the network can reach them. -
Step 7
Verify one change at a time, and roll back if needed
Change one thing at a time. After each restart, check Network Diagnostics, Health
ready, the WebSocket live status, the bridge session, and one low-risk endpoint. If something fails, roll back to the last known-good configuration — do not change mDNS, VLANs, the proxy and authentication all in the same pass.
The admin interface, hardened in the right order
You can verify every option below in v2.0.55's start-options-builder.ts. The release channel is Stable, and the maturity of the command-line and environment configuration is Stable — but neither of those facts says anything about whether a particular controller supports IPv4, IPv6, or crossing VLANs. Server Mode, Camera and Security all appear in Stable too, and their product maturity is still experimental (experimental-in-Stable); do not confuse channel maturity with feature maturity, a distinction the plugins section below leans on again.
| Option | Security / network meaning | Main limit |
|---|---|---|
protocol-log-level | matter.js MessageChannel / Exchange log detail | debug gives per-packet payloads; use it only temporarily and protect the log |
http-ip-whitelist | Allows only the IPv4, IPv6 or CIDR entries you list | Allows everything by default; behind a proxy the source address and the trusted headers have to be right |
mdns-disable-ipv4 | Advertises mDNS over IPv6 only | A controller without IPv6 can no longer discover the bridge |
mdns-network-interface | Limits which interface mDNS uses | Interface names depend on the host; pick the wrong one and the bridge is never found |
mdns-strip-global-ipv6 | Does not publish a global unicast address over mDNS | Does not exclude the Thread ULA; you still have to bind the right interface |
http-auth-username / http-auth-password | Turns on a single HTTP Basic Auth credential | Not multi-account, not RBAC, not SSO; needs TLS or a trusted proxy to protect the transport |
http-base-path | Mounts the Web UI and the API under a sub-path | The proxy rewrite, the WebSocket and the prefix all have to agree |
home-assistant-url / home-assistant-access-token | Trust and connection from HAMH to HA | The token is required and is a secret; keep it out of public files, logs and URLs |
storage-location | Where identity, settings and backups are persisted | Needs least-privilege file permissions, a reliable volume and controlled backups |
http-port | The admin HTTP listen port | Not the Matter bridge operational port, and not proof the firewall is safe |
log-level / json-logs | Operational records and log centralization | Centralized logs still need access control, redaction and a retention policy |
The pinned add-on mirror only exposes the options its own schema lists — do not write every plain start option up as an add-on UI field that may not exist. The other way round, the Ingress URL is managed by the Supervisor, so never hard-code an ingress token or a base path anywhere. In documents and support tickets, use explicit placeholders only, such as <INGRESS_PATH>, <LAN_INTERFACE> and <TRUSTED_PROXY> — never a real value, and never anything shaped like one.
Ingress, the base path, and the WebSocket exception
When a non-root base path is configured, WebApi redirects the root path to that prefix and mounts the API and the Web UI on the same app router. It also supports Home Assistant Ingress and proxy location headers. Because prefix headers change how URLs get rebuilt, a trusted proxy should strip any incoming header of the same name and then write a fixed value of its own — and the HAMH backend is best off never listening directly on an untrusted subnet in the first place.
The reverse proxy has to proxy both ordinary HTTP and the WebSocket upgrade. In v2.0.55 the upgrade does not inherit HAMH's Basic Auth or its IP allowlist at all — it is attached directly to the raw HTTP server, so a trusted proxy or network boundary has to authenticate and restrict the upgrade itself, and block direct connections to the backend from anywhere else.
flowchart TD R["A request arrives at
HAMH's web server"] --> Q{"Is it an ordinary HTTP
or REST request?"} Q -->|"yes"| P["Passes through Basic Auth
and the IP allowlist"] Q -->|"no, a WebSocket upgrade"| U["Attaches directly to
the raw HTTP server"] P --> X["Reaches the application"] U --> Y["Reaches the same application.
Neither check ran"]
It is a building where the front door has a keypad and the loading bay door around the back does not — not because anyone chose to leave it open, but because whoever wired the alarm system never ran a cable out there. Anyone who knows the bay exists can walk straight through it, keypad or no keypad. The fix is not to complain about the keypad; it is to put a second lock on the bay door yourself.
If the page loads but the state never updates, and Live Event shows Offline, the usual cause is the WebSocket not being forwarded or the base path not matching on both ends — not a broken Matter session. Check the proxy configuration before you touch anything related to fabrics or bridges.
Basic Auth and the IP allowlist
Basic Auth can come from an environment option or from the stored settings in Settings; when the environment setting exists, the code treats the environment as the source of truth. Its semantics are one username and password pair — not tiered permissions for several users. Without TLS, Basic Auth does not give you enough transport confidentiality on its own; behind a proxy you also have to handle the source IP correctly, or the allowlist may only ever see the proxy's own address, or be swayed by a spoofed header.
http-ip-whitelist accepts IPv4, IPv6 or CIDR, and can be given more than once.Lock Credentials
Lock Credentials is a page in Stable 2.0.55 used to link a specific Home Assistant lock entity to a Matter PIN credential. When requirePinForRemoteOperation is on, a remote unlock or unbolt has to verify the PIN, while locking is still allowed without one. List responses come back redacted and the UI never echoes the secret; what is persisted is a PBKDF2 hash and a salt, not retrievable plaintext — but a short PIN still has low entropy, so you have to guard against offline guessing regardless. You can add, update, enable or disable, and delete entries. This is not a Matter commissioning credential, and it is not an ordinary website login password.
HA tokens and secret handling
The HA access token, the Basic Auth password, plugin secrets, Lock Credentials, and a full Matter identity backup all need a secret manager, a restricted environment, or a root-only config — with the HA permissions on that token and the list of people who can read the backups kept as small as possible. Protocol debug output, HTTP access logs and diagnostic exports need a short retention period and a manual check before you share them with anyone. Keep full backups encrypted and offline, and if you suspect a leak after a restore, run the matching credential rotation rather than only deleting a file.
A single home LAN
Add-on host networking, with the phone, the controller hub and HA on the same controlled segment. Still confirm multicast is not suppressed by AP isolation or an IGMP setting, and keep the admin entry point limited to Ingress. A simple topology is not the same as being free to turn IPv6 off or make the Web UI public.
A small-office VLAN
The controller hub sits on the IoT VLAN, HAMH on the Server VLAN, and admins come from the Management VLAN through a proxy. The network admin configures ULA routing and an mDNS gateway, allowing only the bridge's operational traffic in both directions; admin HTTP only ever arrives through the proxy. Basic Auth is extra protection here, not a substitute for the cross-VLAN firewall.
OTBR on the same host
Pin mDNS to the LAN interface, and confirm Network Diagnostics is not showing the Thread interface as the advertisement exit. If a ULA VLAN is only reachable one way, check route selection; fix the return path with a more specific LAN route instead of deleting the Thread routes or turning IPv6 off entirely.
In-process code, not a sandboxed extension
A plugin can register Matter devices, update cluster state, receive controller attribute writes, call external services, and use persistent storage. In v2.0.55 a plugin runs directly inside the HAMH backend process, with no OS-level sandbox around it. The error wrapper, the timeouts and the circuit breaker reduce how far a failure can spread, but none of them can make a malicious package safe. Installing a third-party plugin is the same act as authorizing it to run code with the permissions of the HAMH process itself.
Plugins are supported on standard bridges only. A bridge with Server Mode enabled builds no plugin manager and carries nothing at all, not even the built-in plugins; if every bridge you have is in Server Mode, the Plugins page shows the matching empty state. This has nothing to do with the release channel — Server Mode itself is still experimental (experimental-in-Stable), independent of the plugin question.
Installing a plugin is not like installing an app from a store, each one boxed into its own folder. It is closer to handing a new employee your master keys on their first morning and trusting them to only open the drawers they are supposed to. Most employees are fine. The point of a master key is that nothing on the door stops the ones who are not.
| Source | Update / load | Main risk | Applies to |
|---|---|---|---|
| Built-in | With the HAMH version | May still be experimental; check controller support | Specific Camera and Security capabilities |
| npm package | Loaded when you restart the bridge after installing | Name confusion, dependencies, and the install-script supply chain | Reviewed, version-pinned production testing |
| Uploaded tgz | Restart after you upload and install it | Opaque origin, integrity and contents | Build artifacts you can verify yourself |
| Local symlink | Source changes take effect after a bridge restart | Path substitution, permissions, non-portability and persistence | Isolated development only, never production |
Lifecycle: what a plugin can hook into
| Hook | What it does |
|---|---|
onStart(context) | Required. Discovers and registers devices, and opens connections, when the bridge starts |
onConfigure() | Optional. Restores state after device registration |
onShutdown(reason) | Optional. Cleans up timers and sockets |
getConfigSchema() | Optional. Supplies the UI schema for the plugin's settings |
onConfigChanged(config) | Optional. Applies a settings update |
The context object handed to a plugin gives it: register and unregister device, update state, domain mapping, plugin-scoped storage, a logger, the bridge ID, and an optional HA connection.
Enable / Disable versus Install / Uninstall
Enable and Disable are per-bridge, per-plugin state. Disable unmounts that plugin's mounted devices and persists the choice; Enable starts the plugin again. Settings can be saved while a plugin is disabled, and they take effect once you enable it later. Install and Uninstall work at the package level and usually need a bridge restart before they apply — do not confuse “the package is installed” with “the plugin is enabled on this bridge.”
The circuit breaker: SafePluginRunner
SafePluginRunner applies a default timeout to ordinary lifecycle calls, and when consecutive failures reach a threshold it opens the circuit breaker and disables the plugin automatically; a successful operation resets the failure count. The Reset button only clears the breaker so it can try again — it does not fix the root cause. Shutdown cleanup is still attempted even while the breaker is open, so resources are not leaked in the meantime.
stateDiagram-v2 [*] --> Enabled Enabled --> BreakerOpen: consecutive failures reach the threshold BreakerOpen --> Disabled: the plugin is disabled automatically Disabled --> Enabled: Reset, then Enable, after you fix the cause
| Capability | Release channel | Product maturity | Controller support |
|---|---|---|---|
| The Plugins page and plugin manager | Stable 2.0.55 | Usable; third-party quality is each publisher's own responsibility | Depends on the device type that gets registered |
| The built-in Camera Plugin | Built into Stable | Experimental (experimental-in-Stable); the media path has no completed end-to-end verification on real hardware | The official docs at this same version say Apple Home does not render it; SmartThings was the main target at the time, which cannot be extrapolated further |
| The built-in Security Plugin | Built into Stable | Experimental (experimental-in-Stable), with its own state machine | How the mode switch and contact sensor appear depends on the controller |
| Plugins on a Server Mode bridge | Server Mode exists inside Stable | Not supported; Server Mode itself is experimental | No plugin endpoints at all |
| REST / WebSocket | Verifiable in the v2.0.55 code | For the Web UI and for integrations | Not directly related to Matter controller support |
The v2.0.55 code and the pinned docs list the REST routes, the WebSocket messages and the response shapes — but nowhere in those sources does the project promise a public API versioning policy guaranteeing permanent compatibility. Unless another source guarantees it, do not describe this as a stable public contract. Any automation against it should pin a version, check status and schema, set timeouts, and run a smoke test before every upgrade.
A plugin manifest can declare hamhPluginApiVersion; the manager's own constant is currently version one, and a mismatch is recorded only as a warning. That warning is not a compatibility guarantee, and it does not upgrade a third-party package for you. A live matter.js EndpointType also has to come from the same matter.js instance running inside HAMH, so an external package is usually better off working with serializable device-type and cluster data instead.
-
Step 1
Confirm the standard bridge and the isolation scope
In Bridges, pick a standard bridge that does not have Server Mode enabled, and confirm first that it is running and that its fabric and health are normal. Give the experimental Camera and Security plugins — or any third-party plugin — a dedicated bridge; that narrows both the controller-compatibility surface and the blast radius of a failure.
-
Step 2
Back up, then review the source
Create a full identity backup and a record of the current plugin state. For npm, pin an explicit version and cross-check the package owner, the source repository, the manifest, the dependencies and the release integrity. A tgz has to be produced by a CI you trust and then verified; a symlink belongs only in an isolated development environment.
-
Step 3
Install a package, or pick a built-in
Do not type a built-in name into the npm field — the code rejects the reserved built-in names on purpose. Enable and configure a built-in straight from the Plugins page. For a third-party package, use the matching npm, Upload or Local tab, restart the target bridge when the prompt tells you to, then Refresh the list.
-
Step 4
Configure the minimum the schema asks for
Fill the required fields first, make sure every number parses, and leave any schema key you do not recognize at its existing setting. Fields marked secret are redacted by the backend and the stored value is never returned to you; leaving the redacted placeholder alone means keep the current value. Enter any token or password only through your own secret-management process — never into a document or a log.
-
Step 5
Verify the endpoint and the controller
Check the plugin's metadata, its enabled state, its devices and the breaker state, then open the bridge's endpoint tree and check the device types and clusters. Test state and a controller write with one low-risk device. Camera also needs you to assess the TCP firewall on its operational port; with Security you must not use a safety-critical entity for the first test.
-
Step 6
Set up a fallback point for failure
If errors start rising, Disable the plugin first so its mounted devices are removed cleanly, keeping the log and its settings intact. Only after you have fixed the external service or the schema should you Reset the breaker and Enable it again. If you are going to Uninstall, confirm first that no other bridge is still using it, keep the backup, then restart and verify the other plugins.
The interfaces you can verify today, not a promise for tomorrow
Camera Plugin
Camera exposes a chosen Home Assistant camera entity as a Matter camera device over a WebRTC transport flow. It registers no device until a camera entity is set; once one is set, it can be mounted dynamically. A custom HA URL and secret are optional, but the minimal configuration should just reuse the bridge's existing HA connection. A dedicated bridge isolates the effect of the Matter-over-TCP capability on your other controllers.
In Stable 2.0.55 it is still experimental — you cannot claim Apple Home support, and you cannot claim live view has been fully verified on any controller. A camera stream involves privacy, the network and the firewall all at once, and the Plugins page working is not the same thing as meeting the surveillance regulations or the access policy you are actually subject to.
Security Plugin
Security creates a Home / Away / Night / Vacation mode switch and an Alarm contact sensor, and runs on exit and entry delays, a trigger list, an alert list, setters, and a persisted armed state. It is not a synchronizing front end for an existing Alarmo or alarm integration — if both use the same set of sensors, you end up with two state machines that know nothing about each other.
This experimental plugin has no separate verification-code flow of its own; a controller that can operate the mode switch can also disarm it. Expose it only to controllers you trust, and do not treat it as a certified intruder alarm. Trigger events during a Home Assistant outage can also be lost, which is another reason it is not a safety system.
Schema secrets and the breaker
The v2.0.55 backend schema property has an explicit secret flag: a stored secret is replaced by a sentinel value in the listing, and if the value is still that sentinel when you save, the original value is kept untouched. The frontend also falls back to redacting by field name for older schemas, but the real protection has to come from the backend's own secret flag. If a third-party plugin does not mark its secrets correctly, the platform has no way of knowing on its own that a given string is sensitive.
REST surface
/api/plugins gives you the plugin metadata, the redacted config, the breaker state and the devices for each standard bridge; alongside it sit the installed list, npm install, binary tgz upload, local install, uninstall, enable / disable / reset, config schema and config update. The whole of /api also mounts matter, health, bridges export, images, mappings, settings, backup, HA, logs, system, diagnostic, metrics and network. This is a management plane, so it needs the same access controls covered earlier in this part — but the WebSocket carries the same auth-bypass boundary at this version, so do not assume REST protection extends to the upgrade.
WebSocket, logs, metrics, live and ready
The WebSocket path is mounted at /api/ws under the base path. It sends the initial bridge state on connect and can then broadcast bridge updates; a client can ping and pong, and subscribe to or unsubscribe from diagnostics, receiving a snapshot followed by the diagnostic events that come after it. As covered above, in v2.0.55 this upgrade is attached directly to the raw HTTP server, which bypasses HAMH's Basic Auth and IP allowlist entirely — a trusted proxy or network boundary has to authenticate, restrict the upgrade, and block direct backend reachability on its own. The client side also has to handle unknown message types and reconnect on its own.
The logs API supports level, search, category, facility and limit / offset, plus a level count, a clear action, and a Server-Sent Events stream; a low-memory system uses a smaller buffer. Metrics come in JSON and Prometheus form. Health live answers only whether the process is alive, and ready looks only at whether HA is connected. These endpoints cover different scopes, so a single 200 response from one of them is not a claim that the whole Matter topology is healthy.
| Interface | What it is for | Security note |
|---|---|---|
| Plugin REST | Install, lifecycle, configuration | It can change things; restrict it to administrators |
| WebSocket | Bridge updates and diagnostics | Follows the base path, but does not inherit HAMH's Basic Auth or allowlist; the proxy has to authenticate and restrict the upgrade |
| Logs / stream | Queries and live logs | May contain information about your environment; clear is a destructive diagnostic action |
| Metrics | Resource, bridge and HA trends | Labels and versions are operational information |
| live / ready | Process and HA readiness probes | The code skips Basic Auth here; rely on network restrictions instead |
Trying the built-in Camera plugin
Build a dedicated standard bridge, put one non-sensitive test camera on it, restrict it to a test controller and a test VLAN, and confirm the TCP policy, the health, and recovery through Disable. The result speaks only for that controller, that version and that network — do not extrapolate it to Apple Home, Google Home, Amazon Alexa or any other product.
Third-party cloud plugins
Test in an isolated environment with a minimum-scope credential, schema secrets marked correctly, and a log that prints no request headers or tokens. Watch the failure, memory and network trends for a while before you consider production, and review any package upgrade again as new code, not as a formality.
External monitoring
Let a monitor read ready, metrics and the logs it needs over a controlled network, and never give it an admin account that can install or delete plugins. Pin a v2.0.55 response fixture before an upgrade; afterward, verify the content type, the required fields and the WebSocket messages before you adjust the parser.
ready failed once. Automation may alert and collect, nothing more — any API call that changes state should still need a person to approve it.The symptom you see, and the safe fix — not just a restart
Network and admin access
| Symptom | Check | Safe fix |
|---|---|---|
| Commissioning cannot find the bridge | The phone / hub segment, mDNS forwarding, the bound interface, IPv6, and AP isolation | Go back to one controlled Layer 2 segment and verify there, then restore the VLAN policy one item at a time; do not start with repeated factory resets |
| No Response after a successful commissioning | Whether mDNS is publishing an unreachable interface or global address, the firewall in both directions for the operational port, and session health | Bind the LAN interface, strip global IPv6 if you have to, and restart the one affected bridge after fixing the firewall |
| Cross-VLAN connectivity drops after OTBR starts | Whether the return IPv6 route has been taken over by Thread's broad ULA route | Have the network admin add a more specific LAN route for the remote controller VLAN, then verify it; do not disable the whole of Thread |
| The reverse-proxied page opens but the data never updates | The WebSocket upgrade, the base path, and the Ingress / forwarded-prefix header | Make the rewrite and the prefix agree, have the proxy overwrite the header, and reload the page |
| Turning on the allowlist locks the admin out too | Whether the backend sees the client or the proxy, and how the CIDR and IPv6 entries are written | Restore the previous config from a controlled console and re-add only the sources you have actually confirmed; do not open it to everyone as a temporary measure |
| Basic Auth forgotten, or the sources conflict | Whether Settings shows the source as environment — the environment source wins | Update or remove the matching secret from the host's controlled console, restart, and rotate it immediately; do not paste the value into an issue |
Plugins and the API
| Symptom | Check | Safe fix |
|---|---|---|
| The Plugins page is empty, or the built-ins are missing | Whether there is no bridge at all, whether every bridge is in Server Mode, or whether the standard bridge is not running | Create or start a dedicated standard bridge; do not go to npm and install a built-in of the same name |
| The install succeeded but the plugin did not load | The installed list, the manifest's main file and API version, whether the bridge has restarted, and import errors in the log | Confirm the package's integrity and compatibility, then restart that one bridge; remove a package you do not trust outright and rotate any secret it could have touched |
| Circuit breaker tripped | The last error, the consecutive failure count, the external service, and the schema | Disable first, fix the root cause, then Reset and Enable; a bare Reset will only trip again |
| The settings page does not redact a secret | Whether the plugin's schema actually marks the field as secret | Stop using it and get the maintainer to fix the schema; rotate any exposed value immediately, because guessing from the field name in the frontend is not a sufficient guarantee |
| The web UI state does not update but REST is fine | The proxy's WebSocket upgrade, the base path, and the browser socket | Fix the proxy and reconnect; do not reset the bridge's fabric over this |
| A device is left on the controller after an uninstall | Whether the bridge restarted, whether the plugin's endpoint is still there, and the controller's own cache | Let the bridge rebuild normally and the endpoint disappear first, then remove it safely on the controller side; do not run a disaster reset for one plugin |
The ones that come up again and again
Can HAMH disable IPv6 completely?
mdns-disable-ipv4 only stops the IPv4 advertisement in mDNS; it does not disable IPv6, and crossing VLANs needs a routable ULA all the more once IPv4 discovery is gone.Is Basic Auth multi-account, or RBAC?
Can the IP allowlist replace the password and the proxy?
Are Ingress and http-base-path the same thing?
Why can mDNS not be bound to the Thread interface?
Does the Lock Credentials list show values in plaintext?
Can a plugin run on a Server Mode bridge?
Is the built-in Camera plugin a mature production feature in Stable?
Is the circuit breaker a security sandbox?
Is the REST API a public contract guaranteed to stay compatible?
Is a local symlink plugin suitable for production?
Does a successful readiness check mean every plugin is healthy?
ready looks only at whether HA is connected. Plugin breakers, failed bridges, sessions and controller support all have to be monitored separately from it.Where to go from here
You have hardened what goes in. Part 13 covers what happens when something still goes wrong.
Every option and warning in this part assumes you already have a way back out — a real backup, taken before the change that broke something. The final part of this guide walks through the backup and restore routine end to end, and then works through the complete troubleshooting reference for the whole series: the failures that touch installation, bridges, mapping, commissioning, and everything covered across this operations part.
Open the full guidePart 12 of the Home Assistant Matter Hub Complete Guide series on the Apporo blog.
Adapted from the Home Assistant Matter Hub Complete Guide, produced by WoowTech and released under CC BY 4.0. This adaptation is published by Apporo under the same licence.
Light · Air · Water · Control · apporo