Both at once: AP and station
The ESP32 can join your network and run one of its own at the same time, with a single radio. That last part is the constraint that decides how the code has to be ordered.
One radio, two jobs
WIFI_AP_STA is not two radios. It is one radio being time-sliced between two
roles, and both roles have to be on the same channel — so whichever half joins
a network first decides the channel for the other one.
Bring the station up first and that decision is made before any client has connected. Bring the AP up first and every client associated on channel 1 is dropped the moment the station finds a router on channel 11, which reads to the user as "the setup page keeps disconnecting".
What it is good for
Configuration, mostly. The board holds a setup page on its own AP while simultaneously proving it can reach the network you just gave it — which is exactly what a provisioning portal needs, and why every one of them uses this mode.
It is also good for a diagnostics channel that survives losing the network: however badly the station half is doing, the AP half is still there to connect to and ask.
What it is not good for
Throughput. Two roles sharing one radio costs both of them, and a client uploading through the AP while the station uploads to a broker will disappoint everybody.
Nor is it a Wi-Fi extender. Packets do not pass between the two interfaces — the AP's clients can reach the ESP32 and nothing beyond it.
Turn it off when you are done
Dual mode costs current whether or not anyone is connected, and it is the
default in more example sketches than it should be. When provisioning is over,
WiFi.mode(WIFI_STA) drops the AP and gets the radio back.
The code
Station first, then the access point. The order is not cosmetic — it is the difference between clients that stay associated and clients that get dropped.
#include <WiFi.h>
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_AP_STA);
// Station first: joining decides the channel for both halves.
WiFi.begin("your-ssid", "your-password");
for (int i = 0; i < 40 && WiFi.status() != WL_CONNECTED; i++) delay(250);
if (WiFi.status() == WL_CONNECTED)
Serial.printf("station %s on channel %d\n",
WiFi.localIP().toString().c_str(), WiFi.channel());
// Then the AP, on whatever channel the station settled on.
WiFi.softAP("esp32-setup", "12345678", WiFi.channel() ? WiFi.channel() : 1);
Serial.printf("ap %s\n", WiFi.softAPIP().toString().c_str());
}
void loop() {}Once both are up there are two IP addresses on one board. Anything you serve is reachable at both, on two different subnets, and mDNS only advertises the station one.
Two interface objects, both active. Same ordering rule: bring the station up and let it settle before the access point starts advertising.
import network, time
sta = network.WLAN(network.STA_IF)
ap = network.WLAN(network.AP_IF)
sta.active(True)
sta.connect("your-ssid", "your-password")
for _ in range(40):
if sta.isconnected():
break
time.sleep(0.25)
print("station", sta.ifconfig()[0] if sta.isconnected() else "not joined")
ap.active(True)
ap.config(essid="esp32-setup", password="12345678", authmode=network.AUTH_WPA2_PSK)
print("ap ", ap.ifconfig()[0])wlan.status('rssi') and ap.status('stations') are the two things worth logging. The second tells you how many clients are actually associated, which the LED on the board does not.
When it does not work
There is one radio and it can only be on one channel. Joining a router on channel 11 moves the access point there too, and every associated client has to re-join. Start the station first and the jump happens before anyone is connected.
A client connected to the ESP32's AP is on the ESP32's subnet, and the ESP32 does not route. It is a way to reach the board, not a way through it. If you need routing, NAPT has to be enabled explicitly and it is not fast.
One radio time-slicing between two roles roughly halves what either would get alone, and it gets worse with each client on the AP. Dual mode is for configuration and diagnostics, not for carrying data.
Usually a failed station reconnect taking the radio with it. Set WiFi.setAutoReconnect(true), and consider dropping to AP-only when the station has been down for a while, rather than letting the retries interrupt the AP.
The other way to make a board portable: teach it every network it might meet and let it pick when it gets there.
Several networks, one board →Edit this page — content/esp32/dual-mode-ap-and-station.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.