Sharing an ESP32 sensor page with anyone
An ESP32 with a sensor and a small web page is an afternoon of work. Letting people outside your WiFi open that page, and letting many of them open it at once, is the part that takes longer.
Why it is harder than it looks
Two things stand in the way. The first is the network: the page lives at an address like 192.168.1.40, which means nothing outside your home, and your router does not let anyone in. The second is the ESP32 itself: it can hold only a handful of connections at once (the stock lwIP configuration allows ten sockets in total), and the Arduino WebServer answers one request at a time. A page that works for you on the sofa falls over when a hundred people open it in the same minute.
The usual ways
- Port forwarding. Open a port on the router and point it at the ESP32. It needs access to the router, an address that does not change, and a provider that gives you a public one (many do not, see CGNAT). Every visitor then reaches the ESP32 directly, without encryption, and the connection limit above is yours to hit.
- Send the readings to a cloud dashboard. The device posts its values to a service that draws the charts and serves the page. This scales well, and for plain numbers it is a good answer. It is not your page any more: the layout, the buttons and anything else the ESP32 serves have to be rebuilt in that service.
- An outbound tunnel. The device keeps one connection open to a server, and the server gives the device's own page a public HTTPS address. Nothing changes on the router, and the page you wrote is the page people see. The rest of this guide is about this way, with Pinscope.
The page
Split the page from the data. The HTML is sent once; after that the browser asks for a small JSON answer every few seconds and redraws. On an Arduino sketch with WebServer:
server.on("/", [] { server.send_P(200, "text/html", PAGE); });
server.on("/data.json", [] {
String json = "{\"value\":" + String(readSensor(), 2) + "}";
server.send(200, "application/json", json);
});
and in the page:
async function update() {
const d = await (await fetch('data.json')).json();
document.getElementById('value').textContent = d.value.toFixed(1);
}
update();
setInterval(update, 5000);
The PublicSensor example that comes with the Pinscope Arduino library (File → Examples → Pinscope → PublicSensor) is this, complete: the last ten minutes of readings, a small chart, a message when the device stops answering, and the chip's own temperature as the sensor, so it runs with nothing connected. Replace readSensor() with yours.
Giving it an address
Add the project key from the dashboard and one call in setup():
pinscope_config_t config = {};
config.project_key = "psk_...";
pinscope_start(&config);
The device's page is then at an HTTPS address of its own, behind your login. To let others in:
- Share creates a link that works without an account for one hour, 24 hours or 7 days, on every plan, including the free one.
- Make public, on paid plans, opens the page to everyone with no expiry, and Choose address gives it a name you choose, like
https://garden.pinscope.dev.
When many people open it
On a public page or a share link, the ESP32 does not answer each visitor. Pinscope keeps the last answer to each address for two seconds and gives it to everyone who asks in that time, so the device sees about one request for data.json every two seconds, whether ten people are watching or a thousand. If the device is slow to answer, visitors get the last good answer, up to a minute old, instead of an error.
In a test, 1,000 visitors opened a share link to an ESP32-S2 within ten seconds, each loading the page on its own connection, and every one of them got it.
For that to work, the page has to let its answers be kept:
- Poll with plain requests, as above. A WebSocket from a visitor goes to the device itself, and only one visitor at a time can hold one.
- Keep answers small. A JSON of a few hundred bytes is ideal; anything over 512 KB is not kept.
- Do not send
Cache-Control: no-storeorprivate, and do not set cookies on what visitors read. Such answers are not kept, and every visitor goes to the device.
Before you share the link
- Visitors can press the buttons on the page too. If some of them should not, refuse those requests for share links with a request rule; it applies to the public page as well.
- Only traffic between the device and Pinscope counts toward the monthly limit. Answers given from the kept copy do not.
- If the device goes offline, visitors see that it is not answering. The example's page says so in plain words instead of showing a broken chart.
Try it
Three devices are free, and no card is needed. Create an account, or read the five-minute guide first.
More
- Remote access to an ESP32 without TLS: what it measured and what it costs
- Reaching an ESP32 web server from outside its network
- Updating ESP32 firmware over the internet, with rollback
- Crash backtraces from devices in the field
- Remote logging: ESP_LOG without a serial cable
- Heap and stack monitoring in the field
- An ESP32 behind CGNAT, 4G or Starlink
- TLS memory usage on the ESP32, and how to reduce it
- ArduinoOTA over the internet, with a rollback
- ESP32 remote access compared: port forwarding, ngrok, Cloudflare Tunnel, Tailscale
- esp_http_server slow? The 250 ms delay and the fix
- Before the first ESP32 ships: a field support checklist