"Failed to create socket" on ESP32: You Have Run Out of LWIP Sockets
A voice assistant that went silent after a firmware update turned out to be an exhausted LWIP socket pool. How to recognise it, why the symptom misleads, and the one Kconfig value to change.
The bug report from the field was: “After I use the intercom, the voice assistant stops working. It just hangs.”
That sentence contains a wrong assumption, and the wrong assumption cost a full round of debugging in the wrong place. This article is about the real cause, but it is also about why the reported symptom pointed away from it.
Symptoms
- The wake word still triggered. The assistant would visibly enter its listening state.
- The speaker produced nothing. No TTS audio ever came back.
- The state machine never returned to idle. Every subsequent request, including the intercom, was rejected with
talk_busy. - Serial log, easy to miss among everything else:
E EspUdp: Failed to create socket
Root cause
Each voice conversation in this firmware opens a temporary UDP socket to the speech server for the audio stream. It is created when the session starts and closed when it ends.
The project’s sdkconfig had:
CONFIG_LWIP_MAX_SOCKETS=10
Over the previous few releases the number of long-lived sockets had crept up to nine or ten:
| Persistent socket | Count |
|---|---|
| MQTT over TLS (two brokers) | 2 |
| MJPEG streaming server | 1-2 |
| H.264 streaming server | 1-2 |
| Joystick / IMU control channel, UDP 8208 | 1 |
| Intercom audio, UDP 8209 | 1 |
| Assorted listeners | 1-2 |
When the pool is full, the voice session cannot open its audio socket. The request to the server still goes out over the existing MQTT/WebSocket path, so the wake-up “works”. But the reply audio has nowhere to arrive, the session never sees an end-of-stream, and the state machine waits forever.
Fix: raise the pool.
CONFIG_LWIP_MAX_SOCKETS=16
The maximum for LWIP in ESP-IDF is 16 on most targets. Each socket costs a small amount of RAM whether or not it is in use, which is why the default is conservative.
Why the report was misleading
The release that added the intercom socket (v2.2.18) was the last straw that filled the pool. It also happened to be the release the user first tried the intercom on. So from their point of view: used intercom → assistant broke.
The truth was: upgraded to v2.2.18 → assistant broke, and it would have broken without touching the intercom at all.
Because of that framing, the previous debugging round went after a genuine but unrelated hazard — a race condition in how the microphone was handed between the intercom and the wake-word engine. That race was real and got fixed (strict serial handshake, talk_busy guard). It was not the cause of this bug.
Lessons
- Before adding any persistent socket, count the ones you already have. Write the list down; it takes two minutes.
- Read the serial log even when the symptom is “obviously” somewhere else. Nothing about “the speaker is silent and the state machine is stuck” suggests sockets. The log line did.
- Apply the same audit to every fixed-size resource pool. On the ESP32 that includes LEDC channels (8), PCNT units (4 on most targets), RMT channels, hardware timers, and I²C/SPI hosts. Running out of LEDC channels for motor PWM is what pushed my servo onto MCPWM — same category of problem, different pool.
- Correlation in time is not causation. “It broke when I used X” is evidence about when, not about why.