How to Fix ESP32 esp_mqtt_client_start Failing with Error create mqtt task

Problem

An ESP32 using esp_mqtt_client logs this once at boot and then never connects to the broker:

serial.log
E (4521) mqtt_client: Error create mqtt task
E (4521) APP: Failed to start MQTT client: ESP_FAIL

The device keeps running otherwise — Ethernet/WiFi is up, HTTP works — but MQTT stays dead forever, until the next reboot. Retrying esp_mqtt_client_start() later often keeps failing as well.

Cause

esp_mqtt_client_start() creates a dedicated FreeRTOS task for the MQTT event loop. That allocation needs a contiguous block of internal (non-PSRAM) RAM for the task stack — typically ~6 KB.

Two things commonly go wrong:

  1. The start is attempted exactly once, typically inside the IP_EVENT_ETH_GOT_IP (or WiFi GOT_IP) event handler. If task creation fails transiently at that moment — e.g. because other startup tasks temporarily hold memory — there is no retry, ever.
  2. Late start attempts keep failing because the internal heap fragments quickly after boot. 60 seconds in, the largest contiguous internal block can be a few hundred bytes even though tens of KB are “free”. Task stacks cannot come from PSRAM, so fragmented internal heap = no new tasks.

Solution

Two complementary fixes:

1. Start the MQTT client as early as possible

Don’t wait for the IP event. Create and start the client during application init, before the heap gets fragmented. The MQTT task will simply wait/reconnect until the network is actually up — esp_mqtt_client handles that gracefully.

mqtt_init.cpp
// Call early in app init, NOT from the GOT_IP event handler
esp_mqtt_client_config_t cfg = {};
cfg.broker.address.uri = broker_uri;
client = esp_mqtt_client_init(&cfg);
esp_mqtt_client_register_event(client, MQTT_EVENT_ANY, mqtt_event_handler, nullptr);
esp_err_t err = esp_mqtt_client_start(client);
if (err != ESP_OK) {
    ESP_LOGE(TAG, "MQTT start failed: %s — supervisor will retry", esp_err_to_name(err));
}

2. Add a supervisor task that retries periodically

A small task checks every ~20 s whether the client exists and was started, and retries if not:

mqtt_supervisor.cpp
static void mqtt_supervisor_task(void *) {
    for (;;) {
        vTaskDelay(pdMS_TO_TICKS(20000));
        if (client && !client_started) {
            esp_err_t err = esp_mqtt_client_start(client);
            ESP_LOGI(TAG, "MQTT supervisor retry: %s", esp_err_to_name(err));
            if (err == ESP_OK) client_started = true;
        } else if (!client) {
            client = esp_mqtt_client_init(&cfg); // recreate
            if (client && esp_mqtt_client_start(client) == ESP_OK)
                client_started = true;
        }
    }
}

Also log the largest allocatable internal block on failure — this immediately reveals fragmentation:

diag.cpp
ESP_LOGW(TAG, "free internal: %u, largest block: %u",
         heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
         heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL));

If largest block is in the hundreds of bytes while free is tens of KB, it’s fragmentation — and the only reliable remedy is starting tasks earlier (fix #1) or rebooting.

Result

With early start + supervisor retry, a transient task-creation failure at boot no longer bricks MQTT until the next power cycle — the client comes up on the first or second retry, and network-side outages are handled by esp_mqtt_client’s built-in reconnect.


Check out similar posts by category: ESP32 Embedded