ESPHome OTA Update Failed: Safe Mode, Recovery and USB Reflashing

Fix ESPHome OTA update failures: IP and port troubleshooting, 2026.9 encrypted OTA migration, boot loops, Safe Mode, rollback, partition errors and ESP32 USB reflashing.

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 seeFirst place to investigate
Compile or YAML validation failsConfiguration, framework, component compatibility
Compile succeeds; hostname cannot be resolvedmDNS, device IP, network/VLAN
Device pings but OTA connection times outOTA platform/port, firewall, running firmware
Device asks for a password or rejects encryptionOld credentials versus running firmware
Upload reaches 100%, then node vanishesNew firmware boot, Wi-Fi settings, image/partition issue
ESP32 restarts repeatedlyCrash, brownout, watchdog, strapping or rollback
Node appears only as fallback Wi-Fi hotspotMain SSID/password/IP settings wrong or AP unreachable
USB serial port has disappearedFirmware 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 behaviourWhat to do next
garden-node.local not foundFind router IP; upload with --device IP
API connects but OTA port is closedCheck whether native OTA is in installed firmware; test port 3232
ESP requests passwordKeep the old password for the first migration upload
device did not offer encryptionUpdate installed firmware to 2026.9+ first, using its existing auth method
Transfer completes; repeated resetsCapture serial boot logs; try Safe Mode/rollback; check power and latest components
OTA updates are impossible in Safe Mode logsInspect partition/recovery-app guidance; prepare USB recovery if accessible
Device falls back to its own SSIDRestore Wi-Fi or re-provision through captive portal
USB device vanishes after program startsEnter ROM BOOT mode and flash a minimal serial image
Firmware too large for OTA partitionReduce 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.

Official documentation

Share your love