Beginner6 min readESP32

Connect an ESP32 to Wi-Fi

Join a Wi-Fi network in station mode with the ESP32 Arduino core: connect with a timeout, print the IP address, recover from dropouts, and keep your password out of your code.

In station mode the ESP32 joins an existing Wi-Fi network, the way a phone or laptop does, and gets an IP address from your router. This article builds that up in two sketches: a minimal connection with a timeout, then a version that survives the router rebooting and keeps your password in a separate, git-ignored file. The code targets the ESP32 Arduino core 3.x.

Before you start: 2.4 GHz only

The original ESP32's radio supports 802.11 b/g/n on 2.4 GHz only. It can't see 5 GHz networks at all. If your router broadcasts the same network name on both bands, that's fine: the ESP32 joins on 2.4 GHz. If the network is 5 GHz-only, or the 2.4 GHz band is switched off, the ESP32 reports that the network doesn't exist. (A few newer family members, such as the ESP32-C5, add 5 GHz. The original ESP32 does not.)

Two other kinds of network need extra work that this article doesn't cover: networks with a login web page (hotel or guest Wi-Fi with a captive portal) and WPA2-Enterprise networks that ask for a username as well as a password.

A minimal connection with a timeout

#include <WiFi.h>
 
// ---- Change these ----
const char WIFI_SSID[]     = "YOUR_WIFI_NAME";
const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD";
// ----------------------
 
const unsigned long CONNECT_TIMEOUT_MS = 20000;
 
bool connectToWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
 
  Serial.print("Connecting to ");
  Serial.print(WIFI_SSID);
 
  unsigned long start = millis();
  while (WiFi.status() != WL_CONNECTED) {
    if (millis() - start >= CONNECT_TIMEOUT_MS) {
      Serial.println();
      Serial.print("Timed out. WiFi.status() = ");
      Serial.println((int)WiFi.status());
      return false;
    }
    delay(250);
    Serial.print('.');
  }
 
  Serial.println();
  Serial.print("Connected. IP address: ");
  Serial.println(WiFi.localIP());
  Serial.print("Signal strength (RSSI): ");
  Serial.print(WiFi.RSSI());
  Serial.println(" dBm");
  return true;
}
 
void setup() {
  Serial.begin(115200);
  delay(500);
 
  if (!connectToWiFi()) {
    Serial.println("Restarting in 10 seconds...");
    delay(10000);
    ESP.restart();
  }
}
 
void loop() {
}

What each piece does:

  • WiFi.mode(WIFI_STA) makes the role explicit. The ESP32 can also run its own access point, or both at once, so it's worth saying which one you want.
  • WiFi.begin() returns immediately. Joining the network and getting an IP address happen in the background, in the Wi-Fi driver's own task. That's why the sketch then polls WiFi.status().
  • The timeout matters. Without it, a typo in the password or a router that's switched off leaves the sketch printing dots forever. With it, the sketch prints the status code and restarts, so a device in a cupboard recovers on its own.
  • WiFi.RSSI() is the signal strength in dBm. It's always negative, and values closer to zero are stronger. If you get frequent dropouts, check this number first.

If the connection times out, the printed status code is the first clue:

ValueNameMeaning
1WL_NO_SSID_AVAILThe network wasn't found: a typo in the name, out of range, or a 5 GHz-only network.
3WL_CONNECTEDConnected and has an IP address.
4WL_CONNECT_FAILEDThe network was found but joining failed.
6WL_DISCONNECTEDNot connected: still trying, or it gave up.

A wrong password doesn't always produce the same status code. The disconnect reason from the event handler below is more specific.

Surviving dropouts

Routers reboot, microwaves interfere, and signal fades. A sketch that runs for weeks has to cope with the connection dropping and coming back. The Arduino core gives you three tools for this.

1. Auto-reconnect. WiFi.setAutoReconnect(true) tells the core to rejoin the network when the connection drops. It's already on by default in the Arduino core; calling it just makes the intent visible in your code.

2. Wi-Fi events. WiFi.onEvent() registers a function that the Wi-Fi system calls whenever something happens: joined the access point, got an IP address, lost the connection. That's more precise than polling WiFi.status(), and the disconnect event carries a reason code that tells you why.

3. A safety net in loop(). If the connection has been down for a long time despite auto-reconnect, call WiFi.reconnect() to force a fresh attempt with the same credentials.

Note

Your event function runs in the core's event task, not inside loop(). Keep it short: set a flag, print a line, return. Don't call delay() or start network requests from inside it. Do that work in loop() when you see the flag change.

This sketch puts the three together. It reads the credentials from secrets.h, which the next section creates.

#include <WiFi.h>
#include "secrets.h"  // defines WIFI_SSID and WIFI_PASSWORD (see the next section)
 
const unsigned long CONNECT_TIMEOUT_MS = 20000;  // wait this long in setup()
const unsigned long OFFLINE_RETRY_MS   = 60000;  // force a reconnect after this long offline
const unsigned long STATUS_INTERVAL_MS = 10000;  // print a status line this often
 
volatile bool wifiConnected = false;
unsigned long offlineSince = 0;
unsigned long lastStatusPrint = 0;
 
// Called by the Wi-Fi system (in its own task) whenever something happens.
void onWiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
  switch (event) {
    case ARDUINO_EVENT_WIFI_STA_CONNECTED:
      Serial.println("[wifi] joined the access point, waiting for an IP address");
      break;
    case ARDUINO_EVENT_WIFI_STA_GOT_IP:
      wifiConnected = true;
      Serial.print("[wifi] got IP address ");
      Serial.println(IPAddress(info.got_ip.ip_info.ip.addr));
      break;
    case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
      wifiConnected = false;
      Serial.print("[wifi] disconnected, reason ");
      Serial.println(info.wifi_sta_disconnected.reason);
      break;
    default:
      break;
  }
}
 
void setup() {
  Serial.begin(115200);
  delay(500);
 
  WiFi.onEvent(onWiFiEvent);  // register before begin() so no event is missed
  WiFi.mode(WIFI_STA);
  WiFi.setAutoReconnect(true);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
 
  Serial.print("Connecting to ");
  Serial.println(WIFI_SSID);
 
  unsigned long start = millis();
  while (!wifiConnected && millis() - start < CONNECT_TIMEOUT_MS) {
    delay(250);
  }
  if (!wifiConnected) {
    Serial.println("Not connected yet; auto-reconnect keeps trying in the background");
  }
  offlineSince = millis();
}
 
void loop() {
  unsigned long now = millis();
 
  if (wifiConnected) {
    offlineSince = now;
  } else if (now - offlineSince >= OFFLINE_RETRY_MS) {
    Serial.println("[wifi] offline for a minute, forcing a reconnect");
    WiFi.reconnect();
    offlineSince = now;
  }
 
  if (now - lastStatusPrint >= STATUS_INTERVAL_MS) {
    lastStatusPrint = now;
    if (wifiConnected) {
      Serial.print("[status] online, IP ");
      Serial.print(WiFi.localIP());
      Serial.print(", RSSI ");
      Serial.print(WiFi.RSSI());
      Serial.println(" dBm");
    } else {
      Serial.println("[status] offline");
    }
  }
 
  // Your application code goes here. Check wifiConnected before using the network.
  delay(10);
}

wifiConnected is marked volatile because the event task writes it and loop() reads it. Expect the "disconnected" line to repeat while the core retries. The reason number comes from the ESP-IDF wifi_err_reason_t list. Two you'll meet often: 201 means the network wasn't found, and 15 (4-way handshake timeout) usually means a wrong password.

To test it, power-cycle your router while the sketch runs. You should see the disconnect, a few retries, then a new "got IP address" line once the router is back.

Keep credentials out of the sketch

If your sketch lives in a Git repository, a password typed into the .ino file ends up in the history, and from there on GitHub. Put credentials in their own file and tell Git to ignore it.

1. Create secrets.h. In Arduino IDE 2.x, click the ⋯ menu at the right end of the tab bar, choose New Tab, and name it secrets.h. The file is saved in the sketch folder next to the .ino file and compiled along with it.

#pragma once
 
// Wi-Fi credentials. This file is git-ignored - never commit it.
const char WIFI_SSID[]     = "YOUR_WIFI_NAME";
const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD";

2. Commit a template instead. Create secrets.example.h with the same contents and placeholder values. That shows other people (and future you) which constants the sketch expects. Then, from the sketch folder:

echo "secrets.h" >> .gitignore
git add .gitignore secrets.example.h
git status   # secrets.h must not appear anywhere in this output

Anyone who clones the project copies secrets.example.h to secrets.h and fills in their own values.

Two limits to keep in mind:

  • .gitignore only affects files that were never committed. If the password was committed even once, it's still in the history. Change the Wi-Fi password rather than trying to scrub it out.
  • The credentials still end up in the compiled firmware. Anyone who has the board in their hands can read them from the flash chip. Keeping them out of Git protects the repository, not the device.

Next steps

Stuck on a step?

Describe your board and what you see; the assistant has this page as context.

Ask the assistant

Keep going