Skip to content
Get started

ESPHome OTA Updates

ESPHome’s Over-The-Air (OTA) platform allows you to remotely install modified/updated firmware binaries onto your ESPHome devices over their network interface (Wi-Fi / Ethernet / Thread).

This platform is used by both the ESPHome dashboard as well as the command line interface (CLI) (via esphome run ... ) to install firmware onto supported devices.

In addition to OTA updates, ESPHome also supports a “safe mode” to help with recovery if/when updates don’t work as expected. This is automatically enabled by this component, but it may be disabled if desired. See Safe Mode for details.

The recommended form reuses the native API encryption key, so one key protects the device; on a device that is already installed, read Encryption before adding the block.

# Example configuration entry, encrypted with the API key (recommended)
api:
encryption:
key: !secret device_name__encryption_key
ota:
- platform: esphome
encryption:

The secret names on this page follow the <hostname>__<base> shape the Device Builder writes to secrets.yaml, see Using secrets.yaml. Without an API encryption key, a password authenticates the uploader but does not keep the image confidential:

# Example configuration entry, password only (no API encryption key)
ota:
- platform: esphome
password: !secret device_name__ota_password
  • encryption (Optional): Encrypt the OTA update with the same Noise protocol the native API uses. See Encryption below. Cannot be combined with password.

    • key (Optional, string): The base64 encryption key. Omit it so the API encryption key is used and one key protects the device; only set it when there is no api: block at all, for example a device that uses MQTT. Next to a static API key it must be the same key, a different one is rejected at validation.
  • password (Optional, string): The password to use for updates. Not recommended for new devices, use encryption instead. Cannot be combined with encryption.

  • allow_partition_access (Optional, boolean): Only on esp32. Allow updating more partition types. Used for updating the partition table and the bootloader. Defaults to false.

IMPORTANT

Prefer encryption over password; it keeps the firmware image confidential in transit, not just authenticated. If you use a password, always use a strong, unique one. See the Security Best Practices guide for more information.

  • port (Optional, int): The port to use for OTA updates. Defaults:

    • 3232 for the ESP32
    • 8266 for the ESP8266
    • 2040 for the RP2040
    • 8892 for Beken chips
    • 8082 for the host platform
  • id (Optional, ID): Manually specify the ID used for code generation.

  • version (Optional, int): Version of OTA protocol to use. Version 2 is more stable. To downgrade to legacy ESPHome, the device should be updated with OTA version 1 first. Defaults to 2.

  • All automations supported by Ota.

NOTE

After a serial upload, ESP8266 modules must be reset before OTA updates will work. If you attempt to perform an OTA update and receive the error message Bad Answer: ERR: ERROR[11]: Invalid bootstrapping, the ESP module/board must be power-cycled.

Since the configured password is used for both compiling and uploading, the regular esphome run <file> command won’t work. This issue can be worked around by executing the operations separately with an on_boot trigger:

esphome:
on_boot:
- lambda: |-
id(my_ota).set_auth_password("New password");
ota:
- platform: esphome
id: my_ota
password: "Old password"

The “id: my_ota” in the OTA block is important. This is referenced in the lambda. After this trick has been used to change the password, the on_boot trigger may be removed and the old password replaced with the new password in the ota: section.

If OTA is already enabled without a password, simply add a password: line to the existing ota: config block. If the device has an API encryption key, use Encryption instead; a password next to an API key only serves older uploaders and warns at validation.

  • If you know your password but want to remove it, enter an empty string: id(my_ota).set_auth_password(""); instead of changing.

  • If you no longer know your password and the web server has been activated:

    • Remove the OTA password from the configuration
    • Build a new image locally.
    • Upload it via the web_server OTA platform; either through the device’s web interface or by running esphome upload <config.yaml> --ota-platform web_server (see the Web Server OTA CLI usage section).

Without encryption the OTA update travels over the network in plaintext, and unless a password: is set anyone who can reach the device can upload to it. The image contains your Wi-Fi credentials and your API encryption key, so anyone who can capture the traffic on your network can read them. Encrypting the update keeps the image confidential and, because it uses the Noise NNpsk0 pattern with a pre-shared key, also authenticates the uploader; no password is needed.

The recommended setup is an API encryption key plus a bare encryption: block under the OTA platform. The OTA uses the API key, so there is one key per device and nothing else to manage:

api:
encryption:
key: !secret device_name__encryption_key
ota:
- platform: esphome
encryption:

Only give the OTA block its own key: when there is no api: block at all, for example a device that talks MQTT. Generate a key with the on demand generator in the API component documentation:

mqtt:
broker: !secret mqtt_broker
ota:
- platform: esphome
encryption:
key: !secret device_name__ota_esphome_key

By contrast, a device whose API key is provisioned at runtime by Home Assistant keeps one key: the provisioned key goes into the yaml and the OTA block inherits it, see Enabling Encryption on an Existing Device.

WARNING

A device flashed by serial can carry encryption: from the start. A device updated over the air gets it once it runs ESPHome 2026.9.0 or newer with an encryption key it can offer, and keeps it from then on. Firmware without such a key cannot offer encryption, and with the block in the config the CLI refuses to send plaintext, so an install against it stops with the device did not offer encryption; refusing to send the image in plaintext. See Enabling Encryption on an Existing Device for the way around it.

Encryption cannot be combined with a password; the key already authenticates the uploader, so remove the password: line when you add encryption:. Adding the web server OTA platform next to it is allowed but warns during validation, since that endpoint accepts the same firmware image over plaintext HTTP. The captive portal’s update page (captive_portal: without that platform) exists only while the fallback AP is active and is the intended recovery path for a device that cannot join Wi-Fi, so it does not warn.

IMPORTANT

Once a device runs firmware with encryption: enabled it refuses plaintext OTA updates, and the CLI refuses to send a plaintext image when encryption: is configured. This is intentional; a downgrade to plaintext would be an unauthenticated upload. If the key is lost, recover the device with a serial flash, or through the web server OTA platform if the config has it. Changing the key is not an OTA operation yet either: the device only accepts the key it is running and the CLI presents the key in the config, so a new key goes in the same two ways. A key rotation system is planned for the future.

To troubleshoot a failing encrypted handshake, build with the logger at VERY_VERBOSE so the shared noise component’s messages are compiled in; a logs: entry alone cannot raise a level above level:. initial_level keeps every other component at its usual volume:

logger:
level: VERY_VERBOSE
initial_level: DEBUG
logs:
noise: VERY_VERBOSE

Add encryption: under the OTA platform only once the device runs firmware that offers encryption. With that line in the config the CLI never sends plaintext, so an over the air install against older firmware is refused. Which steps apply depends on whether the device uses the native API.

A key provisioned by Home Assistant must be in the yaml before step 1: the Device Builder writes it there, otherwise copy the key Home Assistant provisioned into api: encryption: key:. Never generate a fresh key for such a device, that locks Home Assistant out.

  1. Install a firmware that offers encryption. Keep the config as it is; if api: has no encryption: yet, add one with a key. Install with ESPHome 2026.9.0 or newer, then check the device log for Encryption: offered, plaintext accepted. A device that already shows this starts at step 2.

    api:
    encryption:
    key: !secret device_name__encryption_key
    ota:
    - platform: esphome
    password: !secret device_name__ota_password
  2. Require encryption. Add encryption: under the OTA platform, remove the password:, and install. Check the device log for Encryption: required.

    api:
    encryption:
    key: !secret device_name__encryption_key
    ota:
    - platform: esphome
    encryption:

Without api: the running firmware cannot offer encryption, so an api: block is used once as a stepping stone.

  1. Install a firmware that offers encryption. Add a temporary api: block with a generated key and reboot_timeout: 0s. Install with ESPHome 2026.9.0 or newer, then check the device log for Encryption: offered, plaintext accepted.

    mqtt:
    broker: !secret mqtt_broker
    # Temporary, removed again in step 2
    api:
    encryption:
    key: !secret device_name__ota_esphome_key
    reboot_timeout: 0s
    ota:
    - platform: esphome
    password: !secret device_name__ota_password
  2. Move the key to the OTA platform. Remove the api: block, put the same key under ota: encryption:, remove the password:, and install. Check the device log for Encryption: required.

    mqtt:
    broker: !secret mqtt_broker
    ota:
    - platform: esphome
    encryption:
    key: !secret device_name__ota_esphome_key
  • The step 1 upload is plaintext, the running firmware does not offer encryption yet, and validation warns about the password next to the key; both are expected and end with step 2. That plaintext fallback is planned to go away no earlier than ESPHome 2027.3.0, so do step 1 before then; after that the block arrives by serial flash or through the web server OTA platform.
  • The step 1 build is larger, it includes the API server and the noise library. On a device with 1 MB of flash, leave out components you do not need for that one build.
  • Home Assistant asks for the key after step 1 if one was added, and discovers the temporary api: device on the second path; ignore that, it disappears after step 2.

On the ESP32 it is possible to modify the partition table with an OTA update. This feature can be used to update devices that can’t be flashed via serial and have an old partition table with a small NVS partition. It can also be used to convert devices running Tasmota to ESPHome. Before you can update the partition table you need to add the option allow_partition_access: true to the config and install it to your device.

ota:
- platform: esphome
allow_partition_access: true

CAUTION

There is a risk of bricking the device if the power is interrupted or the ESP is reset during a partition table update, requiring serial flashing to repair it. Make sure you have a stable power supply. The update is usually completed within less than 15 seconds after running the esphome upload command.

IMPORTANT

If the location of the NVS partition changes during a partition table update, the data stored in it won’t be available after the update. In that case, if you used Captive Portal or Improv to configure your WiFi credentials you need to configure them again after updating.

Open the ESPHome logs for detailed information about potential errors. To perform the partition table update you can run the following command:

esphome upload --partition-table name-of-your-config.yaml

To upload a custom partition table from a CSV file, you can run the commands below. gen_esp32part.py is part of ESP-IDF and can be found here.

python3 gen_esp32part.py partitions.csv partitions.bin
esphome upload --partition-table --file partitions.bin name-of-your-config.yaml

CAUTION

When converting a Tasmota ESP32 device to ESPHome, it is required to add allow_partition_access: true on the first ESPHome firmware you upload to the device. Regular OTA updates will not work until the partition table update is completed.

On the ESP32 it is possible to update the bootloader with an OTA update. This is useful on devices that can’t be flashed via serial and need a newer bootloader, for example to enable OTA rollback or SRAM1 as IRAM. Before you can update the bootloader you need to add the option allow_partition_access: true to the config and install it to your device.

ota:
- platform: esphome
allow_partition_access: true

CAUTION

There is a risk of soft-bricking the device if the power is interrupted or the ESP is reset during the bootloader update, requiring serial flashing to recover it. Make sure you have a stable power supply, and be ready to reflash via USB if it fails.

Open the ESPHome logs for detailed information about potential errors. To perform the bootloader update, run:

esphome upload --bootloader name-of-your-config.yaml

Without --file, the bootloader image from the most recent compile is used (build/bootloader/bootloader.bin for native ESP-IDF, or .pioenvs/<name>/bootloader.bin for PlatformIO).

To upload a custom bootloader binary, run:

esphome upload --bootloader --file bootloader.bin name-of-your-config.yaml