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:
![]()
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:
- 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.
- 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
namevalues 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.
jqthenoise_psk(andpassword) out ofcore.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 survivingmain.cpp. - Rebuild with the same name, board, and API key if needed.
- Stream logs with
esphome logsbefore you flash. - Back up the config somewhere the dashboard can’t archive it.
