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 static API encryption key, for example a device that uses MQTT without api:. 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 static API key to use, for example a device that talks MQTT and has no api: block, or one whose API key is provisioned at runtime by Home Assistant. 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

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 API encryption key, and keeps it from then on. Older firmware 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

A device on ESPHome 2026.9.0 or newer with an API encryption key already offers OTA encryption, and esphome run uses that key whenever the device offers; the encryption: block is what makes the device require it. Add the block, remove any password:, and install; the encrypted handshake authenticates that upload, so the password is not needed for it. If the device still runs older firmware, install once without the block first, keeping any existing password: and with api: encryption: key: set: until ESPHome 2027.3.0 that upload falls back to plaintext with a warning and lands a firmware that offers, then add the block and drop the password. From 2027.3.0 the CLI no longer falls back to plaintext when it has a key to use, so a device with an API encryption key still on firmware older than 2026.9.0 must be updated before then; after that, update it by serial flash or through the web server OTA platform.

An OTA password: next to an API key only serves older clients and costs about 3.5 KB of flash on an ESP8266, so validation warns.

A key provisioned at runtime by Home Assistant (an API encryption: block without a key:) makes the device offer too, but the CLI cannot use a key it does not have, so those uploads stay plaintext until one of these is done:

  • Put the key Home Assistant provisioned into api: encryption: key:. The Device Builder writes it there when Home Assistant hands it over; do not generate a fresh one, that locks Home Assistant out after the next install.
  • Give the OTA encryption: block its own key:, which leaves the device with two keys: the provisioned API key and the OTA key in the config.

A device without a static API key cannot get its own OTA key: over the air: its running firmware either offers no encryption at all (an MQTT device) or offers with the provisioned API key rather than the OTA key the CLI would present, and with the block in the config the CLI does not send plaintext. Put the block in by serial flash or through the web server OTA platform; after that, installs are encrypted with the OTA key.

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