Menu

ESP32 Watchdog Timer: Why Your Device Keeps Resetting

Task Watchdog, Interrupt Watchdog, and RTC Watchdog Explained

July 6, 2026 by Alessandro Colucci
ESP32 Watchdog Timer illustration

The error that brought you here

If you searched for this, you've probably already seen one of these in your serial monitor:

E (5231) task_wdt: Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:
E (5231) task_wdt: - loopTask (CPU 1)
E (5231) task_wdt: Tasks currently running:
E (5231) task_wdt: CPU 0: IDLE0
E (5231) task_wdt: CPU 1: loopTask
E (5231) task_wdt: Aborting.

Or worse: no error at all, just a silent reset in the field, reported by a customer who says the device "randomly restarts" every few hours. This article covers both — how the watchdog actually works on ESP32, the three mistakes that cause most unwanted resets, and what a production firmware should actually do when it fires.

There isn't just one watchdog on ESP32

People say "the watchdog" like it's one thing. On ESP32 there are three, and mixing them up is the first source of confusion:

  • Task Watchdog Timer (TWDT) — software watchdog managed by FreeRTOS. It tracks specific tasks (by default, the idle tasks on each core; in Arduino, loopTask too) and expects each to "check in" periodically. This is the one behind the error above.
  • Interrupt Watchdog Timer (IWDT) — a hardware-backed timer that specifically detects when interrupts are disabled for too long (e.g. a long portDISABLE_INTERRUPTS() section, or a task hogging a core without yielding). It fires with a "Guru Meditation Error: IWDT" panic, not the task_wdt message above.
  • RTC Watchdog — the lowest-level hardware watchdog, independent of FreeRTOS entirely. It's what recovers the chip if the software stack itself is completely wedged (e.g. stuck in a hard fault loop before FreeRTOS could even schedule a recovery).

For 90% of the "my ESP32 keeps resetting" cases in production firmware, you're dealing with the Task Watchdog — that's the focus of the rest of this article.

Enabling and configuring the Task Watchdog properly

In ESP-IDF, the task watchdog is configured via esp_task_wdt_config_t and initialized once:

#include "esp_task_wdt.h"

esp_task_wdt_config_t twdt_config = {
    .timeout_ms = 5000,
    .idle_core_mask = (1 << 0) | (1 << 1), // watch both idle tasks
    .trigger_panic = true,
};
esp_task_wdt_init(&twdt_config);
esp_task_wdt_add(NULL); // subscribe the current task

Every task you subscribe with esp_task_wdt_add() must call esp_task_wdt_reset() before the timeout elapses, or it gets flagged. On the Arduino-ESP32 core, the loop task is subscribed automatically with a default timeout (historically 5s, but check your core version — this changed across releases), which is why plain Arduino sketches rarely see this error until they add something blocking.

Mistake #1: blocking too long without feeding it

This is the single most common cause. Any of these can block long enough to starve the watchdog:

  • A delay() or blocking sensor read longer than the timeout, inside a task that's subscribed to the watchdog
  • Writing to flash (NVS commit, OTA write, SPIFFS/LittleFS operations) — these are slower than people expect, especially on a busy flash bus shared with WiFi
  • A blocking network call (DNS resolution, TCP connect with no timeout) inside the main loop
  • A while(1) polling loop waiting on a condition that never yields to the scheduler

The fix isn't always "feed it more often" — often the real fix is moving the blocking work off the watched task entirely (a dedicated FreeRTOS task not subscribed to the watchdog, or an async/callback-based API instead of a blocking one).

Mistake #2: subscribing the wrong task, or forgetting a core

If you manually configure idle_core_mask and only watch core 0 while your blocking code runs on core 1 (or vice versa, depending on how you pinned tasks with xTaskCreatePinnedToCore), the watchdog simply never sees the problem — until it does, in a different, harder-to-reproduce way. Match your watchdog configuration to your actual task-to-core assignment, not to a copy-pasted default.

Mistake #3: timeout too short for legitimate slow operations

OTA writes, large NVS commits, and TLS handshakes are all legitimately slow — sometimes slower than a conservative default timeout. Rather than disabling the watchdog for these (which defeats its purpose), the usual production pattern is to call esp_task_wdt_reset() at safe checkpoints inside the slow operation itself, or to move it to an unwatched task if it's rare and well-isolated.

What to actually do when it fires

Preventing resets is only half the job — a device that ships to a customer needs to survive one gracefully and tell you why. On boot, check the reset reason:

#include "esp_system.h"

esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_TASK_WDT || reason == ESP_RST_INT_WDT || reason == ESP_RST_WDT) {
    // Log it, to Serial, to NVS for later retrieval, or straight to your
    // backend if you have connectivity.
}

And make sure a repeated watchdog trigger can't turn into an infinite reset loop — if your device watchdog-resets, reconnects to WiFi, and immediately re-triggers the same blocking call that caused the first reset, you've built a brick that resets every 5 seconds forever. A simple guard (count resets in a short window via RTC memory, fall back to a safe/degraded mode after N) avoids this.

Production checklist

  • Know which of the three watchdogs you're actually looking at (task_wdt message vs Guru Meditation IWDT vs a hard hardware reset with no log at all)
  • idle_core_mask matches your real task-to-core pinning
  • Every subscribed task either checks in well within the timeout, or isn't subscribed at all
  • Slow-but-legitimate operations (OTA, NVS, TLS) have a plan — checkpoint resets or isolation, not a blanket disable
  • Reset reason is logged somewhere you can actually retrieve after the device is in the field
  • A reset-loop guard exists so a recurring trigger degrades gracefully instead of resetting forever

None of this is exotic — it's the kind of boilerplate every production ESP32 firmware ends up needing, and every project reinvents it slightly differently. If you'd rather specify the behavior and get working code instead of wiring this up by hand again, that's exactly what PleaseDontCode is for.

Back to Blog Build your first device

Want to build this project?

You just read about ESP32 Watchdog Timer: Why Your Device Keeps Resetting.
What if you could turn it into a working project in minutes?

With PleaseDontCode, describe in plain language what your device should do: the AI generates complete firmware, ready to flash on 100+ ESP32 and compatible boards.

Pick the sensors, actuators, and communication protocols you need — we handle the code. No programming experience required.

Build your first device Free to start, no credit card required
Chat with us on WhatsApp