Have a project in mind? Let's talk.

The ESP32 watchdog timer is not one timer but three, and confusing them is the reason so many projects either reset in the field for no obvious reason or ship with the watchdog effectively off. Espressif's watchdog documentation splits them into the Interrupt Watchdog Timer (IWDT), the Task Watchdog Timer (TWDT), and the RTC Watchdog Timer (RTC_WDT), each guarding a different failure mode. This post explains what each one actually watches, walks through the TWDT API that most application code uses, and then covers the errors people actually hit: the "Task watchdog got triggered" dump, interrupt watchdog panics, watchdogs that vanish under JTAG, and how to configure all of this for production devices rather than bench prototypes. Source
The hardware gives you two groups of watchdog timers. The Main System Watchdog Timer (MWDT_WDT) sits in the timer groups and backs both the IWDT and the TWDT. The RTC Watchdog Timer is a separate beast that tracks boot time from power-up until your main function runs, and by default it is disabled right before your code starts. Source
The Interrupt Watchdog Timer uses the MWDT in Timer Group 1 and is fed by the FreeRTOS tick interrupt on each CPU. Its job is narrow: detect when interrupt service routines are blocked for too long. If the tick interrupt on a CPU cannot run within the timeout, something is hogging the CPU at interrupt level, usually a long critical section or an ISR doing work it has no business doing. The default response is the panic handler printing "Interrupt wdt timeout on CPU0" or CPU1, after which you either debug through the backtrace or let the chip reset, which is usually what you want in production. The IWDT is enabled by default through CONFIG_ESP_INT_WDT, with the timeout set by CONFIG_ESP_INT_WDT_TIMEOUT_MS. Source
The Task Watchdog Timer uses the MWDT in Timer Group 0 and watches tasks instead of interrupts. By default it subscribes the idle task of each CPU, which is a clever trick: if the idle task cannot run, some task has been hogging the CPU without yielding for the whole timeout period. That is the classic signature of a spin loop on a peripheral or an accidental infinite loop. Any task can also be subscribed individually, which is where the API comes in below. One behavior surprises people: on a TWDT timeout the default action is only to print a warning and a backtrace, then keep running. If you want a timeout to panic and reset the chip, you must set CONFIG_ESP_TASK_WDT_PANIC. Source
The RTC watchdog covers the boot window before your application takes over. If you want it to keep guarding the device after startup, set CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE and your app must explicitly feed it with rtc_wdt_feed() or disable it with rtc_wdt_disable(); otherwise the chip resets itself. Most projects leave the default behavior alone, but for unattended field devices, keeping a hardware-level guard over early startup code is worth the extra few lines. Source
The TWDT is the watchdog you will actually touch in code. The flow is: initialize once with esp_task_wdt_init(), subscribe any task you want watched with esp_task_wdt_add(), call esp_task_wdt_reset() periodically from inside that task to feed the watchdog, and unsubscribe with esp_task_wdt_delete() if the task exits. Note the signature in current ESP-IDF: esp_task_wdt_init() takes a pointer to an esp_task_wdt_config_t struct rather than the old timeout-and-panic arguments, so code copied from older tutorials will not compile against v5 and later without adjustment. Source
Two details make the API more useful than it first looks. First, you can supervise something finer than a task: esp_task_wdt_add_user() creates a named user handle, and esp_task_wdt_reset_user() feeds the watchdog for that handle. That lets you assert that a specific code path, say a sensor read or a network reconnect, actually executes within the timeout, even if the task around it is alive and looping. Second, you can define esp_task_wdt_isr_user_handler() in your own code to receive the timeout event and extend the default behavior, for example to snapshot state before the reset. The official system/task_watchdog example in the esp-idf repository demonstrates subscribe, feed and unsubscribe for both tasks and users, and it is the right starting point to copy from. Source Source
Timeout selection matters more than the API. The documentation's rule of thumb: set CONFIG_ESP_TASK_WDT_TIMEOUT_S to at least as long as you expect any single task to legitimately monopolize the CPU. Five seconds is a common default, but the right number comes from your worst-case blocking operation, not from a tutorial. Source
This is the error that brings most people to this topic. The full message lists the tasks that did not reset the watchdog in time, and it almost always means one of three things. The most common is a task that never yields: a while loop polling a register, waiting on a flag, or bit-banging a peripheral without any blocking call, so the idle task (or the subscribed task itself) starves past the timeout. The fix is to let the task block, for example on a notification, queue or delay, rather than spin. Source
The second cause is a legitimately long operation. Espressif calls out erasing large flash areas, which can take long enough to trip the TWDT even though nothing is wrong. The documented fixes are to raise CONFIG_ESP_TASK_WDT_TIMEOUT_S in menuconfig, or to call esp_task_wdt_init() again with a longer timeout before the erase and restore it after. The same pattern applies to any known long operation, such as a calibration routine or a filesystem format. Source
The third cause is not a TWDT issue at all but an IWDT one, and the panic line tells you which one fired: "Interrupt wdt timeout" means interrupt-level code blocked the tick, while the task watchdog message names the starved tasks. For the interrupt case, the offending pattern is a long critical section or an ISR that does real work; move the work into a task and keep ISRs short. Only if the work genuinely cannot be shortened should you raise CONFIG_ESP_INT_WDT_TIMEOUT_MS. Source
One gotcha wastes hours: while debugging with OpenOCD, the CPUs halt at every breakpoint, but if the watchdogs kept running they would reset the chip mid-debug. So OpenOCD disables the interrupt and task watchdog hardware timers at every breakpoint and does not re-enable them afterward. The practical consequence is that watchdog functionality is essentially off whenever the chip is attached over JTAG: no warnings, no panics, no resets. A task-starvation bug that resets the device in the field will simply not reproduce on your bench debugger. If you are chasing a suspected watchdog issue, debug with logging on a free-running board, not with breakpoints. Source
For a device that ships, a few deliberate choices turn the watchdog from a nuisance into a safety net. Decide the failure policy first: the TWDT's default warn-and-continue is right for development, but in production you usually want CONFIG_ESP_TASK_WDT_PANIC set so a hung task resets the chip into a clean state, and the panic handler's behavior (debug output versus immediate reset) is controlled by CONFIG_ESP_SYSTEM_PANIC. Set timeouts from measured worst-case blocking, not defaults. Subscribe the tasks that matter, not every task, because a feed call scattered through code nobody owns becomes cargo cult. And review the configuration whenever you add features with long blocking operations: the flash-erase case applies directly to OTA updates, which is why our guides on ESP32 OTA rollback and deep sleep power consumption pair naturally with this one. A device like our ESP32 AI voice agent build, which streams audio and holds network connections open, is exactly the kind of workload where a subscribed network task with a sane timeout catches a hung reconnect loop before the user notices. Source
It is a set of hardware timers that reset or warn when the firmware stops making progress. The ESP32 has three: the Interrupt Watchdog (IWDT), which detects blocked interrupt service routines; the Task Watchdog (TWDT), which detects tasks that run without yielding; and the RTC watchdog, which guards the boot window before your main function runs.
A task watched by the TWDT did not feed the watchdog within the timeout. By default the TWDT watches the per-CPU idle tasks, so the usual meaning is that some task hogged the CPU without yielding, typically a spin loop on a peripheral or an infinite loop. The message lists the tasks that failed to reset the watchdog, which tells you where to look.
The IWDT watches interrupt-level execution: it is fed by the FreeRTOS tick interrupt, and it fires when ISRs or critical sections block that tick for too long, printing "Interrupt wdt timeout on CPU0" or CPU1. The TWDT watches task-level progress: it fires when a watched task fails to call its feed function in time, which usually means another task never let it run. Different hardware timers, different failure modes, different error messages.
Subscribe the task with esp_task_wdt_add() after esp_task_wdt_init(), then call esp_task_wdt_reset() periodically from inside that task, at a rate well within the configured timeout. To supervise a specific code path rather than a whole task, use esp_task_wdt_add_user() and esp_task_wdt_reset_user() with the returned user handle.
Almost never. The resets are the symptom; the cause is a task that starves others or an ISR that blocks. Fix the cause by making tasks block properly and keeping ISRs short, or raise the timeout for documented long operations like large flash erases. Disabling the watchdog removes the only automatic recovery you have when a field device hangs at 3 a.m.
Because OpenOCD disables the interrupt and task watchdog timers at every breakpoint and does not re-enable them, so watchdogs are effectively off during JTAG debugging. This is deliberate, since a halted CPU would otherwise trigger constant resets. Reproduce watchdog issues with logging on a free-running board instead.
Espressif's guidance is to set it at least as long as the longest time any single task may legitimately monopolize the CPU. Measure your worst case, such as a flash erase, sensor calibration or network reconnect, and set the timeout above it with margin. Blindly copying a five-second default either resets healthy devices or reacts too slowly to real hangs.
Ready to take the first step towards unlocking opportunities, realizing goals, and embracing innovation? We're here and eager to connect.