Recovering a Removed ESPHome Configuration

Recovering a lost ESPHome config (and reflashing when you can’t)

I had an IKEA Vindriktning with an ESP-01 module in it that went missing during our hurricane recovery two years ago. This week it turned up in a crate under the sink. It still appeared in Home Assistant, but I had deleted it from the ESPHome dashboard, so I assumed the YAML was gone.

When I plugged it in, it gave no visible sign of life, but it was on the network. What follows is how I got the original config back without touching the device, and what I would have done if the config really had been gone.

TL;DR: As of ESPHome 2026.6.0 the ESPHome dashboard doesn’t actually delete your YAML. It moves it to /config/esphome/archive/. Look there first. Everything else in this post is for when that folder is empty.

First rule: don’t delete the Home Assistant integration

Home Assistant’s ESPHome integration entry holds the device’s native API encryption key. As long as that entry exists you can still talk to the device, watch its logs, and confirm what it is. Delete it and you lose the key. Leave it alone until you’re done.

Step 1: SSH into Home Assistant

I use the SSH add-on. Everything below runs from that shell.

ssh [email protected]

Step 2: Find every record for the device

Grab the MAC and IP from your router or from the device page in Home Assistant, then search for both:

grep -Ril -E 'aa:bb:cc:dd:ee:ff|192\.0\.2\.100' /config/.storage /config/esphome 2>/dev/null

Mine turned up:

/config/.storage/core.config_entries
/config/.storage/core.device_registry
/config/.storage/core.entity_registry

Step 3: Pull the API key out of core.config_entries

This is the important one. core.config_entries holds the ESPHome integration entry, including the noise_psk (the API encryption key) and, on older setups, the plaintext password. jq is in the SSH add-on, so:

jq -r '.data.entries[]
  | select(.domain == "esphome")
  | select(.unique_id == "aa:bb:cc:dd:ee:ff")
  | {title, unique_id, host: .data.host, port: .data.port,
     device_name: .data.device_name, noise_psk: .data.noise_psk,
     password: .data.password}' \
  /config/.storage/core.config_entries

If you don’t have jq (it is included in HAAS), the file is pretty-printed JSON, so plain grep works:

grep -n -B12 -A6 '"unique_id": "aa:bb:cc:dd:ee:ff"' /config/.storage/core.config_entries

What I got back:

title:       air-sensor-01
unique_id:   aa:bb:cc:dd:ee:ff
host:        192.0.2.100
port:        6053
noise_psk:   <base64 key>
password:    null

The noise_psk value is the exact string that goes in api: encryption: key: in the YAML. Save that somewhere.

Step 4: Find out what the hardware is

The device registry tells you the board and the ESPHome version it was built with. Both matter if you end up rebuilding the config. Yeah you could open it up, or if you remember…great… I couldn’t remember and didn’t want to open it up.

grep -n -i -B8 -A20 'aa:bb:cc:dd:ee:ff' /config/.storage/core.device_registry \
  | grep -E '"(name|manufacturer|model|sw_version|hw_version)"'
"name": "Air-Sensor-01",
"manufacturer": "Espressif",
"model": "esp01_1m",
"sw_version": "2024.4.1",

The entity registry tells you what the config exposed:

grep -n '"platform": "esphome"' -A30 /config/.storage/core.entity_registry \
  | grep -E '"(entity_id|original_name|unit_of_measurement)"' \
  | grep -i -A2 air_sensor_01
"entity_id": "sensor.particulate_matter_2_5um_concentration",
"original_name": "Particulate Matter 2.5µm Concentration",
"unit_of_measurement": "µg/m³",

So: an ESP-01 1M, ESPHome 2024.4.1, one PM2.5 sensor. That’s enough to reconstruct the config from scratch if I had to.

Step 5: Confirm the device is actually alive

Check the ports from the HA host. 6053 is the native API, 8266 is OTA on ESP8266 (ESP32 uses 3232), 80 is the optional web server.

for p in 6053 8266 80; do
  nc -z -w2 192.0.2.100 $p && echo "$p open" || echo "$p closed"
done
6053 open
8266 open
80 closed

Then check that Home Assistant is really talking to it with the key it has:

ha core logs 2>/dev/null | grep -i -E 'air-sensor-01|192\.0\.2\.100' | tail -40
Successfully resolved air-sensor-01 @ 192.0.2.100
Successfully connected to air-sensor-01 @ 192.0.2.100
Successful handshake with air-sensor-01 @ 192.0.2.100

A successful handshake means the key in core.config_entries matches the key on the device. The entity itself was reporting 0.0 µg/m³ with a fresh timestamp, so the firmware was running, the PM1006 sensor just wasn’t producing readings yet.

Step 6: Look for the YAML before you write one

The dashboard “Delete” action has moved configs to an archive folder for a long time; the new Device Builder (default in the add-on since ESPHome 2026.6.0) even has an “Archived devices” menu with an Unarchive button. Something I didn’t know until after I did all this. From the shell:

image

ls -la /config/esphome/archive/

If nothing obvious is there, search the whole ESPHome tree, including any surviving build directories, for the device name or the sensor name:

grep -rIl -E 'air-sensor-01|Particulate Matter 2.5' /config/esphome /data 2>/dev/null

Mine was sitting right there:

/config/esphome/archive/vindriktning.yaml

Moving it back up a level makes it reappear in the dashboard, though the best way is probably to use the UI and the unarchive option:

mv /config/esphome/archive/vindriktning.yaml /config/esphome/

The recovered file:

esphome:
  name: air-sensor-01

esp8266:
  board: esp01_1m

logger:
  baud_rate: 0

api:
  encryption:
    key: "<same base64 key as noise_psk>"

ota:
  password: "<ota password>"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "air-sensor-01 Fallback"
    password: "<ap password>"

captive_portal:

uart:
  rx_pin: GPIO2
  baud_rate: 9600

sensor:
  - platform: pm1006
    pm_2_5:
      name: "Particulate Matter 2.5µm Concentration"

The api.encryption.key matched the noise_psk from Step 3 exactly. At that point I had a config the dashboard could compile and push over the air with no further ceremony.

Step 7: What to do when the YAML really is gone

You can’t pull YAML back off the device. The firmware has no copy of it. What you can do is write a new config that the running firmware will accept over the air, and you need two things for that:

  1. The API encryption key. You have this from Step 3. Reusing it means Home Assistant keeps the same device, entities, and history without re-adopting anything.
  2. The OTA password. Home Assistant does not store this. Check /config/esphome/secrets.yaml (survives deletion, and most people use !secret ota_password), old HA backups under /backup, a password manager, or a surviving .esphome/build/<name>/src/main.cpp, which has it compiled in as a string.

If you have both, write the minimal config with the same name, the same board from the device registry, the recovered key, and the old OTA password:

esphome:
  name: air-sensor-01

esp8266:
  board: esp01_1m

logger:
  baud_rate: 0

api:
  encryption:
    key: "<noise_psk from core.config_entries>"

ota:
  - platform: esphome
    password: "<old OTA password>"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

uart:
  rx_pin: GPIO2
  baud_rate: 9600

sensor:
  - platform: pm1006
    pm_2_5:
      name: "Particulate Matter 2.5µm Concentration"

Two notes on that:

  • The OTA password in the config must match what’s on the device for the first upload. Change it afterward if you want, in a second upload.
  • Keep the entity name values identical to what the entity registry showed if you want history to carry over. Rename later once it’s back under control.

Then push it. From the dashboard: Install → Wirelessly. From the shell in the add-on:

esphome run /config/esphome/air-sensor-01.yaml --device 192.0.2.100

Before flashing anything, stream the live log with the recovered key. If this works, your API key is right and you have a record of what the old firmware was doing:

esphome logs /config/esphome/air-sensor-01.yaml --device 192.0.2.100

One caveat for old ESP8266 boards: OTA needs enough free flash to hold the new image next to the running one. A minimal config on an esp01_1m fits. If you jump from 2024.x to 2026.x and pile on features, check the build size before you push.

No OTA password? Serial it is.

Without the OTA password there is no over-the-air path. Pull the module, connect a USB-serial adapter, hold GPIO0 low at boot, and flash it from the dashboard (Install → Plug into this computer) or with esphome run ... --device /dev/ttyUSB0. You still want the API key from Step 3 so Home Assistant picks it right back up.

Where I ended up

Since it was running a 2024 build anyway, I took the opportunity to update it and add a few diagnostics. Same name, same key, same OTA password:

substitutions:
  name: air-sensor-01
  friendly_name: Air Sensor 01

esphome:
  name: ${name}
  friendly_name: ${friendly_name}

esp8266:
  board: esp01_1m

logger:
  baud_rate: 0   # UART0 pins are free for other use

api:
  encryption:
    key: "<api key>"

ota:
  - platform: esphome
    password: "<ota password>"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  power_save_mode: NONE
  ap:
    ssid: "${friendly_name} Fallback"
    password: !secret wifi_password

captive_portal:

uart:
  rx_pin: GPIO2
  baud_rate: 9600

sensor:
  - platform: pm1006
    pm_2_5:
      name: "PM2.5"
      accuracy_decimals: 0
      filters:
        - median:
            window_size: 5
            send_every: 5
            send_first_at: 1
  - platform: wifi_signal
    name: "WiFi Signal"
    update_interval: 60s
    entity_category: diagnostic
  - platform: uptime
    name: "Uptime"
    entity_category: diagnostic

button:
  - platform: restart
    name: "Restart"
    entity_category: diagnostic

Checklist

  • Leave the Home Assistant integration alone.
  • Note the MAC and IP.
  • jq the noise_psk (and password) out of core.config_entries.
  • Get board and ESPHome version from core.device_registry.
  • Get entity names from core.entity_registry.
  • Confirm 6053 and 8266 (or 3232) are open and HA logs show a handshake.
  • Check /config/esphome/archive/ and grep the rest of /config/esphome.
  • Find the OTA password in secrets.yaml, backups, or a surviving main.cpp.
  • Rebuild with the same name, board, and API key if needed.
  • Stream logs with esphome logs before you flash.
  • Back up the config somewhere the dashboard can’t archive it.

But you actually pulled it from the archive, not the device.
And the steps for getting it from the device are to rewrite it, so the title is misleading then…