ESP32 Wi-Fi and BLE Provisioning: Onboarding Devices Without Hardcoding Credentials
Every ESP32 tutorial starts the same way: hardcode your Wi-Fi SSID and password directly into the sketch, flash it, and move on. That works fine on a bench, but it falls apart the moment you want to hand a device to someone else, deploy more than one unit, or sell anything. A user who isn't a firmware developer can't open Arduino IDE and edit a string literal every time they change routers. Provisioning is the missing piece: a standardized way for an end user to hand a freshly-flashed ESP32 its network credentials over Bluetooth or a temporary access point, without you ever touching the source code again. Espressif built this into ESP-IDF as a first-class component, and it's dramatically underused by hobbyists who default to hardcoding or, at best, a janky captive portal they wrote from scratch.
Why This Matters Beyond "It's More Professional"
Provisioning isn't just polish. A hardcoded-credentials device is a single-network device — move it, and you're re-flashing. A device you plan to give away, sell on Etsy, or deploy at a friend's house needs a way for someone else to configure it without your laptop. And if you're building anything with an enclosure (see our guide on Designing Enclosures for Electronics Projects), there may not even be an exposed USB port for reflashing after final assembly. Provisioning turns a one-off project into something you can actually distribute.
The Three Provisioning Transports
TransportHow it worksBest forDownsides BLEPhone app connects over Bluetooth LE, scans for networks, sends SSID/password encrypted via the device's protocomm sessionEnclosed products, battery-powered devices, best user experienceRequires a BLE-capable variant (ESP32, S3, C3, C6 all support it); phone app dependency SoftAPESP32 broadcasts its own temporary Wi-Fi AP; user connects to it and submits credentials through a captive portal or appBoards without usable BLE stacks, more universal phone compatibilityUser has to manually switch Wi-Fi networks mid-setup, which trips up non-technical users Console/SerialCredentials sent over USB serial during a provisioning windowBench development, factory programming stationsNot useful for end-user products — needs a cable and a terminalFor anything you intend to hand to someone else, BLE is almost always the right call — it's what Espressif's own reference apps (ESP BLE Provisioning for iOS/Android) are built around, and it avoids the network-switching dance that SoftAP forces on the user.
Setting Up BLE Provisioning with ESP-IDF
ESP-IDF ships the wifi_provisioning component along with the protocomm layer that handles the encrypted handshake. The basic flow in your firmware:
- On boot, check NVS for stored Wi-Fi credentials. If present, connect normally and skip provisioning entirely.
- If absent (first boot, or after a factory reset), call wifi_prov_mgr_init() and start the provisioning service with wifi_prov_mgr_start_provisioning(), specifying WIFI_PROV_SCHEME_BLE.
- Advertise a BLE service name derived from the device's MAC (e.g. PROV_ABC123) so multiple units in the same room don't collide.
- The companion app connects, performs a security handshake, sends the SSID/password, and the device attempts to join.
- On success, wifi_prov_mgr_deinit() tears down the BLE service so it isn't left advertising indefinitely — this matters both for battery life and because an open provisioning window is an attack surface.
The component supports three security schemes, and picking the wrong one is the most common mistake makers run into:
SchemeProtectionWhen to use Security 0None — credentials sent in the clearBench testing only, never a shipped product Security 1Curve25519 key exchange + AES-CTR, optional proof-of-possession (POP) stringGood default for most maker projects Security 2SRP6a password-authenticated key exchangeProducts where you want mutual authentication without a pre-shared POP baked into firmwareThe Arduino-Core Shortcut
If you're not ready to move to ESP-IDF proper, the Arduino core for ESP32 exposes a slimmed-down wrapper via the WiFiProv library, which supports both BLE and SoftAP provisioning with a handful of function calls. It's less configurable — you don't get fine control over the protocomm session — but for a first working prototype it's the fastest path, and it's compatible with the same Espressif companion apps used by the full IDF version.
Storing Credentials Without Leaving Them in Plaintext
By default, provisioned Wi-Fi credentials land in NVS unencrypted, readable by anyone who pulls the flash with a cheap SPI programmer. If your device will ever leave a trusted environment, pair provisioning with NVS encryption (see our guide on ESP32 Secure Boot and Flash Encryption) so the credentials — and any other secrets you store alongside them — aren't recoverable from a stolen or discarded unit.
Designing the Reset Path
Every provisioned product needs a way back to zero. The standard pattern is a long-press on a boot or dedicated button that calls nvs_flash_erase() on the Wi-Fi namespace and reboots into provisioning mode — the same pattern used by nearly every smart plug and bulb on the market. Skip this and you'll field support requests from anyone who changes their router password, moves the device to a new house, or simply mistypes their SSID the first time.
Common Failure Points
SymptomLikely cause Device doesn't show up in the provisioning appBLE not advertising — check that wifi_prov_mgr_start_provisioning() actually returned ESP_OK, and that no other BLE service is already running and hogging the radio Provisioning app connects but handshake failsSecurity scheme mismatch between firmware and app, or wrong POP string entered Device joins Wi-Fi during provisioning but won't reconnect after a power cycleCredentials weren't actually committed to NVS before the provisioning session tore down — check for a missing nvs_commit() in your event handler Provisioning works once, then never triggers again even after a factory reset button pressReset handler is erasing the wrong NVS namespace, or provisioning manager isn't re-initialized after eraseProvisioning is one of those pieces of infrastructure that feels like overkill for a one-off desk gadget but becomes mandatory the moment you build a second unit or give one away. Once it's in place, though, it's copy-paste boilerplate for every ESP32 project after — pair it with OTA updates (see our ESP32 OTA Updates guide) and you've got the two pieces of infrastructure that separate a real product from a bench prototype.
Related Guides
- ESP32 Secure Boot and Flash Encryption: Protecting Firmware and Data on Deployed Devices
- Setting Up Marauder on the ESP32 Wi-Fi Dev Board for Flipper
- ESP32 as a WiFi Sniffer/Deauth Detector
- Debugging ESP32 Firmware with JTAG: OpenOCD, GDB, and ESP-IDF
- ESP32 Bluetooth Mesh Networking: Building Large-Scale BLE Mesh Sensor and Lighting Networks
- Build a Standalone RFID Access Control Reader with a PN532 and ESP32
- Build a Wired ESP32 IoT Sensor Node with W5500 Ethernet for Reliable Uptime
- Build a DIY ESP32 DMX512 Lighting Controller