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_passwordConfiguration variables
Section titled “Configuration variables”-
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.
- 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
-
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 tofalse.
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:
3232for the ESP328266for the ESP82662040for the RP20408892for Beken chips8082for 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.
Updating the Password
Section titled “Updating the Password”Changing an Existing Password
Section titled “Changing an Existing Password”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.
Adding a Password
Section titled “Adding a Password”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.
Removing a Password
Section titled “Removing a Password”-
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_serverOTA platform; either through the device’s web interface or by runningesphome upload <config.yaml> --ota-platform web_server(see the Web Server OTA CLI usage section).
Encryption
Section titled “Encryption”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_keyBy 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_VERBOSEEnabling Encryption on an Existing Device
Section titled “Enabling Encryption on an Existing Device”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.
Devices With the Native API
Section titled “Devices With 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.
-
Install a firmware that offers encryption. Keep the config as it is; if
api:has noencryption:yet, add one with a key. Install with ESPHome 2026.9.0 or newer, then check the device log forEncryption: offered, plaintext accepted. A device that already shows this starts at step 2.api:encryption:key: !secret device_name__encryption_keyota:- platform: esphomepassword: !secret device_name__ota_password -
Require encryption. Add
encryption:under the OTA platform, remove thepassword:, and install. Check the device log forEncryption: required.api:encryption:key: !secret device_name__encryption_keyota:- platform: esphomeencryption:
Devices Without the Native API
Section titled “Devices Without the Native API”Without api: the running firmware cannot offer encryption, so an api: block is used once as a stepping stone.
-
Install a firmware that offers encryption. Add a temporary
api:block with a generated key andreboot_timeout: 0s. Install with ESPHome 2026.9.0 or newer, then check the device log forEncryption: offered, plaintext accepted.mqtt:broker: !secret mqtt_broker# Temporary, removed again in step 2api:encryption:key: !secret device_name__ota_esphome_keyreboot_timeout: 0sota:- platform: esphomepassword: !secret device_name__ota_password -
Move the key to the OTA platform. Remove the
api:block, put the same key underota: encryption:, remove thepassword:, and install. Check the device log forEncryption: required.mqtt:broker: !secret mqtt_brokerota:- platform: esphomeencryption: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.
Updating the partition table on ESP32
Section titled “Updating the partition table on ESP32”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: trueCAUTION
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.yamlTo 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.binesphome upload --partition-table --file partitions.bin name-of-your-config.yamlCAUTION
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.
Updating the bootloader on ESP32
Section titled “Updating the bootloader on ESP32”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: trueCAUTION
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.yamlWithout --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