Reading Time: 5 minutes

Field note · Embedded debugging · Home network

Eero “not accepting connections”: fixing an ESP32 Wi-Fi reason=2 failure

A GPS tracker that had streamed telemetry for weeks suddenly refused to join the Wi-Fi — correct password, router a foot away, rejected every single time. Eight things later, the fix was one line, and the cause was the last thing a strong signal lets you suspect.

Resolved Board ESP32-S3 Error WIFI_REASON_AUTH_EXPIRE (2) Router Eero mesh (WPA2) Fix lower Wi-Fi TX power
TL;DR

The short version

What broke: A little Wi-Fi gadget (an ESP32 microcontroller) stopped connecting to the home Eero mesh. The router kept rejecting it — even though the password was right, nothing had changed, and the router was inches away.

What actually fixed it: Turning the gadget’s Wi-Fi transmitter down. One line of code — WiFi.setTxPower(WIFI_POWER_8_5dBm) — dropped its transmit power from full blast to about a quarter of it, and it connected on the first try and has stayed connected ever since.

The counter-intuitive bit: The signal was excellent, so “not enough power” was obviously not the problem. It turned out the problem was too much power: pushed to maximum, right next to the router, the little radio’s signal gets distorted, and the router refuses the messy handshake. Backing it off cleans it up.

If your ESP32 throws reason=2 / “Auth Expired” with the correct password and a strong signal, lower its transmit power first. It costs one line and thirty seconds to test.

ESP32-S3 + u-blox NEO-M9N GPS receiver Wi-Fi JSON / 10s Eero mesh WPA2 · 2.4 GHz HTTPS POST WordPress live dashboard map · charts
Fig 1 — The telemetry path. The break was entirely at the first hop: the ESP32 → Eero association.
Technical

The full write-up

1 The system & the symptom

The device is a home-built GNSS tracker: an ESP32-S3 paired with a u-blox NEO-M9N receiver. Every 10 seconds it POSTs a JSON telemetry frame over Wi-Fi to a WordPress plugin that stores the track and renders a live dashboard. The network is an Eero mesh on WPA2. It had run reliably for weeks; the last good post was timestamped 21:16 the previous evening.

Then telemetry stopped. The serial console showed the device seeing the network, trying to join, and being deauthenticated ~2 seconds later — on every retry:

Serial — the failure, on every attempt [uplink] WiFi connecting to “LordMayhew”… [uplink] WiFi disconnected, reason=2 [uplink] WiFi disconnected, reason=2 gpsFix=1 sats=26 sdReady=0 wifi=0

Every observable fact pointed away from the device:

  • Credentials correct & unchanged — it had connected the night before.
  • Signal excellent — the AP was ~1 ft away; a scan reported it at −30 dBm.
  • Radio functional — receiving a reason=2 deauth at all proves the ESP32 transmitted a join request and received the AP’s reply.

reason=2 is WIFI_REASON_AUTH_EXPIRE (value 2 in esp_wifi_types.h) — the AP declaring the station’s authentication no longer valid and deauthenticating it. Correct password + strong signal + constant rejection is a genuinely confusing combination, which is why it took a full elimination pass to isolate.

2 The investigation — eliminate one cause at a time

Every hypothesis was tested on the actual hardware, and every one but the last returned the same reason=2. Recording the result of each is what let the process converge instead of circling.

#HypothesisWhat was doneResult
1Firmware connection logic was wrongRestored the exact working code, then tried 4 variants (auto-reconnect on/off, explicit begin(), reconnect(), back-off timing)reason=2
2Router blacklisted our MACAssigned a fresh locally-administered MAC (verified by read-back) — a router can’t distinguish a new MAC from any new devicereason=2
3Router now requires WPA3 / PMFForced explicit WPA2/WPA3-SAE (Hash-to-Element) + PMF-capablereason=2
4Corrupted Wi-Fi NVS / RF calibrationFull chip erase (esptool erase_flash) then reflashed cleanreason=2
5Marginal power browning out the radioDisconnected the 2.8″ TFT (~80 mA of load removed)reason=2
6Something specific to that networkTried the main and guest SSIDs — two separate networks on the meshreason=2
7An Eero-side block / security settingChecked device list (present, no block), confirmed WPA2, enabled the “legacy device” compatibility modereason=2
Transmit power was too highLowered TX power to 8.5 dBm via WiFi.setTxPower()connected

3 The red herring that cost the most time

The Eero app showed the tracker’s “last active” timestamp updating with each attempt — which looked exactly like the router seeing and accepting the device, pointing the whole investigation router-ward. In reality the ESP32 was associating for a split second, then being immediately deauthenticated. The mesh logged that fleeting handshake as activity; the rejection followed too fast to notice. A last-seen timestamp during a rapid connect/deauth loop is not evidence of success.

4 Root cause — the honest version

Verified fact: lowering the ESP32’s Wi-Fi transmit power fixed it, reproducibly and permanently. Leading explanation: at full power the transmitted signal is distorted enough that the AP rejects the handshake.

By default the ESP32 transmits at roughly 19.5 dBm. The widely-reported (and best-fitting) explanation is that on some boards the RF front-end is over-driven at maximum power — poor antenna/impedance matching or supply sag during transmit bursts degrades signal quality (EVM) enough that a fussy AP can’t cleanly complete authentication, and answers with a reason=2 deauth. Sitting a foot from the router at full power is close to the worst case for it.

Fact-check / honesty note AUTH_EXPIRE is a multi-cause error across the ESP32 family — others have traced the same code to router 802.11n handling, band-steering, or authentication-timing quirks, and fixed it by changing router settings or hardware rather than TX power. So the transmit-power mechanism above is the leading explanation, not a proven one. What is proven here is empirical: on this board, against this AP, dropping TX power was the single change that turned constant rejection into a stable connection.
BEFORE 19.5 dBm full power distorted Eero ✗ reason=2 deauthenticated AFTER 8.5 dBm reduced clean Eero ✓ POST → 200 connected & posting
Fig 2 — Same board, same router, one variable changed: transmit power.

5 The fix

Set the transmit power in Wi-Fi setup, and again right after begin() (which can reset it on some cores). Confirm it took by reading it back — setTxPower() is known to silently not apply on some builds.

// in wifiUplinkInit(), after WiFi.mode(WIFI_STA):
WiFi.setTxPower(WIFI_POWER_8_5dBm);

WiFi.begin(WIFI_SSID, WIFI_PASS);
WiFi.setTxPower(WIFI_POWER_8_5dBm);   // re-apply: begin() may reset it
Serial.printf("txpower now %d\n", WiFi.getTxPower());  // verify

WiFi.setTxPower() takes one of a fixed ladder of levels — from WIFI_POWER_MINUS_1dBm up to WIFI_POWER_19_5dBm. getTxPower() returns the value in quarter-dBm units, so 8.5 dBm reads back as 34 (34 × 0.25). If 8.5 doesn’t do it on your board, step further down the ladder. At ~1 ft from the router, 8.5 dBm is still far more than the link needs.

6 Verification

Decisive on the first boot after the change — no reason=2, an immediate join, and a successful POST:

Serial — after the fix [uplink] WiFi connecting… (txpower now 34) [uplink] POST -> 200 (577 bytes) gpsFix=0 sats=0 sdReady=0 wifi=1

A 45-second stability check then showed a steady stream — one post every ~10 s, all HTTP 200, zero rejections:

Stability check 2.1s POST -> 200 (577 bytes) 11.0s POST -> 200 (577 bytes) 20.7s POST -> 200 (577 bytes) 30.7s POST -> 200 (577 bytes) → 4 posts · 4× HTTP 200 · reason=2 count: 0

The dashboard, frozen since the previous evening, went live again within seconds.

7 Takeaways

  • For reason=2 / AUTH_EXPIRE with a correct password and strong signal, lower the transmit power first. A one-line, thirty-second test that would have saved hours here.
  • A strong signal argues against the real cause. The failure comes from too much power, and proximity to the router makes it worse — the opposite of intuition.
  • Distrust “the router accepted it” signals during a connect/deauth loop. A last-seen timestamp can be a split-second association, not a success.
  • It’s multi-cause. If lowering TX power doesn’t fix your AUTH_EXPIRE, look at the router’s 802.11n / band-steering / WPA mode next — but try the free one-liner first.
  • Eliminate methodically and log every result. The table is the method; it’s also what proved the fix rather than merely correlating with it.

8 References & further reading


Satellite GPS Tracker · firmware v1.20 · resolved 12 July 2026 · verified fix, honest mechanism, and a strong signal that lied.