Electronics Design AU
ESP32

How Do You Set Up Wi-Fi and Provision an ESP32 Device?

Last updated 28 June 2026 · 7 min read

Direct Answer

Setting up Wi-Fi on an ESP32 in ESP-IDF requires initialising the NVS, TCP/IP stack, and event loop; creating a Wi-Fi station configuration with the SSID and password; and registering event handlers to act on WIFI_EVENT_STA_CONNECTED and IP_EVENT_STA_GOT_IP. For products that need to accept Wi-Fi credentials from the user in the field (provisioning), ESP-IDF provides two approaches: SoftAP provisioning (the ESP32 temporarily creates a Wi-Fi access point; the user connects via phone and sends credentials over HTTP) and BLE provisioning (the ESP32 advertises a BLE service; the Espressif provisioning app sends credentials over BLE). After provisioning, credentials are stored in NVS (Non-Volatile Storage) and reloaded on subsequent boots.

Detailed Explanation

Connecting an ESP32 to Wi-Fi in ESP-IDF involves more setup than the Arduino WiFi.begin() one-liner — but the extra structure provides event-driven handling, reconnection control, and clean integration with the rest of the FreeRTOS-based firmware stack.

Station Mode: Connecting to an Existing Wi-Fi Network

Station mode (STA) is the mode used for products that connect to an existing Wi-Fi router. The minimal ESP-IDF setup:

#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"

static void wifi_event_handler(void *arg, esp_event_base_t base,
                                int32_t event_id, void *event_data) {
    if (base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        esp_wifi_connect();    /* reconnect on drop (add retry logic for production) */
    } else if (base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
        /* IP address is now assigned; start application-level networking here */
    }
}

void wifi_init_sta(void) {
    nvs_flash_init();                         /* required for Wi-Fi to store calibration data */
    esp_netif_init();                         /* initialise TCP/IP stack */
    esp_event_loop_create_default();          /* create the default event loop */
    esp_netif_create_default_wifi_sta();      /* create the default STA netif */

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&cfg);

    esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL);
    esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_event_handler, NULL);

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = "MyNetwork",
            .password = "MyPassword",
        },
    };
    esp_wifi_set_mode(WIFI_MODE_STA);
    esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
    esp_wifi_start();    /* triggers WIFI_EVENT_STA_START → connect() */
}

The event loop is central to ESP-IDF's Wi-Fi: rather than blocking on a connection function, the application registers handlers and reacts to events asynchronously. This keeps the Wi-Fi connection management decoupled from application logic.

Key events in station mode:

  • WIFI_EVENT_STA_START — Wi-Fi stack started; call esp_wifi_connect() here.
  • WIFI_EVENT_STA_CONNECTED — associated with the AP; IP not yet assigned.
  • IP_EVENT_STA_GOT_IP — DHCP has assigned an IP address; networking is ready.
  • WIFI_EVENT_STA_DISCONNECTED — connection lost; check wifi_event_sta_disconnected_t.reason for cause.

Access Point Mode: Hosting a Wi-Fi Network

In AP mode, the ESP32 creates its own Wi-Fi network. This is used for provisioning and local configuration interfaces:

void wifi_init_ap(void) {
    esp_netif_create_default_wifi_ap();    /* create AP netif */
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&cfg);

    wifi_config_t ap_config = {
        .ap = {
            .ssid = "ESP32-Setup",
            .ssid_len = strlen("ESP32-Setup"),
            .channel = 1,
            .password = "setuppassword",     /* min 8 chars for WPA2; empty string = open */
            .max_connection = 4,
            .authmode = WIFI_AUTH_WPA2_PSK,
        },
    };
    esp_wifi_set_mode(WIFI_MODE_AP);
    esp_wifi_set_config(WIFI_IF_AP, &ap_config);
    esp_wifi_start();
}

A common pattern is to combine STA + AP modes simultaneously (WIFI_MODE_APSTA), so the ESP32 can be connected to the upstream Wi-Fi network while simultaneously hosting a local access point for a configuration interface. This is used in range extenders and mesh setup flows.

Wi-Fi Provisioning: Getting Credentials onto the Device

For mass-produced products, credentials cannot be hardcoded. The user must supply their Wi-Fi SSID and password. ESP-IDF provides the wifi_provisioning component for this.

SoftAP Provisioning

The ESP32 starts in AP mode. The user's phone connects to the ESP32's AP and a companion app (the Espressif Provisioning app, or a custom app using the esp-idf-provisioning mobile SDK) sends the credentials via a secured HTTP endpoint. After receiving and storing the credentials in NVS, the ESP32 disables AP mode and connects to the user's router.

#include "wifi_provisioning/manager.h"
#include "wifi_provisioning/scheme_softap.h"

wifi_prov_mgr_config_t config = {
    .scheme = wifi_prov_scheme_softap,
    .scheme_event_handler = WIFI_PROV_EVENT_HANDLER_NONE,
};
wifi_prov_mgr_init(config);

bool provisioned = false;
wifi_prov_mgr_is_provisioned(&provisioned);

if (!provisioned) {
    wifi_prov_mgr_start_provisioning(WIFI_PROV_SECURITY_1, "pop-abcd1234", "PROV_ESP32", NULL);
    /* pop = proof-of-possession: a short code printed on the device label, 
       ensures only the legitimate user can provision this device */
} else {
    wifi_prov_mgr_deinit();
    /* Connect using stored credentials */
}

BLE Provisioning

BLE provisioning uses the same wifi_prov_mgr API but with a different scheme:

#include "wifi_provisioning/scheme_ble.h"

wifi_prov_mgr_config_t config = {
    .scheme = wifi_prov_scheme_ble,
    .scheme_event_handler = WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM,
};
wifi_prov_mgr_init(config);
/* BLE is automatically released after provisioning completes */

The Espressif provisioning app (available on iOS and Android) handles both SoftAP and BLE flows. For a custom white-label app, Espressif's esp-idf-provisioning-android and esp-idf-provisioning-ios SDKs provide the protocol implementation.

Proof-of-Possession (PoP)

The pop parameter in wifi_prov_mgr_start_provisioning() is a short identifier — often a code printed on the device label or displayed on an OLED screen. It prevents neighbouring devices from being provisioned by someone who can see the ESP32's BLE advertisement or AP. The PoP is used to derive an SRP (Secure Remote Password) session key, so the provisioning transaction is authenticated without requiring a pre-shared TLS certificate.

HTTP Server for Local Endpoints

Once the ESP32 is connected to Wi-Fi, esp_http_server provides a lightweight HTTP server for local REST API endpoints, firmware update upload, or web UI:

#include "esp_http_server.h"

static esp_err_t status_handler(httpd_req_t *req) {
    const char *json = "{\"status\":\"ok\",\"uptime_s\":1234}";
    httpd_resp_set_type(req, "application/json");
    httpd_resp_send(req, json, HTTPD_RESP_USE_STRLEN);
    return ESP_OK;
}

httpd_handle_t start_webserver(void) {
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    httpd_handle_t server = NULL;
    httpd_start(&server, &config);

    httpd_uri_t status_uri = {
        .uri = "/api/status",
        .method = HTTP_GET,
        .handler = status_handler,
    };
    httpd_register_uri_handler(server, &status_uri);
    return server;
}

For HTTPS (TLS), use esp_https_server with an X.509 certificate. For products accessible only on the local network, a self-signed certificate is common — but the certificate must be pinned in the client app to avoid man-in-the-middle exposure.

For production ESP32 products requiring Wi-Fi integration, provisioning flows, HTTPS connectivity, and cloud backend integration, Zeus Design's firmware team delivers complete connected firmware from hardware bring-up through to cloud commissioning.

Design Considerations

  • Handle WIFI_EVENT_STA_DISCONNECTED with backoff, not infinite retry. Calling esp_wifi_connect() in a tight loop on disconnection can flood the network with association requests and starve other tasks. Implement exponential backoff (500 ms, 1 s, 2 s, ..., max 30 s) with a retry limit before entering an error state.
  • Use power save mode for battery applications. Call esp_wifi_set_ps(WIFI_PS_MIN_MODEM) after connecting to enable modem sleep between DTIM beacon intervals. This reduces average Wi-Fi current from ~80 mA continuous to ~20 mA. For deeper power savings, use deep sleep with periodic wake and reconnect only when data needs to be sent.
  • NVS encryption for credentials at rest. Without NVS encryption, Wi-Fi credentials stored in flash can be read by anyone with physical access to the device (via esptool.py flash read). Enable NVS encryption in idf.py menuconfig → Security features → for any product where credential theft is a concern.
  • ADC2 channels cannot be read while Wi-Fi is active. ADC2 shares the analog front end with the Wi-Fi radio — reading any ADC2 channel while Wi-Fi is running returns ESP_ERR_TIMEOUT. Route all sensor ADC inputs to ADC1 channels (GPIO32–GPIO39 on the classic ESP32) from the start of hardware design. For the full GPIO and ADC peripheral guide including attenuation settings and the ESP-IDF v5.x oneshot API, see How Do You Use GPIO, ADC, and Timers on the ESP32?.

Common Mistakes

  • Not calling nvs_flash_init() before Wi-Fi init. The Wi-Fi stack stores calibration data and other state in NVS. Missing this call produces a ESP_ERR_NVS_NOT_INITIALIZED error on first call to esp_wifi_init(). Call nvs_flash_init() at the start of app_main().
  • Blocking the provisioning task until connected. The provisioning manager and Wi-Fi event loop run on the default event loop task; blocking the task that calls wifi_prov_mgr_start_provisioning() prevents events from being processed. Use an event group or semaphore to wait for the IP_EVENT_STA_GOT_IP event from a separate waiting task.
  • Ignoring the DHCP lease expiry. On long-running devices, the DHCP lease can expire and be reassigned. If the device does not renew the lease, IP_EVENT_STA_LOST_IP fires — handle this event and re-register sockets or re-establish MQTT connections that are bound to the old IP.
  • Using esp_wifi_connect() without checking if already connected. Calling esp_wifi_connect() when already in WIFI_MODE_STA and connected produces an error (ESP_ERR_WIFI_CONN). Check the state or guard the call with a flag to avoid spurious error logs.

Frequently Asked Questions

Should I store Wi-Fi credentials in NVS or in firmware flash?
Always use NVS (Non-Volatile Storage). Hardcoding credentials in firmware flash means every device ships with the same credentials (a security problem for any product sold to end users) and any credential change requires a firmware update. NVS provides a key-value store in a dedicated flash partition that persists across reboots and can be erased independently of the firmware. ESP-IDF's Wi-Fi provisioning components write to NVS by default. The NVS partition can also be encrypted with a device-specific key using NVS encryption — important for products that must protect credentials at rest.
How do I handle Wi-Fi reconnection after the router reboots or the connection drops?
Register a handler for WIFI_EVENT_STA_DISCONNECTED and call esp_wifi_connect() from within it, with an exponential backoff or limited retry count. Do not retry infinitely without a maximum count or delay — this prevents connection loops that starve other tasks. For production firmware, implement a maximum retry count (e.g. 5 attempts with increasing delay), then fall back to a visual indicator (LED blink) or enter a safe mode where provisioning is re-enabled. The WIFI_REASON codes in the disconnect event provide the specific cause (auth failure vs AP not found vs idle timeout) for appropriate handling.
What is the difference between SoftAP provisioning and BLE provisioning?
SoftAP provisioning: the ESP32 acts as a Wi-Fi access point (no BLE required); the user's phone connects to this AP and a companion app sends credentials over HTTP. Works on all devices with Wi-Fi; requires the user to manually switch phone Wi-Fi to the ESP32 AP. BLE provisioning: the ESP32 advertises a BLE GATT service; the Espressif app scans for it and sends credentials over BLE without requiring the phone to disconnect from its normal Wi-Fi. Provides better UX on modern smartphones but requires a BLE-capable ESP32 variant (the S2 has no BLE and cannot use BLE provisioning). For Matter products, BLE provisioning is mandatory — Matter commissioning is always done via BLE.

References

Related Questions

Related Forum Discussions