Skip to Content

What you expose to the network, and what you let run inside the process

the wall, and what you let through it
Matter Hub Guide · Part 12

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.

2 planes
Matter reachability (mDNS discovery, then an operational session over IPv6) and the admin Web UI / API are separate paths into HAMH. Securing one does nothing for the other
0 checks
How many of Basic Auth and the IP allowlist the WebSocket upgrade passes through in Stable 2.0.55. A trusted proxy has to close that gap on its own
No sandbox
What isolates a plugin from the rest of HAMH once you enable it. It runs in-process, with the same permissions as the backend itself
Two paths, not one

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.

In plain terms

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"]
Two arrows in, one processThe top arrow, declared first, is the controller's Matter path — discovery over mDNS, then an operational session over IPv6. The bottom arrow is the admin browser's HTTP, REST and WebSocket path. Both land on the same bridge process, which is the whole point of this section: a proxy in front of the bottom arrow changes nothing about the top one.
Never expose HAMH straight to the internet. Behind the Web UI sit bridge controls, backups, plugin installation, Lock Credentials and the API itself. Basic Auth and the IP allowlist are a single layer of risk reduction — not multi-account role-based access, not single sign-on, not full auditing, and not a platform built to resist a determined brute-force attempt. Put HAMH behind a trusted LAN or VPN and a controlled reverse proxy first, and treat everything in this part as hardening on top of that, not a substitute for it.
PlaneTrafficRequired capabilityMain risk
HA planeHAMH to Home Assistant, over HTTP / WebSocketA reachable URL, a valid access tokenLeaked secrets, excessive permissions on the token
Matter discoverymDNS multicastThe right LAN interface, IPv4 / IPv6 advertisementsMulti-interface mistakes, VLANs silently blocking it
Matter operationalUDP on the port each bridge is set to; a Camera setup may also need TCPThe controller reachable in both directionsFirewalls, OTBR misrouting the return path
Admin planeHTTP, REST, WebSocketIngress or a proxy, plus authenticationUnauthorized actions and information disclosure
IPv6, mDNS and VLANs

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.

In plain terms

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"]
Four ends to this ladderThree are reached by answering a diamond with no — no routable ULA, no mDNS reflector, no firewall opening in both directions — and each is a stop. Only answering yes three times in a row reaches the success box at the foot of the chain. Jumping straight to a firewall rule while all you have is link-local wastes the afternoon this diagram exists to save.
The smallest workable topology. For your first commissioning, put the phone, the controller hub and HAMH on one controlled subnet where multicast and IPv6 both reach in each direction. Once that is stable, add VLAN policy one item at a time, and verify each addition with the Network Diagnostics screen covered earlier in this part before you add the next.
A safe deployment order

Seven steps, in the order that keeps you from debugging two things at once

  1. 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.

  2. 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.

  3. Step 3

    Limit mDNS to the right interface

    On the add-on, set mdns_network_interface in Configuration to the LAN interface you just confirmed on that read-only screen; on a plain deployment, use the matching mdns-network-interface start 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.

  4. 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.

  5. 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.

  6. 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 live and ready probes skip Basic Auth, which is exactly why you still need to restrict who on the network can reach them.

  7. 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.

Ingress, Basic Auth and the gap

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.

OptionSecurity / network meaningMain limit
protocol-log-levelmatter.js MessageChannel / Exchange log detaildebug gives per-packet payloads; use it only temporarily and protect the log
http-ip-whitelistAllows only the IPv4, IPv6 or CIDR entries you listAllows everything by default; behind a proxy the source address and the trusted headers have to be right
mdns-disable-ipv4Advertises mDNS over IPv6 onlyA controller without IPv6 can no longer discover the bridge
mdns-network-interfaceLimits which interface mDNS usesInterface names depend on the host; pick the wrong one and the bridge is never found
mdns-strip-global-ipv6Does not publish a global unicast address over mDNSDoes not exclude the Thread ULA; you still have to bind the right interface
http-auth-username / http-auth-passwordTurns on a single HTTP Basic Auth credentialNot multi-account, not RBAC, not SSO; needs TLS or a trusted proxy to protect the transport
http-base-pathMounts the Web UI and the API under a sub-pathThe proxy rewrite, the WebSocket and the prefix all have to agree
home-assistant-url / home-assistant-access-tokenTrust and connection from HAMH to HAThe token is required and is a secret; keep it out of public files, logs and URLs
storage-locationWhere identity, settings and backups are persistedNeeds least-privilege file permissions, a reliable volume and controlled backups
http-portThe admin HTTP listen portNot the Matter bridge operational port, and not proof the firewall is safe
log-level / json-logsOperational records and log centralizationCentralized 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"]
Two ends, one applicationThe top path, an ordinary HTTP or REST call, is the one Basic Auth and the allowlist actually inspect. The bottom path, a WebSocket upgrade, reaches the same application code without passing through either check — in Stable 2.0.55 it attaches straight to the raw HTTP server. Closing that gap is a job for a trusted proxy, because HAMH's own checks never see it.
In plain terms

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.
In ENV mode you can only supply one value.
When it is unset, every source is allowed by default — this is a network filter, not a substitute for authentication, TLS, CSRF protection or application-level authorization.

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.

Lock Credentials go with control commands, so treat storage and backups as highly sensitive. Never let a value appear in a screenshot, a log, a translation file, a support ticket, or version control. After you rotate one, verify it in a low-risk, supervised situation, and do not reuse any value that has ever appeared in a document like this one.

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.

Expose things in this order. LAN only first, then Ingress or a VPN, then a trusted proxy with TLS — open it up across VLANs only when there is a concrete need. Do not read “the controller needs local access” as “any outside user has to be able to reach the HTTP interface.”
What a plugin runs with

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.

In plain terms

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.

Back up before you install anything. Create a full backup that includes identity, download it to a controlled offline location, and record the current bridge and plugin state. An uninstall, a package upgrade, a broken symlink or a bad plugin write can all make endpoints disappear — controller rooms, automations and endpoint identity can all be affected at once.
SourceUpdate / loadMain riskApplies to
Built-inWith the HAMH versionMay still be experimental; check controller supportSpecific Camera and Security capabilities
npm packageLoaded when you restart the bridge after installingName confusion, dependencies, and the install-script supply chainReviewed, version-pinned production testing
Uploaded tgzRestart after you upload and install itOpaque origin, integrity and contentsBuild artifacts you can verify yourself
Local symlinkSource changes take effect after a bridge restartPath substitution, permissions, non-portability and persistenceIsolated development only, never production

Lifecycle: what a plugin can hook into

HookWhat 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
One edge needs youFailure carries a plugin from Enabled to Breaker Open to Disabled with no button pressed at all. Getting back to Enabled is the exception: Reset only clears the breaker, and nothing reopens the plugin until you also press Enable — which is why the real cause has to be fixed before either click, not after.
The wrapper is a defensive boundary, not an isolation boundary. A plugin runs in-process with the backend. A fire-and-forget promise inside it can escape the scope of a single runner call, and the process-level unhandled-rejection handler can only log the failure and keep the whole process from crashing outright — that is not a security sandbox, whatever it looks like from the Plugins page.
CapabilityRelease channelProduct maturityController support
The Plugins page and plugin managerStable 2.0.55Usable; third-party quality is each publisher's own responsibilityDepends on the device type that gets registered
The built-in Camera PluginBuilt into StableExperimental (experimental-in-Stable); the media path has no completed end-to-end verification on real hardwareThe 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 PluginBuilt into StableExperimental (experimental-in-Stable), with its own state machineHow the mode switch and contact sensor appear depends on the controller
Plugins on a Server Mode bridgeServer Mode exists inside StableNot supported; Server Mode itself is experimentalNo plugin endpoints at all
REST / WebSocketVerifiable in the v2.0.55 codeFor the Web UI and for integrationsNot 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

REST, WebSocket and the API

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.

InterfaceWhat it is forSecurity note
Plugin RESTInstall, lifecycle, configurationIt can change things; restrict it to administrators
WebSocketBridge updates and diagnosticsFollows the base path, but does not inherit HAMH's Basic Auth or allowlist; the proxy has to authenticate and restrict the upgrade
Logs / streamQueries and live logsMay contain information about your environment; clear is a destructive diagnostic action
MetricsResource, bridge and HA trendsLabels and versions are operational information
live / readyProcess and HA readiness probesThe 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.

Avoid automatic “fixes.” Do not auto-Reset or auto-Enable the moment monitoring sees a single breaker error, and do not run a factory reset just because ready failed once. Automation may alert and collect, nothing more — any API call that changes state should still need a person to approve it.
When it does not add up

The symptom you see, and the safe fix — not just a restart

Network and admin access

SymptomCheckSafe fix
Commissioning cannot find the bridgeThe phone / hub segment, mDNS forwarding, the bound interface, IPv6, and AP isolationGo 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 commissioningWhether mDNS is publishing an unreachable interface or global address, the firewall in both directions for the operational port, and session healthBind 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 startsWhether the return IPv6 route has been taken over by Thread's broad ULA routeHave 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 updatesThe WebSocket upgrade, the base path, and the Ingress / forwarded-prefix headerMake the rewrite and the prefix agree, have the proxy overwrite the header, and reload the page
Turning on the allowlist locks the admin out tooWhether the backend sees the client or the proxy, and how the CIDR and IPv6 entries are writtenRestore 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 conflictWhether Settings shows the source as environment — the environment source winsUpdate 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

SymptomCheckSafe fix
The Plugins page is empty, or the built-ins are missingWhether there is no bridge at all, whether every bridge is in Server Mode, or whether the standard bridge is not runningCreate 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 loadThe installed list, the manifest's main file and API version, whether the bridge has restarted, and import errors in the logConfirm 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 trippedThe last error, the consecutive failure count, the external service, and the schemaDisable first, fix the root cause, then Reset and Enable; a bare Reset will only trip again
The settings page does not redact a secretWhether the plugin's schema actually marks the field as secretStop 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 fineThe proxy's WebSocket upgrade, the base path, and the browser socketFix the proxy and reconnect; do not reset the bridge's fabric over this
A device is left on the controller after an uninstallWhether the bridge restarted, whether the plugin's endpoint is still there, and the controller's own cacheLet 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
Questions people ask

The ones that come up again and again

Can HAMH disable IPv6 completely?
It should not. Matter operational connectivity depends on IPv6. 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?
No. In Stable 2.0.55 the semantics are one HTTP Basic Auth credential, with no roles, no fine-grained API permissions, and no SSO guarantee.
Can the IP allowlist replace the password and the proxy?
No. It only filters source addresses, and the proxy topology can change which source address is even visible to it. You still need a controlled network, real authentication, and TLS underneath it.
Are Ingress and http-base-path the same thing?
No. Ingress is a proxy path the Home Assistant Supervisor provides for you; the base path is the mount setting for a plain web app deployment. Both need the assets, the API and the WebSocket to share one prefix, or the page loads with nothing working underneath it.
Why can mDNS not be bound to the Thread interface?
A controller on the LAN usually cannot use a Thread mesh-local address; even when both addresses happen to be ULAs, that does not make them routable to each other. Bind the real LAN interface instead.
Does the Lock Credentials list show values in plaintext?
No. The frontend uses a sanitized response and shows it redacted. But storage, use in commands, and backups are all still sensitive, so do not relax any protection just because the UI happens to redact it.
Can a plugin run on a Server Mode bridge?
No. The v2.0.55 code explicitly skips plugin info for a Server Mode bridge, and the built-ins are no exception. Use a standard bridge instead.
Is the built-in Camera plugin a mature production feature in Stable?
No. It is an experimental feature living inside the Stable channel. The media path and controller support have to be judged separately from the channel label, and you cannot promise it works on Apple Home or on every platform.
Is the circuit breaker a security sandbox?
No. It enforces a timeout and disables the plugin after consecutive failures. The plugin still runs in-process with the backend the whole time, which carries the same supply-chain and data-access risk it always did.
Is the REST API a public contract guaranteed to stay compatible?
The sources for this version prove what the v2.0.55 routes and shapes are; none of them guarantee permanent compatibility going forward. Any integration should pin a version, tolerate failure gracefully, and test before every upgrade.
Is a local symlink plugin suitable for production?
No. It depends on an absolute path on the host and on the symlink continuing to exist, and a change at the source takes effect right after a restart with no review step. Use it only for isolated development.
Does a successful readiness check mean every plugin is healthy?
No. ready looks only at whether HA is connected. Plugin breakers, failed bridges, sessions and controller support all have to be monitored separately from it.
Next

Where to go from here

the last door: recovery

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 guide

Part 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

Read the layer before you press Restart