A charger won't connect to SteVe
A charger that refuses to connect to SteVe is the most common first-day failure. This runbook walks you through the layered checks — network reachability, OCPP handshake, ChargeBox registration, and authentication — so you can find the failure and recover.
When to run this
Section titled “When to run this”- A new charger has been pointed at SteVe but never appears as connected in the admin UI.
- A previously working charger has gone offline and isn’t reconnecting.
- You’ve just migrated SteVe to a new host or changed its public URL.
Before you start
Section titled “Before you start”Have these on hand:
- The charger’s
chargeBoxId(its OCPP identity string). - The SteVe public WebSocket URL — typically
ws://<host>:8180/steve/websocket/CentralSystemService/<chargeBoxId>orwss://...if you’re terminating TLS in front of SteVe. - Shell access to the host running SteVe (
docker composeavailable). - The
AUTH_PASSWORDfor the SteVe admin UI.
Procedure
Section titled “Procedure”1. Confirm SteVe is actually up
Section titled “1. Confirm SteVe is actually up”docker compose psdocker compose logs app --tail=200You’re looking for a clean Jetty start after Flyway migrations. If the container is restarting, this is not a charger problem — fix SteVe first. Boot takes around 30 seconds; Flyway runs before Jetty binds the port.
Then verify the admin UI responds:
curl -i -u admin:$AUTH_PASSWORD http://localhost:8180/steve/manager/homeA 200 OK means SteVe itself is healthy.
2. Confirm the ChargeBox is registered
Section titled “2. Confirm the ChargeBox is registered”SteVe rejects unknown chargers by default. Open the admin UI at
/steve/manager/chargepoints and confirm the chargeBoxId is
present. If it isn’t, add it — the identity string in SteVe must
match exactly what the charger sends in its WebSocket URL path
(case-sensitive).
3. Confirm the charger can reach SteVe
Section titled “3. Confirm the charger can reach SteVe”From the network the charger sits on (or as close to it as you can get), test the WebSocket endpoint:
curl -i \ -H "Connection: Upgrade" \ -H "Upgrade: websocket" \ -H "Sec-WebSocket-Version: 13" \ -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \ -H "Sec-WebSocket-Protocol: ocpp1.6" \ http://<steve-host>:8180/steve/websocket/CentralSystemService/<chargeBoxId>You want a 101 Switching Protocols response. Other outcomes:
- Connection refused / timeout — firewall, NAT, or wrong host. Port 8180 (or whatever you’ve published) must be reachable from the charger’s network.
404 Not Found— thechargeBoxIdin the URL doesn’t match a registered ChargeBox. Re-check step 2.400 Bad Requestwith no subprotocol — the charger is negotiating an OCPP version SteVe doesn’t accept. Confirm the charger is configured forocpp1.6(orocpp1.5/ocpp2.0.1if you’ve explicitly enabled them).
4. Watch the OCPP handshake live
Section titled “4. Watch the OCPP handshake live”In one terminal:
docker compose logs app -f | grep -i "<chargeBoxId>"Then power-cycle the charger or trigger a reconnect. You should see
a BootNotification arrive within 30 seconds. If you see the
WebSocket open and then immediately close, the charger is likely
rejecting SteVe’s BootNotification response — check that SteVe’s
clock is correct (date on the host); chargers will refuse a
response timestamp that’s wildly skewed.
5. If you front SteVe with a reverse proxy
Section titled “5. If you front SteVe with a reverse proxy”WebSocket upgrades require explicit proxy configuration. The proxy must:
- Forward the
UpgradeandConnectionheaders. - Not buffer the response.
- Allow long-lived connections (idle timeout well above 60 seconds — OCPP heartbeats default to longer intervals).
For nginx:
location /steve/websocket/ { proxy_pass http://steve-app:8180; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; proxy_send_timeout 3600s;}If you’ve moved from ws:// to wss://, also confirm the
charger’s CA trust store includes whatever certificate authority
signed your TLS cert. Many chargers ship with a limited CA bundle
and will silently reject Let’s Encrypt-signed certs until firmware
is updated.
6. Confirm the WebAPI token if ExpresSync is involved
Section titled “6. Confirm the WebAPI token if ExpresSync is involved”If the charger appears connected in SteVe but ExpresSync can’t see it, the bridge between ExpresSync and SteVe’s REST API is the problem, not the charger.
curl -i -H "Authorization: Bearer $WEBAPI_VALUE" \ http://<steve-host>:8180/steve/api/v1/chargepointsA 401 means the WEBAPI_VALUE on the ExpresSync side doesn’t
match the one SteVe was started with. Both AUTH_PASSWORD and
WEBAPI_VALUE are injected at JVM startup as -D overrides from
docker-compose.app.env — if you changed them, SteVe needs a
restart.
Audit and rollback
Section titled “Audit and rollback”None of the checks above are destructive. The only state-changing action is adding a ChargeBox in step 2; you can remove it again from the same admin UI page if it was added in error.
If you restart SteVe to pick up a changed docker-compose.app.env:
docker compose up -d appActive charging sessions will reconnect once the container is back — chargers retry indefinitely. There is no data loss on a clean restart; session state lives in MariaDB.
Still stuck?
Section titled “Still stuck?”Capture the following before asking for help:
docker compose logs app --tail=500from a window covering at least one full reconnect attempt.- The exact WebSocket URL configured on the charger.
- The
chargeBoxIdas it appears in the SteVe admin UI (copy-paste, don’t retype). - Output of the
curlupgrade probe from step 3.