ESP32: assign a static IP and reserve it in the router
Give an ESP32 a fixed IP with WiFi.config(), reserve it in your router with a DHCP reservation, and stop hunting the network for a device that moved.
Every ESP32 project I run for more than a week ends the same way: the
web dashboard worked, then the router rebooted, then the dashboard was
at a different address and nothing on my desk pointed at the right IP
anymore. The fix is two layers, not one. Assign the IP you want inside
the sketch with WiFi.config(), and make the router agree to never hand
that address to anyone else (a DHCP reservation). This tutorial does
both, in about 20 minutes.
The trap I hit: I set a static IP in code, felt clever, went to bed, and the next morning the device was unreachable. My router had handed my chosen address to my laptop while the ESP32 was rebooting, and two devices were now fighting over one IP. A sketch-side static IP with no router-side reservation is a conflict waiting to happen. Do both halves and this stops being a problem forever.
What you need
Needed
- ESP32 dev board with Wi-Fi (e.g. an ESP32-WROOM-32 devkit): the device getting the fixed address
- Your router’s admin password: the reservation half lives in the router’s web UI, not in code
Nice to have
- A phone with a network scanner app (e.g. Fing, or
nmapon a laptop): confirms which addresses are already taken before you pick - A label maker or masking tape: write the device name and IP on the board itself; future-you will not remember
- Multimeter: only if the board also misbehaves electrically (random reboots can masquerade as network problems)
Wiring
None. This one is a software tutorial; the only “hardware” step is powering the board over USB. (If your project already has sensors wired up, nothing about those connections changes.)
Install
Nothing to install. WiFi.h ships with the ESP32 Arduino core. If you
have not installed the core yet, work through the toolchain tutorial
first (Arduino IDE >> File >> Preferences >> Additional Board Manager
URLs, then Boards Manager >> install esp32).
The code
The mental model: WiFi.config() must be called after WiFi.begin()
starts the driver but the addresses only stick if the router also
reserves the address (covered right after the code). Here is a complete
sketch that connects with a fixed IP and tells you where it landed.
#include <WiFi.h>
const char* ssid = "your-wifi";
const char* password = "your-password";
// Pick an address INSIDE your router's DHCP range but OUTSIDE the pool
// it assigns from (see the router section below). Match the subnet your
// router actually uses; 192.168.1.x is common but not universal.
IPAddress local_IP(192, 168, 1, 50);
IPAddress gateway(192, 168, 1, 1); // your router's address
IPAddress subnet(255, 255, 255, 0); // almost always this
// Optional; usually you can leave DNS to the router:
IPAddress primaryDNS(192, 168, 1, 1);
IPAddress secondaryDNS(1, 1, 1, 1); // Cloudflare, as a fallback
void setup() {
Serial.begin(115200);
delay(1000);
WiFi.mode(WIFI_STA);
WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS);
WiFi.begin(ssid, password);
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("Connected. IP: "); Serial.println(WiFi.localIP());
Serial.print("Gateway: "); Serial.println(WiFi.gatewayIP());
Serial.print("Subnet: "); Serial.println(WiFi.subnetMask());
Serial.print("DNS: "); Serial.println(WiFi.dnsIP());
}
void loop() {
// Reconnect logic belongs here in a real device (see the Wi-Fi
// tutorial for a battle-tested version).
}
One line does the work: WiFi.config(...) before WiFi.begin(). The
order matters. Calling config() after the connection is up can leave
you with the old DHCP address until the next reconnect, which looks
like “the code did nothing.”
The router half (the part that makes it stick)
The sketch pins the address, but the router does not know that. If the ESP32 reboots and another device grabs 192.168.1.50 first, you get an IP conflict. The fix is a DHCP reservation: tell the router “this MAC address always gets this IP.”
Router UIs differ, but the path is usually one of these:
- TP-Link: Advanced >> Network >> DHCP Server >> Address Reservation
- Asus: Advanced Settings >> LAN >> DHCP Server >> Manually Assigned IP
- Netgear: Advanced >> Setup >> LAN Setup >> Address Reservation
- OpenWrt: Network >> Interfaces >> LAN >> DHCP Server >> Static Leases
You need two values from the ESP32: its MAC address (printed by this
sketch if you add Serial.println(WiFi.macAddress()); in setup) and the
IP you chose. Enter the MAC, enter the IP, save, reboot the ESP32.
Now the two halves agree. The ESP32 asks for 192.168.1.50 every time, and no other device is ever offered it, because the router itself is holding the reservation.
Static IP vs DHCP reservation vs mDNS
| Approach | Set where | Survives router swap | Good for |
|---|---|---|---|
WiFi.config() static IP | sketch | no (new subnet breaks it) | devices you never move |
| DHCP reservation | router | yes (IP re-pins via MAC) | everything, honestly |
| mDNS name | sketch | yes, but needs .local support | human-friendly access |
If I am being honest: the reservation alone is usually enough, because
DHCP hands out the same address to the same MAC every time once a lease
exists. The WiFi.config() call is belt-and-suspenders for the case
where the router reboots faster than the ESP32 (e.g. a power blink) and
the lease table resets. Doing both costs one line and one router form.
What you learned
WiFi.config(ip, gateway, subnet, dns)beforeWiFi.begin()pins the device’s address; the call order is the whole trick.- A sketch-side static IP without a router-side reservation eventually causes an IP conflict with whatever grabs the address first.
- A DHCP reservation in the router (keyed to the MAC address) makes the IP predictable for every device on your network, not just this one.
- mDNS (
device.local) is the friendlier overlay on top of a stable IP; they solve different halves of the same problem.
When something breaks
- The ESP32 never connects after adding
WiFi.config(). One of the four addresses does not match your network. The subnet mask and gateway are the usual offenders (e.g. some ISPs hand out 192.168.0.x, not 192.168.1.x). Check the gateway and mask on a working computer (Windows:ipconfig; macOS/Linux:ip route) and copy what you see. - Connected at the old IP, not the new one.
WiFi.config()was called afterWiFi.begin()took effect, or the sketch uploaded without saving. Re-flash, and power-cycle the board (not just a soft reset) so the driver starts clean. - “Connected” but no internet. DNS. If you set
primaryDNSto the router, make sure the router actually forwards DNS; if you set it to a public resolver, make sure that resolver is reachable. Setting both DNS entries to the router’s address is the safe default for a home LAN. - Everything works, then stops after a router reboot. Your reservation is missing or keyed to the wrong MAC (boards with two MAC addresses, one for 2.4GHz and one for Ethernet-style interfaces, can surprise you; copy the MAC the sketch prints while connected to your actual network).
- Two devices fight over one IP. You skipped the reservation. Add it, or move your static IP outside the router’s DHCP pool entirely (e.g. pool 100-199, statics at 10-49), which is the cleanest long-term layout.
What to build next
- The mDNS tutorial adds
device.localnames on top of your now stable address, so you can stop typing IPs entirely. - The sensor dashboard web server tutorial is the project this unlocks: a dashboard at a fixed, known address that other tools can scrape forever.
- The MQTT publish/subscribe tutorial prefers stable addressing too; brokers and clients re-find each other much more reliably when the addresses do not wander.
- The NTP time sync tutorial assumes the device can always reach the gateway and DNS you just configured, which is exactly what a pinned address plus a reservation guarantees.