ESP32 TLS memory usage, and how to reduce it
One TLS connection is usually the largest single consumer of heap in ESP32 firmware. Espressif's own figure is about 35 KB. With the right options the same connection idles at about 24 KB, and these are the options.
Where the memory goes
mbedTLS keeps one buffer for records coming in and one for records going out. By default the incoming one is 16 KB, because a server is allowed to send records that large, and the outgoing one is 4 KB. On top of that come the handshake state, the certificate chain being checked, and the session itself. The handshake is the peak: for a second or two the device needs noticeably more than it does afterwards.
The options that matter
| Option | What it does | What it costs |
|---|---|---|
CONFIG_MBEDTLS_DYNAMIC_BUFFER | Allocates the record buffers when a record is being handled and frees them after, and releases handshake data once the session is up | More allocations, so more fragmentation; the peak during the handshake stays |
CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA, ..._FREE_CA_CERT | Frees the configuration and the CA certificate after the handshake | The connection cannot renegotiate or reconnect without setting them up again |
CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN with a smaller SSL_IN_CONTENT_LEN | Shrinks the incoming buffer below 16 KB | Only safe when you control the server and it sends small records. A record larger than the buffer ends the connection |
| The certificate bundle with a short list | Flash, mostly: the full bundle is large | You must know which authorities your server will ever use |
What that gets you, measured
The same application on an ESP32-S2 with ESP-IDF 5.5.3, holding one WebSocket over TLS open, with CONFIG_MBEDTLS_DYNAMIC_BUFFER on:
| Bytes | |
|---|---|
| Heap in use while connected and idle | 23,632 |
| Further dip while data flows | about 6,000 |
| Flash added by TLS | about 155,000 |
So about 24 KB idle and 30 KB under traffic, measured as the free heap of the same firmware built with and without the connection. That is the cost after tuning, not before.
Things that do not help as much as they sound
- A smaller incoming buffer against a server you do not control. A CDN or a cloud load balancer sends 16 KB records whenever it likes. The connection then fails at random, in the field.
- Closing the connection between uses. You get the memory back and pay for a handshake each time, in seconds of CPU and in the peak allocation, and the device cannot be reached in between.
- Hardware acceleration. It makes the handshake faster. It does not make the buffers smaller.
When the device talks to one server only
Most of what TLS carries is there so that any client can talk to any server. A device with one server compiled into it needs less: proof that it reached that server, and encryption. The Noise protocol gives both with one known public key, no certificates and no 16 KB buffer.
Pinscope's device component was moved from TLS to Noise for this reason. The same application then needed 9,584 bytes in total, with every buffer fixed, instead of 23,632. The full account, with what the choice costs.
That is not an argument against TLS in your own firmware: if you call an HTTPS API you do not run, you need TLS, and the options above are how to make it fit.
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
- An ESP32 behind CGNAT, 4G or Starlink