An ESPHome OTA update failure does not automatically mean the ESP32 is bricked. Most failures fall into one of four categories: the firmware never reaches the device, the device rejects the update, the image transfers but does not boot, or the new firmware boots but can no longer join the network. Each category has a different recovery path—and some can be fixed without opening the enclosure.
This guide follows a recovery ladder: identify the failure stage, preserve a working configuration, try the correct IP and OTA method, use ESPHome Safe Mode if the application is rebooting, then reflash over USB only when necessary. It also covers an important change in ESPHome 2026.9: encrypted native OTA updates, including why replacing an old OTA password in a single step can prevent an otherwise healthy device from updating.
Start with the symptom, not the upload button
| What you see | First place to investigate |
|---|---|
| Compile or YAML validation fails | Configuration, framework, component compatibility |
| Compile succeeds; hostname cannot be resolved | mDNS, device IP, network/VLAN |
| Device pings but OTA connection times out | OTA platform/port, firewall, running firmware |
| Device asks for a password or rejects encryption | Old credentials versus running firmware |
| Upload reaches 100%, then node vanishes | New firmware boot, Wi-Fi settings, image/partition issue |
| ESP32 restarts repeatedly | Crash, brownout, watchdog, strapping or rollback |
| Node appears only as fallback Wi-Fi hotspot | Main SSID/password/IP settings wrong or AP unreachable |
| USB serial port has disappeared | Firmware may have changed USB behaviour; enter ROM download mode |
Separate a failed transfer from a failed boot. If the dashboard reports an upload error at 4%, the new image may not have replaced the running firmware. If the transfer completes but the device drops off the network, do not keep sending the same binary: examine the boot logs and recent YAML changes first.
Preserve your working configuration and keep outputs safe
Before risky recovery work, save the last-known-good YAML and secrets.yaml privately. Note the device’s real IP, board variant, flash size, configured OTA platform, API encryption key, and any components recently changed. Back up a known-good firmware image if you already have it; a YAML file cannot always reproduce an old binary when libraries have since changed.
For a device controlling heaters, pumps, door locks or mains relays, make the controlled equipment electrically safe before repeatedly rebooting or flashing the ESP32. Safe Mode intentionally disables ordinary components, so relay states and expected local automations may not behave as they do in the normal application. Do not assume an emergency stop or critical safety interlock can depend on ESPHome firmware.
A physical USB recovery may require opening an enclosure. Disconnect hazardous supplies before exposing wiring or connecting a laptop. Do not attach the laptop’s USB power in parallel with an unknown external power circuit unless the board’s power-path design supports it.
Check configuration and compile before any upload
You can distinguish YAML/build errors from network failures without touching the deployed node:
esphome config garden-node.yaml
esphome compile garden-node.yaml
A configuration validation error is not an OTA failure. Fix incorrect component names, invalid pins, duplicate IDs or deprecated settings first. If a newer ESPHome version changed the framework or a component, try a small test build using the exact board variant rather than changing many features simultaneously.
esphome run garden-node.yaml validates, compiles, uploads and then tries to display logs. An error when opening the log connection after a successful upload does not by itself prove that flashing failed. Read the stage where the command stopped.
Find the real IP instead of relying on .local
An ESPHome device can be connected to Wi-Fi while garden-node.local does not resolve on the computer running Device Builder. mDNS frequently behaves differently across VLANs, VPNs, Docker networks and some router configurations.
Check the router’s DHCP client list or access-point controller for the ESP32’s current IP address. Then test it from the computer that performs the upload:
ping 192.168.1.42
esphome upload garden-node.yaml --device 192.168.1.42
On Windows, check TCP connectivity to the native ESPHome OTA port, normally 3232 on ESP32:
Test-NetConnection 192.168.1.42 -Port 3232
A ping reply does not prove that port 3232 is reachable. An ESP32 could answer ICMP while OTA is disabled, blocked by a firewall or not yet started. Equally, some networks block ping but allow TCP, so use multiple observations.
If your last firmware used a static IP, check its actual address and subnet. Updating the YAML’s manual_ip does not change the IP of the firmware currently running until the update succeeds. Upload to the old address first.
For more network diagnostics, see our ESPHome Wi-Fi Disconnects and Reconnect Loops guide.
Verify which OTA platform the running device actually has
Current ESPHome supports multiple OTA platforms. A typical native setup is:
ota:
- platform: esphome
For ESP32, the native OTA service normally listens on TCP port 3232. Its reachability is separate from the Native API connection on port 6053. A node may work in Home Assistant while OTA is blocked, and it can sometimes accept OTA even when the API is unavailable.
A web-based OTA setup instead requires both:
web_server:
port: 80
ota:
- platform: web_server
Adding platform: web_server to today’s YAML will not create a web OTA endpoint on a device that is still running yesterday’s firmware. The installed firmware must already include that feature. If neither OTA method is present and serial flashing is available, USB is the recovery path.
The 2026.9 encrypted OTA change
Beginning with ESPHome 2026.9, the native ESPHome OTA platform can encrypt the firmware transfer using Noise and an encryption key. This matters because the firmware image contains sensitive values such as Wi-Fi credentials and API keys. A traditional OTA password authenticates the uploader but does not itself encrypt the transmitted firmware image.
For a new device flashed over USB, current recommended YAML reuses the Native API key:
api:
encryption:
key: !secret garden_api_key
ota:
- platform: esphome
encryption:
The empty encryption: mapping means the OTA platform inherits the API encryption key. Do not add password: alongside encryption:; they cannot be combined on the same native OTA platform. Use a unique key per device and store it in private secrets, not in a public repository.
Migrating an existing password-protected device safely
Do not replace password: with encryption: in one attempt if the currently running firmware is old. That firmware may still demand the old password and may not support the encrypted handshake yet.
First, keep the old OTA password and make sure the running device receives a firmware build from ESPHome 2026.9 or newer with its existing Native API encryption key:
api:
encryption:
key: !secret garden_api_key
ota:
- platform: esphome
password: !secret garden_old_ota_password
Upload using the existing credentials and confirm the newly running device offers encryption. Then make a second change: remove the OTA password and require encryption:
api:
encryption:
key: !secret garden_api_key
ota:
- platform: esphome
encryption:
Upload again and verify that the device reports encryption as required. If Home Assistant originally provisioned the API key, reuse that exact key; inventing a new one can break both Home Assistant access and OTA authentication. For MQTT-only devices without the Native API, follow ESPHome’s separate documented migration involving a temporary API key rather than copying the HA-only example above.
If the upload reports ESP requests password, but no password given!, the running firmware still expects the old password. If it reports the device did not offer encryption; refusing to send the image in plaintext, do the intermediate update first or use an existing alternative recovery route. This is an authentication/version mismatch, not evidence that the ESP32 hardware failed.
The firmware uploads, then immediately reboots
A successful transfer only proves that the image reached flash. The new application can still crash because of an incompatible external component, wrong flash/PSRAM setting, invalid GPIO, corrupted stored preferences or insufficient power.
Connect a serial console and capture the first boot messages, including the reset reason and any exception or brownout output. Prefer USB/UART logs for a fast crash loop; the network logger and Native API might never start. Try a known-good supply and temporarily disconnect external modules whose startup currents or strapping-pin levels could affect boot.
If the last change added a display, BLE proxy, camera, custom lambda or large library, return to the last-good YAML and upload a minimal build first. For S3 boards, check flash and PSRAM configuration against the actual module marking: enabling octal PSRAM on a quad-only board is not a Wi-Fi problem. See our ESP32-S3 SuperMini pinout, USB and PSRAM guide.
What ESPHome Safe Mode does
ESPHome Safe Mode is designed for repeated early boot failures. It is normally enabled automatically by the OTA component. After 10 failed boot attempts by default, ESPHome can start in a restricted recovery configuration: serial logging, network connectivity and OTA remain available, while ordinary components such as sensors and displays are disabled.
The default boot-success window is one minute. Once a normal boot survives that long, its failure counter can be cleared. In Safe Mode, a device can therefore come back onto the network even when a newly added display or external component keeps crashing normal firmware.
safe_mode:
num_attempts: 10
boot_is_good_after: 1min
reboot_timeout: 5min
These are representative defaults; you do not need to add the block merely to enable Safe Mode on a normal OTA-enabled node. Safe Mode cannot fix a wrong Wi-Fi password, absent OTA support, an inaccessible network or a corrupt bootloader by itself. It also is not the same thing as the ESP32’s ROM download/BOOT mode.
Force Safe Mode from a healthy device
You can expose a recovery button in advance:
safe_mode:
button:
- platform: safe_mode
name: "Enter ESPHome Safe Mode"
Pressing the button through Home Assistant reboots the device into Safe Mode. The button cannot rescue a node that is already unreachable unless the firmware had another way to trigger it. ESPHome also documents repeated hardware resets as a route to Safe Mode on supported configurations; read your installed firmware’s Safe Mode settings before relying on that procedure.
When a node enters Safe Mode, upload a corrected, minimal image using the same OTA method already present on the device. Do not expect your regular Home Assistant entities or Native API-based controls to function in Safe Mode: most components are deliberately inactive.
OTA rollback: useful, but not magic
Current ESPHome ESP32 configuration has enable_ota_rollback support and defaults to it being enabled. On a compatible rollback-capable bootloader and suitable partition layout, an update that crashes before the firmware is marked healthy can be rolled back to the preceding image.
There is an important limitation: serial flashing can update the bootloader, ordinary application OTA usually does not. Older boards may need a compatible bootloader installed over USB before rollback can work. Do not enable unfamiliar advanced bootloader options on a remotely deployed node and assume the running bootloader supports them.
Since ESPHome 2026.8, an orderly restart, shutdown or deep-sleep entry normally counts as a successful boot. For battery nodes that sleep quickly, this affects whether the new image is confirmed or rolled back. Consult the Safe Mode boot_is_good_on_shutdown and safe_mode.mark_successful options if you intentionally want stricter validation.
Not enough space or an incompatible partition layout
OTA normally needs room for an incoming application image in a designated OTA partition. A binary that compiles successfully may still be too large for the partition layout already installed on the node. Flashing more features into YAML does not increase its physical flash size or automatically rewrite its partition table.
Check the board’s real flash capacity, the configured flash_size, the output binary size and the serial boot log. Avoid changing all partition settings on a remote device without a physical recovery plan.
Newer ESPHome versions can update an ESP32 partition table and bootloader through the native OTA platform when the running firmware was prepared with allow_partition_access: true. These are advanced recovery operations with a genuine soft-brick risk if power fails; they can also move the NVS region and make saved Wi-Fi settings unavailable. For an accessible board, prefer a deliberate USB reflash of a tested complete image instead.
Web OTA as an alternative route
If the running firmware already includes web_server: plus ota: - platform: web_server, open the node’s actual IP address in a browser, authenticate to its web interface, and upload the device’s OTA-format application image, not a factory/merged image.
The current CLI also supports:
esphome upload garden-node.yaml --device 192.168.1.42 --ota-platform web_server
This can help when native OTA port 3232 is blocked, or the old native OTA password is unavailable but web-server authentication still works. It is not a workaround for a device that lacks web OTA in its running firmware. Web OTA uses HTTP and Basic authentication when configured; unlike encrypted native OTA, it is not a confidential firmware transfer. Keep it restricted to trusted local networks and protect its endpoint.
Do not install firmware.factory.bin through a web OTA page. The factory/merged image is intended for an appropriate initial/serial flash procedure; the OTA path expects the designated application image (firmware.bin or firmware.ota.bin according to the builder’s output and instructions).
Fallback AP and captive portal: network recovery, not ROM recovery
If the original YAML included a fallback Wi-Fi access point and captive_portal:, a device unable to join your main Wi-Fi may start its own SSID. Join that AP locally and inspect the portal for Wi-Fi provisioning or its recovery/update interface when enabled.
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Garden Recovery"
password: !secret fallback_ap_password
captive_portal:
A visible fallback hotspot is useful evidence that firmware and Wi-Fi radio are running. It does not prove that Home Assistant or the ordinary OTA address is reachable, and it cannot help if the firmware crashes before the fallback AP starts. Keep fallback credentials private; a recovery AP is a deliberate local access path, not a public service.
Last resort: recover over USB
If the application does not boot, both network OTA paths are absent, Wi-Fi credentials are unrecoverable, or the partition table/bootloader is broken, connect the board physically and use ESPHome Device Builder’s USB option or an appropriate ESPHome serial installer. Start with a data-capable cable, inspect the operating system’s serial-port list and close any program already holding the port.
For a classic ESP32 or ESP32-S3, ROM download mode is entered by holding GPIO0 low during reset (usually hold BOOT, tap RESET/EN, then release BOOT). On ESP32-C3, the boot strap is GPIO9 low, with GPIO8 kept high; a peripheral pulling the wrong level can prevent successful download mode. Some compact boards have only one button or a vendor-specific recovery sequence—consult that board’s guide.
After the ROM USB/UART device appears, select the correct port and flash the factory/serial image created for your exact board. The safest workflow is to let ESPHome Device Builder/Web prepare the proper image and flash layout rather than writing an arbitrary .bin to address 0x0. An OTA-only binary is not a substitute for a merged factory image in an initial flash procedure.
If the USB serial port disappears when normal firmware boots but reappears in ROM download mode, the chip is still recoverable; your application may have reassigned native USB, crashed very early or disabled CDC. See our ESP32-S3 USB Not Detected recovery guide.
Do not erase the entire flash as your first troubleshooting step. An erase can remove NVS-stored Wi-Fi credentials, calibration, counters and other persistent state. Only use full erase when necessary and after saving what can be saved.
A clean minimal recovery image
For a common classic ESP32 DevKit, this is a reasonable temporary base to prove power, flash, Wi-Fi and native OTA. Replace the board and secrets for your own hardware:
esphome:
name: garden-node
friendly_name: Garden Node
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
api:
encryption:
key: !secret garden_api_key
ota:
- platform: esphome
encryption:
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Garden Node Recovery"
password: !secret fallback_ap_password
captive_portal:
Important: this is a current new-device/serial-reflash example. When recovering an already deployed, password-protected node by OTA, retain the running device’s old authentication settings until the staged encryption migration is completed. Do not blindly copy the new encryption: block into an older installed node.
Upload the minimal image, verify that the device remains reachable, and add your sensor or display components back in small groups. If the same failure returns immediately after one group, the fault is now narrowed to that hardware/configuration change.
Recovery checklist by failure message
| Message or behaviour | What to do next |
|---|---|
garden-node.local not found | Find router IP; upload with --device IP |
| API connects but OTA port is closed | Check whether native OTA is in installed firmware; test port 3232 |
ESP requests password | Keep the old password for the first migration upload |
device did not offer encryption | Update installed firmware to 2026.9+ first, using its existing auth method |
| Transfer completes; repeated resets | Capture serial boot logs; try Safe Mode/rollback; check power and latest components |
OTA updates are impossible in Safe Mode logs | Inspect partition/recovery-app guidance; prepare USB recovery if accessible |
| Device falls back to its own SSID | Restore Wi-Fi or re-provision through captive portal |
| USB device vanishes after program starts | Enter ROM BOOT mode and flash a minimal serial image |
| Firmware too large for OTA partition | Reduce image or plan a controlled partition/serial flash |
Make the next OTA update safer
Keep physical access to your first prototype, enable a supported OTA method and Safe Mode, retain private backups of working YAML/keys, and test any flash/PSRAM/partition changes on a spare device. On remote nodes, stage one change at a time rather than updating framework, encryption, partitions and external components in one upload.
For Home Assistant-only devices, the Native API and native encrypted OTA are a clean default; see our Native API vs MQTT guide. The key principle is simple: identify whether the failure is in building, transferring, authenticating, booting or connecting before choosing the recovery mechanism.