Publish an ESP32 control UI over HTTPS
Make an ESP32 dashboard accessible through ClientProxy while keeping its frontend files off the device's tunnel connection. The proxy serves the React UI from App File Cache; a tunnel client on your LAN forwards small API requests to the ESP32.
Browser → HTTPS → ClientProxy → cached HTML, JavaScript, CSS
└→ tunnel client on your LAN → ESP32 HTTP API
This guide uses the esp32-hosted-ui example. Download or clone its source before following the commands. Its tunnel client runs on a NAS, Raspberry Pi, home server, or computer that can reach the device. Keep that machine powered on while using the public dashboard.
What you need
- A board supported by the example: ESP32-S3, ESP32 DevKit, or ESP32-C3.
- PlatformIO, a USB connection to the board, and Wi-Fi.
- The
esp32-hosted-uiexample source, including its firmware,ui/, and packaging script. - Node.js 20 or newer, npm,
zip, andunzip. - A ClientProxy tunnel with an eligible assigned product and App File Cache access.
- The tunnel-client on a machine on the ESP32's LAN.
1. Flash and provision the device
In the example project's root directory, flash the environment for your board:
pio run -e esp32-s3 --target upload
pio device monitor -b 115200
For an ESP32 DevKit use -e esp32dev; for an ESP32-C3 use -e esp32c3.
On first boot:
- Join the device's
esp32-hosted-ui-XXXXXXWi-Fi network. - Open the captive portal, or browse to
http://192.168.4.1. - Enter your Wi-Fi credentials and a unique device admin password of 8–64 characters.
- After restart, read the device's LAN address from the serial monitor, for example
192.168.1.42.
The device admin password protects its APIs. It is separate from your ClientProxy API Key. Do not put either secret in the frontend source or cache ZIP.
2. Check LAN access
From the machine that will run the tunnel client, open:
curl -i http://192.168.1.42:80/
Replace the example IP with your device's address. You should receive the example's explanation page. Reserve the ESP32's address in your router's DHCP settings so it stays stable.
3. Configure the tunnel
In the ClientProxy dashboard, create a tunnel, select a proxy server, and assign an eligible product. Add a domain mapping:
- Select Auto-generate default domain for your first test.
- Set Local IP:port to the ESP32's LAN address, for example
192.168.1.42:80. - Copy the generated domain, Tunnel ID, and API Key.
Start the client on the LAN machine:
tunnel-client \
--api-url https://api-eu.clientproxy.io/api \
--tunnel-id YOUR_TUNNEL_ID \
--api-key YOUR_API_KEY
Choose api-us, api-eu, or api-asia for your setup. Keep TLS enabled for this desktop/NAS client.
Use the ESP32's LAN IP in the mapping. 127.0.0.1 would point to the machine running the tunnel client. At this stage, the public HTTPS domain should show the same explanation page as the local HTTP address.
4. Build and package the React UI
From the example project's root:
cd ui
npm ci
npm run build
cd ..
bash scripts/package-ui.sh
The script creates dist/esp32-hosted-ui.zip. It puts index.html at the archive root alongside assets/, omits source maps, and checks the 10 MB upload limit.
If packaging manually, ZIP the contents of ui/dist/, not the enclosing directory. The proxy must find index.html directly at the archive root.
5. Upload and link App File Cache
- Open App File Cache in the dashboard and create an entry, such as
ESP32 UI v1. - Choose Upload Zip and upload
dist/esp32-hosted-ui.zip. - Open your tunnel's Domains and edit the mapping to the ESP32.
- In Link cache file, select the new cache entry and save.
- Reconnect the tunnel client so the proxy fetches the archive.
Open https://YOUR_GENERATED_DOMAIN. You should see the React dashboard. Enter the device admin password when the UI requests it.
HTML, CSS, and JavaScript come from the proxy. The example's extensionless /api/* routes go through the tunnel to the ESP32.
6. Verify that the UI controls the real device
- Open Overview and check the live uptime and LAN IP.
- Change the device name in Settings, save, and refresh to confirm it persisted.
- In browser developer tools, inspect
/api/statusand confirm it returns live device data. - Temporarily disconnect the ESP32. The cached frontend should still load, while API requests fail. Reconnect it and verify recovery.
The example also provides firmware updates. Use firmware built for the exact board and keep the page open until the update finishes. The UI sends firmware in sequential 12 KB multipart requests.
Updating the frontend
Build and package the new UI, create a new cache entry, upload the ZIP, link the new entry to the domain, and reconnect the tunnel client.
The proxy may retain an existing cache ID's files across reconnects. A new cache entry avoids continuing to serve the old frontend after overwriting an archive.
Troubleshooting
| Problem | What to check |
|---|---|
| Device does not join Wi-Fi | Check provisioning; the supplied GPIO0 configuration supports holding BOOT for three seconds at boot to reset it |
| UI loads, but APIs fail | Check device power, LAN IP, tunnel client, and the domain's backend mapping |
| API returns an authentication error | Use the device admin password, not the tunnel API Key |
| Browser shows the explanation page | Confirm the cache is linked, the ZIP has a root index.html, and the client reconnected |
| Browser shows the previous UI | Create and link a new cache entry, reconnect, then refresh |
| UI works locally but shows sample values | The local Vite preview uses sample data; verify live /api/status on the public domain |
For a different design where the tunnel firmware itself runs on the ESP32, use the separate proxy-esp32 project. That requires its own walkthrough and has different memory, concurrency, and throughput limits.