← Back to Writings

Auth Expired: Why My ESP32 Refused to Rejoin WiFi After Every Power Outage

A tiny Bluetooth proxy that worked fine for weeks kept hanging after every power cut. The fix was physical, the root cause was a reconnect race, and I was wrong twice before I listened to the one clue I kept ignoring.

June 14, 2026
ESP32ESPHomeBluetooth ProxyHome AssistantWiFi Auth ExpiredReason Code 2ESP32-C3Cold Boot ReconnectRouter Association StateDebuggingBolt

The Setup

I run a small ESP32-C3 board in my home as a pure Bluetooth proxy. It does one job: relay a battery-powered BLE temperature and humidity sensor in the bedroom up to Home Assistant, so the readings end up in my dashboards and time-series database. No local sensors, no relays, just a quiet little radio bridge running ESPHome.

For weeks it was invisible in the best way. Then it started misbehaving, and always after the same trigger: a power outage. Every time the power blinked, the board would come back up and then just sit there. Not connected to WiFi, not relaying anything, the sensor going stale in Home Assistant. The only thing that brought it back was physically unplugging the board, waiting about a minute, and plugging it in again. A quick unplug and replug did nothing. It had to be the slow one.

That detail turned out to be the whole answer. It also turned out to be the thing I dismissed the longest.

This is the story of how a one-minute unplug ritual exposed a reconnect race between a cheap WiFi chip and an even cheaper home router, and how I talked myself into two wrong theories before I finally believed my own eyes.

The Error

Wiring the board to my Mac over USB and watching the serial logs gave me the one line that mattered:

[W][wifi_esp32]: Disconnected ssid='...' reason='Auth Expired'

Followed immediately by the board looping through its retry phases: scan, connect, fail, restart, over and over, roughly once a second.

Auth Expired is WiFi disconnect reason code 2. In plain terms it means “your previous authentication is no longer valid.” The important part is who sends it. This code comes from the access point, not the client. So the router was actively rejecting the board, not the other way around. The board wanted in. The router kept slamming the door.

The signal strength was fine the whole time, hovering around -56 dB. So this was not a range problem or a weak antenna. It was something about authentication state, and it only happened on a cold boot after power loss.

Wrong Theory Number One: It’s the Router’s WiFi 6 Settings

My first real lead pointed at the router. It is a budget consumer unit running in a mixed mode that advertises every modern WiFi flavor at once, including the newer WiFi 6 features like OFDMA and TWT, on a 40 MHz wide channel. ESP32 chips are famously grumpy about coexisting with aggressive modern radio features, and the internet is full of threads blaming exactly this.

So I changed the 2.4 GHz settings: narrowed the channel from 40 MHz to 20 MHz, disabled OFDMA and TWT, and dropped the mode from the WiFi 6 mix down to plain b/g/n. Then I did the one-minute unplug and replug.

The board connected. Beautiful. Case closed, or so I thought.

Then I did the honest thing and reverted the router back to its original WiFi 6 settings to confirm the cause. If the modern radio features were the problem, the board should have broken again.

It stayed connected. Perfectly stable, relaying the sensor, on the exact settings I had just blamed.

That single test killed the theory. The radio mode was not the root cause. Something else had changed during that one-minute unplug, and I had been crediting the wrong variable.

Wrong Theory Number Two: Bluetooth Is Starving the WiFi

The board is a single-core chip sharing one radio between WiFi and a continuous BLE scan. My ESPHome config had the BLE scanner running with its scan window equal to its scan interval, which means it was scanning essentially 100 percent of the time. The logs even showed warnings that the BLE task was taking a long time to yield.

This was a real problem worth fixing. A radio that never stops listening for Bluetooth has very little time left to talk to WiFi. So I dropped the scan window down to the ESPHome default, a small fraction of the interval, leaving plenty of airtime for WiFi.

The BLE warnings vanished, which was a genuine improvement. But the board still threw Auth Expired on the next cold boot. The contention was better, the bug was not gone. Theory two, dead.

I kept the change anyway. It was the right thing to do regardless. But it was not the answer.

The Clue I Kept Ignoring

Two theories down, I went back to the one fact that had been sitting in front of me the entire time, the fact I kept treating as a quirk instead of evidence.

Unplug for one minute, plug back in, and it connects on the first try. Every time. A fast unplug and replug fails. Waiting while it stays powered does not help either.

I had been calling this lucky timing. It was not luck. It was the mechanism, and once I took it literally the whole thing fell into place.

The Root Cause

Here is what is actually happening.

When the board boots fresh after a power cut, it immediately starts hammering the router with reconnect attempts, about once per second. The router still holds stale association and authentication state for that board’s MAC address from before the outage. Every aggressive reconnect attempt hits that stale entry, the router responds with Auth Expired, and crucially the constant hammering keeps the entry alive. The router never gets a quiet moment to age out the dead state, because the board keeps poking it.

When I unplug the board for a full minute, it goes completely silent. No packets, no reconnect attempts, nothing. That silence is what the router needs. With nothing refreshing the stale entry, it finally ages out. Plug the board back in, and now it meets a clean router with no leftover state, so it authenticates on the first try.

This explains every strange thing about the bug:

  • Why only this board. It is the only device in the house that boots fresh repeatedly and then retries this aggressively. My lights, plugs, and other gear stay connected through normal operation and never hammer the router into holding a stale entry.
  • Why it never healed on its own. As long as the board was powered and retrying, the router could not age out the bad state. Waiting was useless. Stopping the retries was mandatory, and the only way to stop them was to cut power.
  • Why the router radio settings did not matter. This was never about OFDMA, channel width, or WiFi 6. It was about association state in the router. Changing the radio mode happened to coincide with a power cycle, which is why theory one looked right for about an hour.

The board itself was healthy the whole time. The firmware was fine. The signal was fine. The bug lived in the handshake between a chip that retries too eagerly and a router that holds state too long.

What I Actually Did About It

A root cause you cannot fully delete is still worth managing. Since I cannot rewrite the router’s association timeout, I went after the two things I can control.

First, on the network side, a static DHCP reservation for the board’s MAC and turning off the router’s DoS and flood protection. Aggressive once-per-second reconnects look a lot like a flood to a defensive router, and that defense is part of what keeps the bad state pinned.

Second, and more honestly, a watchdog. The board can crash and cold-boot itself without any power outage at all, which means the failure can happen while I am away with no one to do the unplug ritual. So the real safety net is monitoring: if the relayed sensor goes stale for more than a few minutes, I get a notification. It does not fix the board, but it turns a silent multi-day outage into a message I can act on.

Lessons Learned

The technical lesson is that Auth Expired on a cold boot is rarely about credentials or radio settings. It is worth checking whether your client is hammering reconnects faster than the access point can clean up after the previous session. The fix space is on both ends: slow the client’s retry, or give the router a reason and a moment to forget.

The bigger lesson is about debugging discipline. The single most reproducible fact in the entire investigation, the one-minute unplug, was the one I kept explaining away because it sounded too dumb to be meaningful. I chased two elaborate theories about modern WiFi features and radio contention while the actual answer was sitting in a behavior I had labeled as superstition. My assistant Bolt and I both did this, repeatedly, until we stopped and took the boring clue at face value.

When a user, or a coworker, or your past self keeps repeating the same odd workaround, that repetition is data. The unglamorous, reproducible observation usually outranks the clever hypothesis. I knew that already. This little board made me relearn it.

Stuck on a bug that makes no sense?

I debug stubborn hardware and infrastructure problems for a living, the kind where every obvious fix fails and the real cause is hiding behind a clue you keep dismissing. If you have one of those, let's talk.

Get in touch