ESP32 behind CGNAT, 4G or Starlink
You set up port forwarding and dynamic DNS, it worked at home, and at the customer's site nothing answers. The usual reason is that the router there does not have a public address of its own.
What carrier-grade NAT is
There are not enough IPv4 addresses, so many providers put a whole street, or a whole mobile network, behind a few public ones. Your router gets a private address from the provider, and the provider's equipment does a second round of address translation. Mobile data, most 4G and 5G routers, Starlink's standard service and a growing number of cable and fibre connections work this way.
You can tell from the router's status page: if its WAN address starts with 100.64 to 100.127, 10., 172.16 to 172.31 or 192.168, or simply differs from what a "what is my IP" page shows, there is another NAT in front of it.
Why the usual tricks stop working
- Port forwarding opens a port on your router. The connection from outside never gets that far: it stops at the provider's NAT, where you cannot open anything.
- Dynamic DNS publishes the public address, which is shared by thousands of customers. The name resolves, and leads nowhere.
- UPnP asks your own router for a port, which is the wrong router.
- IPv6 would solve it where both ends have it. On the ESP32 it works, but you cannot count on the customer's network or your own having it.
What works: the device calls out
Every NAT, however many there are, lets connections out and carries the answers back. So the device opens a connection to a server with a public address and keeps it open, and everything you want to send to the device travels down that connection. Nothing on the customer's side needs configuring, which is the point: you rarely control that network, and often nobody on site can.
Two details decide whether it holds up:
- Idle connections are forgotten. A NAT drops a connection that has been silent for a while, sometimes after a minute or two on mobile networks, and neither end is told. The connection needs traffic at a short, regular interval, and each end must notice when the other has gone quiet and reconnect.
- The port matters. Restrictive networks allow web traffic out and little else. A device that connects to port 443 gets through where one on a custom port does not.
How Pinscope does it
The device keeps one connection open to port 443. The server pings it; the device reconnects by itself when it has heard nothing for 35 seconds, with a delay that grows if the server stays unreachable. Requests to the device's web server, its logs, its metrics and firmware updates all use that one connection, so the device needs to reach exactly one address:
connect.pinscope.dev TCP 443, outbound
It uses the Noise protocol instead of TLS, which keeps the component at 9.4 KB of RAM. One consequence to know about: a firewall that inspects traffic and allows only real TLS on port 443 will block it. Ordinary NAT, CGNAT and mobile networks do not.
On mobile data, watch the volume
A connection that is kept alive sends a little all the time: pings, and metrics once a minute. That part is small and steady. Log output is what adds up, since every line the firmware prints is sent. On a metered SIM, read the SIM's own counter after a day before you pick a data plan, log less, or switch log forwarding off in menuconfig and keep crash reports and metrics.
Try it
Three devices are free with every feature, 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
- TLS memory usage on the ESP32, and how to reduce it