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 pollsWiFi.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:
| Value | Name | Meaning |
|---|---|---|
| 1 | WL_NO_SSID_AVAIL | The network wasn't found: a typo in the name, out of range, or a 5 GHz-only network. |
| 3 | WL_CONNECTED | Connected and has an IP address. |
| 4 | WL_CONNECT_FAILED | The network was found but joining failed. |
| 6 | WL_DISCONNECTED | Not 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 outputAnyone who clones the project copies secrets.example.h to secrets.h and fills in their own values.
Two limits to keep in mind:
.gitignoreonly 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
- Wi-Fi weather station: serve a live sensor dashboard from the ESP32.
- Soil moisture sensor with MQTT: connect, publish one reading, and go back to sleep.
- Deep sleep basics: turn Wi-Fi off between readings to save power.
- ESP32 GPIO pin guide: why analog inputs belong on ADC1 when Wi-Fi is on.
Stuck on a step?
Describe your board and what you see; the assistant has this page as context.
Keep going
Getting Started with the ESP32 in Arduino IDE
Install the ESP32 Arduino core, pick the right board and port, and upload a first Blink and Serial sketch, plus fixes for the upload errors almost everyone hits.
ESP32 GPIO Pin Guide: Which Pins Are Safe to Use
Strapping pins, flash pins, input-only pins, ADC1 vs ADC2, touch pins and the default I2C and SPI pins on the original ESP32 (WROOM-32), with a table of the pins to use first.