Skip to content

Fox ESS Modbus Bridge

Beta

Set up the optional Fox ESS Modbus Bridge for faster live telemetry in Check My Solar - Docker stack, Cloudflare Tunnel, and Today view updates every 10 seconds.

Updated View as Markdown

By default, Check My Solar reads live data from Fox Cloud the same path as the Fox ESS app. That works well, but Fox Cloud updates up to every 5 minutes, which can make the Today view feel a little slow.

The Modbus Bridge is an optional add-on for Fox ESS inverters. A small Docker stack on your home network reads the inverter directly over Modbus TCP, then Check My Solar pulls that snapshot through a secure private tunnel. When the bridge is online, the dashboard can refresh live numbers about every 10 seconds on the Today view, and immediately after opening the app.


When to use it

The Modbus Bridge is a good fit if you:

  • Have a Fox ESS inverter with Modbus TCP enabled (typically via an RS485-to-Ethernet adapter on your LAN)
  • Run a home server, NAS, or always-on machine that can host Docker
  • Want faster live updates on the Today dashboard without waiting on Fox Cloud polling

Supported inverters

The bridge auto-detects the register profile from your inverter model string. Supported families include:

Profile Example models
H1 G2 H1-G2, AC1-G2, P1
H1 / AC1 G1 H1, AC1, AIO-H1
KH KH10.5, KH-5.0
H3 (legacy) H3, AC3, AIO-H3, Kuara, SK-HWR, STAR, Solavita SP
H3 Pro / Smart / EVO H3-Pro, H3-Smart, P3-SH, Enpal I-X, 1KOMMA5, EVO

If auto-detect fails, set INVERTER_PROFILE manually (see Environment variables).

You do not need the bridge for normal use. Linking Fox Cloud is enough for charts, history, forecasts, and notifications.


Architecture overview

At a high level, data flows from your inverter to the app like this:

Live telemetry path
Fox ESS inverter
Modbus Bridge (Docker)
cloudflared (Docker)
Check My Solar backend
Browser / iOS app

Protocols along the path: Modbus TCP between inverter and bridge; HTTP GET /v1/realtime on your Docker network; outbound Cloudflare Tunnel to Check My Solar’s private backend.

Latency: End-to-end from a Modbus read on your LAN to the value appearing on the Today dashboard is typically about 100 ms-inverter → bridge → tunnel → Check My Solar backend → app. The dashboard polls about every 10 seconds while you are on Today, so you get fresh numbers quickly.

Components

Component Where it runs Role
Inverter Your home Source of truth for live power, battery, grid, and load readings
Modbus Bridge Docker on your LAN Polls the inverter over Modbus TCP, stores the latest snapshot locally
cloudflared Docker on your LAN Connects the bridge into Check My Solar’s private network via a Cloudflare Tunnel secured with TLS 1.3 and post-quantum key agreement
Check My Solar backend Cloudflare Workers Reaches your bridge over a private VPC connection (not the public internet)
Dashboard Web or iOS Requests live telemetry when you are on the Today view and the bridge is healthy

Live variables replaced

When the bridge is online, these Today live fields come from Modbus instead of Fox Cloud:

Variable What it shows
pvPower Total solar generation
pv1Power, pv2Power PV string 1 and 2 power
pvStringCount, pvStringPowers Number of PV strings and per-string power
SoC Battery state of charge
ResidualEnergy Remaining battery energy
batChargePower Battery charge power
batDischargePower Battery discharge power
batVoltage Battery voltage
batCurrent Battery current
batTemperature Battery temperature
gridConsumptionPower Grid import
feedinPower Grid export
gridVoltage Grid voltage
gridCurrent Grid current
gridFrequency Grid frequency
meterPower2 Secondary meter (CT clamp 2)
loadsPower House load
ambientTemperature Ambient temperature
deviceTemperature Inverter temperature
runningState Inverter running state
isOffGrid Off-grid status (derived from running state)
epsPower, epsPowerR, epsVoltR, epsCurrentR EPS / backup circuit (phase R)
workMode Work mode (Self-use, Feed-in priority, etc.)

Today’s totals replaced

When the bridge is online, these Today kWh totals also come from Modbus instead of Fox Cloud:

Total What it shows
generation Solar generation today
feedin Grid export today
gridConsumption Grid import today
chargeEnergyToTal Battery charge today
dischargeEnergyToTal Battery discharge today
loadConsumption House load today

If the bridge goes offline or data goes stale, the app shows a status message and does not fall back to Fox Cloud for these fields by default - historical charts still load from Fox as usual. You can enable Fox Cloud fallback in Menu → Account → Modbus Bridge to use Fox Cloud live data when the bridge is unreachable.

When the bridge is online, work mode on Today also comes from Modbus (instead of Fox device settings). You can open the Work mode modal from the Today view to see what each mode does. Changing work mode from the app requires a read-write bridge - see Read-only mode and work mode control.

What stays on Fox Cloud

The bridge only replaces live telemetry and today’s totals for the current day. Everything else still comes from Fox Cloud:

  • Historical charts (week, month, year)
  • Daily totals for past days and other time views
  • Export limit, scheduler, and most other device settings
  • Forecasts and notifications (Fox Cloud users rely on periodic polling; Modbus Bridge users get near-instant battery, grid, and mode alerts)

Work mode changes from the app are only available when the bridge is configured for read-write access (MODBUS_READ_ONLY=false). Otherwise the app still shows the live work mode from Modbus, but cannot write a new mode.


Prerequisites

  1. Home server capable of running Docker - an always-on machine on the same LAN as your inverter (home server, NAS, Raspberry Pi, etc.). It only needs to run the small Modbus Bridge Docker stack.
  2. RS485 adapter - connects your inverter’s RS485 port to your network as Modbus TCP (default port 502, unit ID 247). Examples community members use:
  3. Fox ESS inverter with Modbus TCP enabled on the adapter
  4. Check My Solar account with the inverter already linked via Fox Cloud
  5. Network access from the Docker host to the Modbus adapter IP

You can test Modbus connectivity before setting up the bridge - from your host or from Docker (see Test inverter connectivity below).


Setup

Create a bridge token in the app

  1. Open Menu → Account
  2. Tap Modbus Bridge
  3. Make sure the correct inverter is selected on the dashboard first - each account can have one active bridge, tied to one device
  4. Tap Create token and copy the secrets shown immediately - they are only displayed once

The modal shows:

  • Bridge token - authenticates the local HTTP API (CMS_BRIDGE_TOKEN)

  • Tunnel run token - starts cloudflared (TUNNEL_TOKEN)

  • Private hostname - must match the bridge configuration (BRIDGE_HOSTNAME)

  • A ready-to-run docker compose command

On a machine on your LAN, clone the modbus-bridge repository:

git clone https://github.com/checkmysolar/modbus-bridge.git
cd modbus-bridge
export CMS_BRIDGE_TOKEN='cms_bridge_...'          # from the app
export MODBUS_HOST='192.168.1.100'              # your Modbus adapter IP
export BRIDGE_HOSTNAME='bridge-....modbus.internal'  # from the app
export TUNNEL_TOKEN='eyJ...'                       # from the app
# Optional: set MODBUS_READ_ONLY=false to allow work mode changes from the app (default is true)
docker compose up -d

The compose stack runs two containers:

  • modbus - polls the inverter and serves GET /v1/realtime on port 8080
  • cloudflared - connects the bridge to Check My Solar’s private network

No inbound ports need to be opened on your router. Traffic flows outbound from cloudflared through the tunnel, encrypted with TLS 1.3 and hybrid post-quantum key agreement on the connection to Cloudflare’s network.

The bridge container uses a distroless runtime image (no shell or package manager), runs as a non-root user, and is not exposed on your LAN — only cloudflared on the internal Docker network can reach it on port 8080.

Test inverter connectivity

Before creating bridge tokens, confirm the inverter is reachable over Modbus from your LAN.

From the host (clone the repo, no Docker):

git clone https://github.com/checkmysolar/modbus-bridge.git
cd modbus-bridge
npm install
npm run build
MODBUS_HOST=192.168.1.100 npm run probe

A successful run prints the detected model and profile, live power, battery, and today’s energy counters, then OK - inverter Modbus connectivity works.

From Docker (one-off; no cloud tokens or compose stack required):

docker run --rm \
  -e MODBUS_HOST=192.168.1.100 \
  ghcr.io/checkmysolar/modbus-bridge:latest \
  dist/probe.js

Optional environment variables: MODBUS_PORT (default 502), MODBUS_UNIT_ID (default 247), MODBUS_TIMEOUT_MS (default 5000).

If the stack is already running, probe inside the modbus container:

docker compose exec modbus dist/probe.js

If the probe fails, check adapter IP, port, unit ID, and that nothing else is holding the Modbus bus. The probe prints a short checklist on error.

Verify in the app

Reopen Modbus Bridge in Account settings. The status banner should show:

Status Meaning
Online Bridge is reachable and data was sampled within the last 60 seconds
Stale Bridge was reachable but the last sample is older than 60 seconds
Waiting Token exists but Check My Solar has not received data from the bridge yet

On the Today dashboard, live numbers should update about every 10 seconds while the bridge is online.


How the dashboard uses bridge data

When you view Today:

  1. The app loads Fox Cloud data as usual for charts
  2. If a bridge is active, it also polls the Modbus endpoint in the background
  3. When bridge data is fresh (sampled within the last 60 seconds), live fields on the energy flow diagram are updated from the bridge
  4. Polling stops automatically if data goes stale or you switch away from Today

Read-only mode and work mode control

By default, the bridge runs in read-only mode (MODBUS_READ_ONLY=true). It polls inverter registers for live telemetry and today’s totals, but never writes holding registers. That is the safest default: Check My Solar can read your inverter faster without the bridge being able to change settings.

What read-only affects

Capability Read-only (true, default) Read-write (false)
Live telemetry on Today Yes Yes
Today’s kWh totals from Modbus Yes Yes
Work mode shown on Today Yes (from Modbus) Yes (from Modbus)
Change work mode from the app No Yes (supported profiles)

Enable work mode changes

To let Check My Solar change work mode through the bridge, set:

MODBUS_READ_ONLY=false

Add it to your Docker environment (for example in .env or docker compose overrides), then restart the modbus container.

In the app

  • Menu → Account → Modbus Bridge shows Access as Read-only or Read and write for the connected inverter.
  • On Today, tap the work mode label to open the Work mode modal. If the bridge is read-only, you can still read about each mode, but Set buttons are hidden.
  • Work mode control requires bridge version 1.4.0 or later and a supported inverter profile (H1 G2, KH, H3 Pro/Smart/EVO over LAN, and others as listed in the bridge release notes).

Security and privacy

  • Post-quantum encryption - Traffic between your home cloudflared container and Check My Solar travels over a Cloudflare Tunnel protected by TLS 1.3 with hybrid post-quantum key agreement (X25519 + ML-KEM).
  • The bridge token is a bearer secret - treat it like a password. Store it only on your home server.
  • Check My Solar stores an encrypted copy server-side so the backend can authenticate to your bridge; the plain token is shown only once at creation.
  • Communication from Check My Solar to your bridge uses a private Cloudflare Tunnel hostname (*.modbus.internal), not a public URL on your home IP.
  • Your inverter is never exposed directly to the internet - only the bridge container talks to it on your LAN.
  • Read-only by default - unless you set MODBUS_READ_ONLY=false, the bridge cannot write Modbus registers, so it cannot change work mode or other inverter settings from Check My Solar.
  • Revoking a token in the app tears down the tunnel and invalidates access.

See Privacy & data for how Check My Solar handles account and device data generally.


Managing tokens

From Menu → Account → Modbus Bridge you can:

  • Create a token for the selected inverter (one active bridge per account)
  • Revoke a token - disconnects the tunnel and stops bridge access
  • Check bridge status and last sample time
  • See connected inverter details, including bridge version, firmware, and Access (read-only vs read-write)
  • Toggle Fox Cloud fallback when the bridge is unreachable

How to update

When the app shows a bridge update is available, pull the latest image and restart the modbus container. You do not need a new bridge token or tunnel token.

Docker Compose (recommended):

cd modbus-bridge
docker compose pull modbus
docker compose up -d modbus

Docker run:

docker pull ghcr.io/checkmysolar/modbus-bridge:latest
docker stop modbus-bridge && docker rm modbus-bridge
# Re-run your original docker run command from the app, or use the same env/volume flags as before

Your .env, tunnel, and bridge_data volume are unchanged. After restart, reopen Menu → Account → Modbus Bridge and confirm the Bridge version matches the latest release.


Troubleshooting

Symptom Things to check
Waiting status never clears Docker containers running? MODBUS_HOST correct? Adapter reachable from the Docker host?
Stale data Inverter online? Modbus polling errors in bridge logs? Poll interval default is 10 seconds
No Modbus Bridge menu item Feature may not be enabled for your account yet
Bridge on wrong device Only one active token per account - revoke the old one before creating for another inverter
Reinstalled Docker Revoke the old token and create a new one - secrets are only shown once at creation
Modbus errors in bridge logs Run docker run --rm -e MODBUS_HOST=... ghcr.io/checkmysolar/modbus-bridge:latest dist/probe.js, or docker compose exec modbus dist/probe.js if the stack is already up. For H1 G1 over RS485, set MODBUS_CONNECTION=aux. Override profile with INVERTER_PROFILE if auto-detect fails.
Cannot change work mode from the app Bridge shows Read-only in Modbus Bridge settings? Set MODBUS_READ_ONLY=false and restart the modbus container. Also check bridge version is 1.4.0+ and your inverter profile supports writes.

Environment variables

Variable Default Purpose
CMS_BRIDGE_TOKEN (required) Authenticates the local HTTP API
MODBUS_HOST (required) RS485 adapter or inverter IP
BRIDGE_HOSTNAME (required) Private tunnel hostname from the app
TUNNEL_TOKEN (required) cloudflared run token from the app
MODBUS_PORT 502 Modbus TCP port
MODBUS_UNIT_ID 247 Modbus unit ID
MODBUS_CONNECTION aux aux for RS485 adapters; lan for direct inverter LAN
INVERTER_PROFILE (auto) Force profile: h1g2, h1Series, kh, h3Legacy, h3Modern
POLL_INTERVAL_MS 10000 Poll interval in milliseconds
MODBUS_TIMEOUT_MS 5000 Modbus read timeout
MODBUS_READ_ONLY true When true, only read registers; work mode and other writes are disabled. Set to false to allow writes.
CMS_API_BASE_URL https://checkmy.solar Check My Solar API origin used for instant notification triggers
CMS_NOTIFICATIONS_ENABLED true When false, the bridge does not POST notification triggers
BRIDGE_VERBOSE_LOG false Log each poll and HTTP request

What the bridge does not provide

Historical charts and daily totals for past days always come from Fox Cloud regardless of bridge status.


FAQ

How does the Docker stack work?

docker compose up starts two containers on a private Docker network (cms_net):

Container Role
modbus Polls your inverter over Modbus TCP on the LAN, stores the latest snapshot, and serves GET /v1/realtime on port 8080
cloudflared Maintains an outbound Cloudflare Tunnel connection so Check My Solar can reach the bridge without opening inbound ports on your router

The modbus container is the only one that talks to your inverter. It needs MODBUS_HOST set to your RS485-to-Ethernet adapter IP on your home network.

When you create a token in the app, Check My Solar also provisions a Cloudflare Tunnel and a private hostname route for your account. You copy the tunnel run token and hostname into your environment variables and start the stack at home.

How does the tunnel reach the bridge?

The private hostname (for example bridge-abc123….modbus.internal) is registered as a Docker network alias on the modbus container. Both containers share the same cms_net network, so cloudflared can resolve that hostname to the bridge container’s internal IP.

When Check My Solar needs live data, its backend calls your private hostname over Cloudflare’s private network. The request arrives at your home cloudflared container through the tunnel, and cloudflared forwards it to http://<BRIDGE_HOSTNAME>:8080 on the Docker network - not to a public IP or a port on your router.

Can I access the bridge from my LAN?

Not by default. We do not expose the bridge HTTP API on your home network. Port 8080 is only reachable on the internal Docker network between modbus and cloudflared, and Check My Solar reaches it through the encrypted tunnel.

You can expose it on your LAN if you want to - for example, by adding a ports mapping to the modbus service in docker-compose.yml so other devices on your network can call GET /v1/realtime. That is optional and not required for Check My Solar.

Can I change the private hostname?

No. The hostname is generated when you create a bridge token and is tied to your Check My Solar account and that token’s tunnel configuration. It always ends in .modbus.internal.

Use the exact value shown in Menu → Account → Modbus Bridge for BRIDGE_HOSTNAME. If you revoke the token and create a new one, you get a new hostname and new secrets - update your Docker environment and restart the stack.

Why are there two containers instead of one?

They do different jobs:

  • modbus - inverter polling and the local HTTP API
  • cloudflared - secure outbound tunnel only; it does not talk to your inverter directly

Do I need to open ports on my router?

No. All traffic from Check My Solar to your bridge flows outbound from cloudflared through the tunnel. You do not need port forwarding, a static public IP, or inbound firewall rules for the bridge.

How fast is it?

End-to-end latency is typically about 100 ms from the inverter to your dashboard: Modbus read on your LAN → local bridge snapshot → Cloudflare Tunnel → Check My Solar backend → Today view. That is much faster than Fox Cloud live data, which can lag by up to several minutes.

While you are on Today with a healthy bridge, the app polls for new snapshots about every 10 seconds, so live power, battery, and grid numbers stay current without hammering your inverter or home network.

Why is the bridge read-only by default?

Read-only mode means the bridge only reads Modbus registers. Check My Solar gets faster live data without the bridge being able to change inverter settings. If you want to change work mode from the app, set MODBUS_READ_ONLY=false on the bridge container and restart - see Read-only mode and work mode control.


Linked from

Navigation

Type to search…

↑↓ navigate↵ selectEsc close