ElectronicZoologyfield notes from the garage
Wireless • ESP32

How to set a custom MAC
address on ESP32

Board: Any ESP32 variant
Requires: Arduino IDE with ESP32 board support

What this guide builds on

The factory MAC address is burned into eFuse and cannot be changed. What you can change is what the radio actually uses. esp_wifi_set_mac() overrides the active MAC at runtime. The factory value is untouched and comes back on every reboot. You set the override again in setup() on every boot.

The main reason to do this in a maker context is ESP-NOW fleet deployments: if every sender board uses the same known MAC, you hardcode one address in the receiver sketch instead of tracking a different address per board.

How it works

Every ESP32 stores a factory MAC address in eFuse, a one-time-programmable memory region on the chip. The WiFi driver reads that value at boot and loads it into the radio. esp_wifi_set_mac() replaces the value the radio is using without touching eFuse. The factory address is always there underneath. Power cycle the board without calling esp_wifi_set_mac() and it returns to the factory MAC.

The call has to happen after WiFi.mode() and before esp_now_init() or any other WiFi operation. The radio needs to be initialised before you can set the MAC, and it needs the correct MAC in place before ESP-NOW registers peers.

Note: esp_base_mac_addr_set() looks like the right call for this but does not reliably propagate to the active interface on either the C3 or WROOM. Use esp_wifi_set_mac(WIFI_IF_STA, ...) directly.

Rules for valid MAC bytes

A MAC address is made up of six groups of 2 values, each using only 0-9 and A-F. The five groups after the first can be anything you like. The first group has one rule.

The second value of the first group must be one of: 0, 2, 4, 6, 8, A, C, E.

For example, in 0xC0 the second value is 0 - valid. In 0xC1 the second value is 1 - invalid, esp_wifi_set_mac() will reject it.

Those eight values are the even ones. Any of them in that position means the address is unicast, which is required. An odd value in that position makes it a multicast address, which the driver will refuse.

0xC0, 0xAA, 0x02, 0xFE are all valid first groups. 0xC1, 0xFF, 0xAB are not.

Set MAC sketch

This sketch sets a custom MAC, then prints both the custom MAC and the original factory MAC so you can confirm the override is in effect.

Open Serial Monitor at 115200 baud after flashing.

/*
 * We stand on the shoulders of giants when we build
 * with knowledge gained from others' efforts.
 * That doesn't make us giants. Be humble.
 * Create with care. Open source is the way.
 *
 * ESP32 - Set Custom MAC Address
 * --------------------------------
 * Sets the active WiFi MAC at runtime using esp_wifi_set_mac().
 * The factory MAC in eFuse is not changed. Override applies until reboot.
 * Call WiFi.mode() first to initialise the stack, then set MAC before
 * any connection attempt.
 *
 * Allowed values: 0 1 2 3 4 5 6 7 8 9 A B C D E F
 * One rule: the second value of the first group must be
 *   0, 2, 4, 6, 8, A, C, or E.
 *   C0:FF:EE:C0:FF:EE is valid. C1:FF:EE:C0:FF:EE is not.
 *
 * Works on: ESP32 Dev Board, ESP32-C3, ESP32-S3, ESP32-S2
 *
 * Open source - MIT Licence
 * Electronic Zoology - field notes from the garage
 * https://electroniczoology.com/guides/how-to-set-custom-mac-address-esp32
 */

#include <WiFi.h>
#include "esp_wifi.h"

// Custom MAC - change to whatever you want.
// C0:FF:EE = "coffee" - drug of choice.
uint8_t customMAC[] = { 0xC0, 0xFF, 0xEE, 0xC0, 0xFF, 0xEE };

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

  // Initialize Wi-Fi stack
  WiFi.mode(WIFI_STA);
  delay(100);

  Serial.print("Factory MAC: ");
  Serial.println(WiFi.macAddress());

  // Set MAC directly on the STA interface
  esp_err_t result = esp_wifi_set_mac(WIFI_IF_STA, customMAC);
  if (result == ESP_OK) {
    Serial.print("Custom MAC set! Verifying via system driver: ");

    uint8_t currentMAC[6];
    esp_wifi_get_mac(WIFI_IF_STA, currentMAC);

    char macStr[18];
    sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X",
            currentMAC[0], currentMAC[1], currentMAC[2],
            currentMAC[3], currentMAC[4], currentMAC[5]);
    Serial.println(macStr);

  } else {
    Serial.print("MAC set failed, error: ");
    Serial.println(esp_err_to_name(result));
  }
}

void loop() {}

Verify it worked

If the custom MAC is accepted you will see your factory MAC on the first line, then Custom MAC set! Verifying via system driver: followed by C0:FF:EE:C0:FF:EE. The sketch reads back using esp_wifi_get_mac() directly rather than WiFi.macAddress() - on some Arduino-ESP32 core versions WiFi.macAddress() can return zeros even after a successful WiFi.mode() call, so reading from the driver is the reliable way to verify.

Arduino IDE Serial Monitor showing factory MAC and custom coffee MAC C0:FF:EE:C0:FF:EE set successfully

If you see a failure message instead, the most likely cause is an invalid first byte. Check that bit 0 of your first byte is 0. 0xAA is always safe.

Reboot the board without the custom MAC call in setup() and WiFi.macAddress() will return the factory address again. The override is not persistent.

Using it with ESP-NOW

The practical value of MAC spoofing for ESP-NOW is in multi-sender setups. Without it, every board has a unique factory MAC and your receiver sketch either needs a lookup table of every sender's address or you rely on broadcast.

With a fixed custom MAC on every sender, the receiver knows exactly who is sending before you even plug the boards in. Flash the same sender sketch to ten boards and all ten identify themselves identically to the receiver.

The call order in setup() must be:

  1. WiFi.mode(WIFI_STA)
  2. esp_wifi_set_mac()
  3. esp_now_init()

Calling esp_now_init() before esp_wifi_set_mac() means ESP-NOW initialises with the factory MAC. The custom MAC set afterwards does not retroactively update ESP-NOW's internal state.

A sender sketch with a fixed custom MAC looks like this in setup():

WiFi.mode(WIFI_STA);
esp_wifi_set_mac(WIFI_IF_STA, customMAC);

if (esp_now_init() != ESP_OK) {
  Serial.println("ESP-NOW init failed");
  while (true) delay(1000);
}

Everything else in the sender sketch stays the same.

What it does not do

It does not change the factory MAC permanently

eFuse is one-time-programmable and the Arduino ESP32 framework gives no safe way to modify it. The factory value always comes back on reboot.

It does not affect BLE

The BLE radio has its own MAC derived from the same factory base value. esp_wifi_set_mac() only applies to the WiFi interface. BLE MAC spoofing uses a separate API.

It does not work after ESP-NOW is initialised

Set the MAC first, always. If esp_now_init() has already run, the custom MAC will not take effect for ESP-NOW peer registration.

Going Further

Ready to use this in an ESP-NOW project? How to use ESP-NOW to connect two ESP32s without a router →

Take a look at our other guides

Read the MAC address first How to get your ESP32 MAC address →