Skip to content
Tutorial
14 min read
October 17, 2026

ESP32 WiFiManager: Configure WiFi Without Hardcoding SSID & Password

Stop hardcoding WiFi credentials. Use the WiFiManager library to give your ESP32 a captive configuration portal: first-boot setup from a phone, saved credentials, custom MQTT fields, a reset button, timeouts and fixes for the common pitfalls.

Every ESP32 tutorial starts with const char* ssid = "MyWiFi";. That is fine on your desk and a disaster as soon as the device leaves it. Give the board to a friend, move house, change your router password or build ten units for different customers, and every one needs the firmware edited and re-flashed over USB.

WiFiManager fixes this. On first boot, or whenever it cannot join a saved network, the ESP32 starts its own access point. You connect to it from a phone, a setup page opens automatically (a captive portal), you pick your home network from a list, type the password, and the ESP32 saves it and joins. Next boot it reconnects on its own. The same portal can also collect other settings, such as an MQTT server address or a device name, so one firmware image works for everybody.

This guide sets it up end to end: basic use, a timeout and fallback, custom fields saved to flash, a button to reopen the portal, a non-blocking mode, a static IP and the problems people actually hit.

Image pending: Phone showing the ESP32 setup access point in the WiFi list and the WiFiManager captive portal page with a list of nearby networks

How WiFiManager Works

  1. On boot, the library checks the credentials stored in the ESP32's flash memory.
  2. If it can connect, your program continues normally.
  3. If there are no credentials or the connection fails, it starts an access point (for example ESP32-Setup) and a tiny web server with DNS redirection.
  4. Your phone joins that access point and is sent to the portal page, usually automatically. If not, you open 192.168.4.1.
  5. You choose a network, enter the password and press save. The ESP32 stores the credentials, restarts the WiFi stack and joins your network.

The credentials are saved by the ESP32 WiFi system in non-volatile storage, so they survive power loss. This is the same access point mode described in the WiFi tutorial, wrapped in a polished setup flow.

Install the Library

In the Arduino IDE open Sketch → Include Library → Manage Libraries, search for WiFiManager, and install the one by tzapu (version 2.x supports the ESP32). If the installed version is a very old 0.x release, update it, because the ESP32 support lives in the newer one.

The Simplest Possible Use

#include <WiFi.h>
#include <WiFiManager.h>

void setup() {
  Serial.begin(115200);

  WiFiManager wm;
  bool connected = wm.autoConnect("ESP32-Setup", "setup1234");

  if (!connected) {
    Serial.println("Failed to connect, restarting");
    ESP.restart();
  }

  Serial.println("WiFi connected");
  Serial.print("IP: ");
  Serial.println(WiFi.localIP());
}

void loop() {
}

autoConnect(name, password) does everything. The first argument is the access point name you will see on your phone, and the second is its password (at least 8 characters). Leave the password out to create an open access point, which is easier but means anyone nearby can open your setup page, so a password is better.

First run: open WiFi settings on your phone, join ESP32-Setup, and wait for the portal to appear. If it does not pop up on its own, browse to http://192.168.4.1. Tap Configure WiFi, select your network, enter the password and save. The Serial Monitor shows the connection and the new IP address.

Second run: reset the board. It reconnects with the saved details and never opens the portal.

Note that autoConnect() blocks until it connects or gives up, which brings us to timeouts.

Image pending: WiFiManager portal main menu on a phone with the Configure WiFi button, followed by the network list and password entry page

Timeouts and Fallback

By default the portal stays open forever. That is bad for a device that should keep working after a power cut while the router is still booting: it would wait in setup mode instead of retrying. Set timeouts:

WiFiManager wm;

wm.setConnectTimeout(20);          // seconds to try a saved network
wm.setConfigPortalTimeout(180);    // seconds the portal stays open

if (!wm.autoConnect("ESP32-Setup", "setup1234")) {
  Serial.println("No WiFi. Restarting to try again...");
  delay(3000);
  ESP.restart();
}

With these settings the ESP32 tries the saved network for 20 seconds, then opens the portal for three minutes, and if nobody configures it, restarts and tries the saved network again. This loop is exactly what you want after a power outage: the ESP32 boots faster than the router, fails once, restarts and connects when the router is ready.

If your project can do something useful offline (log to an SD card, run a local display), continue running instead of restarting, and retry WiFi later in the background.

Know when the portal opens

A callback lets you show a message on a display or light an LED:

void onConfigMode(WiFiManager* wm) {
  Serial.println("Entered config mode");
  Serial.print("Connect to: ");
  Serial.println(wm->getConfigPortalSSID());
  Serial.print("Portal IP: ");
  Serial.println(WiFi.softAPIP());
}

wm.setAPCallback(onConfigMode);

Custom Fields: Collect MQTT Settings and More

The portal can ask for extra values. This is how you build one firmware that works with any broker address.

#include <WiFi.h>
#include <WiFiManager.h>
#include <Preferences.h>

Preferences prefs;
bool shouldSave = false;

char mqttServer[40] = "192.168.1.10";
char mqttPort[6]    = "1883";
char deviceName[24] = "esp32-node";

void saveConfigCallback() {
  shouldSave = true;
}

void setup() {
  Serial.begin(115200);

  // load saved values
  prefs.begin("config", false);
  strlcpy(mqttServer, prefs.getString("server", mqttServer).c_str(), sizeof(mqttServer));
  strlcpy(mqttPort, prefs.getString("port", mqttPort).c_str(), sizeof(mqttPort));
  strlcpy(deviceName, prefs.getString("name", deviceName).c_str(), sizeof(deviceName));

  WiFiManagerParameter pServer("server", "MQTT server", mqttServer, 40);
  WiFiManagerParameter pPort("port", "MQTT port", mqttPort, 6);
  WiFiManagerParameter pName("name", "Device name", deviceName, 24);

  WiFiManager wm;
  wm.setSaveConfigCallback(saveConfigCallback);
  wm.addParameter(&pServer);
  wm.addParameter(&pPort);
  wm.addParameter(&pName);
  wm.setConfigPortalTimeout(180);

  if (!wm.autoConnect("ESP32-Setup", "setup1234")) {
    ESP.restart();
  }

  strlcpy(mqttServer, pServer.getValue(), sizeof(mqttServer));
  strlcpy(mqttPort,   pPort.getValue(),   sizeof(mqttPort));
  strlcpy(deviceName, pName.getValue(),   sizeof(deviceName));

  if (shouldSave) {
    prefs.putString("server", mqttServer);
    prefs.putString("port",   mqttPort);
    prefs.putString("name",   deviceName);
    Serial.println("Config saved");
  }

  Serial.printf("Server: %s:%s  Name: %s\n", mqttServer, mqttPort, deviceName);
}

void loop() {
}

How it works: the three WiFiManagerParameter objects become extra text boxes on the portal page, pre-filled with the current values. When you save, the callback sets a flag, and after autoConnect() returns, the sketch copies the new values out of the parameters and writes them to flash with Preferences. On the next boot, they are read back first and used as the defaults.

Use Preferences (or LittleFS) rather than EEPROM for this: it is simpler and designed for key-value settings. The stored values connect directly to projects like the MQTT temperature sensor and the smart switch, where the broker address no longer needs to be compiled in.

Image pending: WiFiManager Setup page with extra fields for MQTT server, MQTT port and device name above the Save button

A Button to Reopen the Portal

If someone changes their router or moves the device, they need a way to re-enter setup without re-flashing. The BOOT button (GPIO 0) is handy: hold it for three seconds to open the portal on demand.

#include <WiFi.h>
#include <WiFiManager.h>

const int RESET_BUTTON = 0;     // BOOT button, LOW when pressed
unsigned long pressStart = 0;

void startPortal() {
  WiFiManager wm;
  wm.setConfigPortalTimeout(180);
  if (!wm.startConfigPortal("ESP32-Setup", "setup1234")) {
    Serial.println("Portal timed out");
  }
}

void checkResetButton() {
  if (digitalRead(RESET_BUTTON) == LOW) {
    if (pressStart == 0) pressStart = millis();
    if (millis() - pressStart > 3000) {
      Serial.println("Button held: opening config portal");
      startPortal();
      pressStart = 0;
    }
  } else {
    pressStart = 0;
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(RESET_BUTTON, INPUT_PULLUP);

  WiFiManager wm;
  wm.autoConnect("ESP32-Setup", "setup1234");
}

void loop() {
  checkResetButton();
  // your normal code
}

To wipe saved credentials completely (for example before giving the board away), call wm.resetSettings(); once. You can wire that to a longer button hold, such as ten seconds.

On your own boards, use a separate button on a safe pin such as GPIO 27 instead of GPIO 0, so a stuck bootloader button can never trigger it by accident (see the ESP32 pinout guide).

Image pending: ESP32 with the BOOT button highlighted and the Serial Monitor printing that the config portal opened after a three-second hold

Non-Blocking Mode

autoConnect() freezes your program while the portal is open. If the device should keep doing its job (reading sensors, driving outputs) while waiting to be configured, use non-blocking mode:

WiFiManager wm;

void setup() {
  Serial.begin(115200);
  wm.setConfigPortalBlocking(false);
  wm.setConfigPortalTimeout(0);       // keep portal available, never time out

  if (wm.autoConnect("ESP32-Setup", "setup1234")) {
    Serial.println("Connected");
  } else {
    Serial.println("Portal running in background");
  }
}

void loop() {
  wm.process();     // must be called often to serve the portal
  // your normal code keeps running here
}

process() serves the portal when it is open and does nothing otherwise, so it is cheap to leave in the loop.

Static IP Through the Portal

Combine WiFiManager with a fixed address using setSTAStaticIPConfig, before autoConnect():

wm.setSTAStaticIPConfig(IPAddress(192,168,1,184),
                        IPAddress(192,168,1,1),
                        IPAddress(255,255,255,0));

This hardcodes the network layout again, which defeats part of the purpose, so on distributed devices a router-side DHCP reservation is usually better. The static IP guide explains the trade-off in detail.

Security Notes

  • Put a password on the setup access point. An open portal lets anyone in range read the setup page and replace your WiFi credentials.
  • Set a portal timeout. A portal that closes after a few minutes is far less exposed than one that stays up forever.
  • The saved WiFi password is stored in flash in plain form by the ESP32 stack. Anyone with physical access and a USB cable can read it. That is true of hardcoded credentials too.
  • Do not reuse a valuable WiFi password on a device you hand to other people. Give the device a guest network.

Troubleshooting

The portal does not open automatically on the phone. Some phones need a moment, or the "sign in to network" prompt is hidden. Open a browser and go to http://192.168.4.1. Android may also disconnect from a network that has no internet. If so, tap to stay connected.

The setup network does not appear. Wait a few seconds after boot. Check that the board really entered config mode (look at the Serial Monitor). Make sure the phone is searching 2.4 GHz (the ESP32 access point is 2.4 GHz only).

My network is missing from the list. The ESP32 cannot see 5 GHz networks. A router that broadcasts only 5 GHz will never appear. Move the ESP32 closer and refresh. Hidden networks must be typed manually.

It keeps going back into setup mode. The saved password is wrong, the signal is weak or the router is slow. Raise setConnectTimeout(), re-enter the credentials and check the signal with the WiFi tutorial. If it always happens after a power cut, the router boots slower than the ESP32, and the restart loop from the timeout section solves it.

Compile error: WiFiManager.h not found or WebServer conflicts. Install tzapu's WiFiManager, not an ESP8266-only fork. Remove duplicate similarly named libraries from your libraries folder.

Custom parameter values come back empty. Read them after autoConnect() returns using getValue(), and only save when the save callback fired.

The new credentials do not stick after reflashing. Erasing flash completely (for example by selecting "Erase All Flash" when uploading) removes saved credentials. Choose "Sketch only" if you want to keep them.

For deeper connection issues, use the WiFi troubleshooting guide.

Image pending: Flow chart of the WiFiManager boot logic: try saved WiFi, open the portal on failure, save the credentials, restart or continue

Alternatives and Frequently Asked Questions

Are there other ways to provision WiFi? Yes. Espressif's BLE provisioning and SoftAP provisioning with a phone app, and Improv WiFi (used by ESPHome) let you send credentials over Bluetooth or USB. ESPHome includes a fallback hotspot and captive portal out of the box, see Home Assistant without MQTT.

Does WiFiManager work on ESP8266 too? Yes, it started there. The same code mostly works on both, which is useful if you also build with NodeMCU boards.

Can I theme the portal? Yes. You can change the title, add a custom menu and inject your own CSS. Check the library examples.

Does it slow down boot? Only when it has to open the portal. A normal boot with saved credentials connects as fast as plain WiFi.begin().

Is it suitable for products I sell? It is a good starting point for hobby and small-batch devices. For commercial products, consider a provisioning scheme with a secure per-device setup password and a proper factory-reset procedure.

What should I build with it? Anything that leaves your desk: the MQTT sensor, the smart switch, or an ESP32-CAM streamer.

Image pending: Boxed ESP32 sensor node with a small label showing the setup network name and a reset button on the side
Image pending: Phone screen showing the success message after saving credentials, and the ESP32 Serial Monitor reporting the new IP address

Signal In

Built from what makers search for

The lookups piling up in SolderHub's search bar every day shape what we publish next — new component pages, board guides, and firmware notes, sent out as they go live.

By subscribing you agree to our Privacy Policy. Unsubscribe anytime.