Switching mains voltage or high-current DC loads with a microcontroller requires physical isolation. When sourcing an ESP32 Relay x1 (1-channel) setup, the most common point of failure isn't the code—it is the intersection of 3.3V logic, 5V relay coils, and inductive back-EMF. This guide provides a decision-forward framework for selecting the right 1-channel relay, wiring it safely to an ESP32, and debugging the exact kernel panics that occur when the hardware fights the software.

The Verdict: Which ESP32 Relay x1 Module Should You Buy?

Not all 1-channel relays are created equal. The ESP32 operates at 3.3V logic, while most cheap relay modules on the market are designed for 5V Arduino logic. If you connect a standard 5V relay directly to an ESP32 GPIO without level shifting or optocoupler isolation, you risk back-feeding 5V into the ESP32's 3.3V rail, permanently bricking the SoC.

Your Load Requirement Recommended Module Type Verdict
Switching < 30V DC / < 2A (e.g., LED strips, small pumps) Bare MOSFET Module (IRLZ44N or similar logic-level) Skip the relay. MOSFETs are silent, faster, and don't suffer from contact arcing.
Switching 120V/240V AC up to 10A (e.g., lights, heaters, standard appliances) 5V 1-Channel Relay with Optocoupler (Songle SRD-05VDC-SL-C) DEFAULT PICK. Provides physical galvanic isolation. Costs ~$3.50. Must be wired using the JD-VCC isolation trick (detailed below).
Switching AC inductive loads (motors, compressors) requiring silent operation Omron G3MB-202P Solid State Relay (SSR) with Zero-Cross detection Choose when mechanical relay clicking or contact arcing is unacceptable. Runs ~$6.00.

The Concrete Pick: For 90% of DIY home automation projects, buy the Adafruit 3.3V/5V compatible 1-channel relay module (Product ID 4409) or a generic equivalent that explicitly features a built-in NPN transistor drive and an optocoupler. This allows the ESP32's 3.3V GPIO to trigger the optocoupler LED (which only requires ~5mA) without directly driving the 5V relay coil.

Parts List and Exact Board Variants

The code and wiring diagrams in this guide are strictly calibrated to the following hardware. Substituting boards with different pinouts (like the ESP32-S3 or ESP32-C3) will require adjusting the GPIO definitions.

Component Exact Variant / Model Estimated Cost Critical Notes
Microcontroller ESP32 DevKit V1 (30-pin, ESP32-WROOM-32E) $6.00 Ensure it is the 30-pin version. 38-pin versions have different VIN/GND placements.
Relay Module 5V 1-Channel Relay with Optocoupler (Songle SRD-05VDC-SL-C) $3.50 Must have the blue JD-VCC jumper block for proper 3.3V isolation.
Power Supply 5V 2A USB-C or Micro-USB Wall Adapter $8.00 Do not use unbranded 500mA phone chargers; relay coil inrush will cause brownouts.
Wiring 22 AWG Silicone Stranded Wire (Low Voltage) / 14 AWG THHN (Mains) $12.00 Never use 28 AWG breadboard jumper wires for the relay coil power rails.
Difficulty Rating: 3.5 / 5 (Low-voltage wiring is trivial; mains AC wiring requires strict adherence to safety protocols).

Pin Mapping and Mains Wiring Procedure

⚠️ HIGH VOLTAGE SAFETY WARNING: This procedure involves wiring 120V/240V AC mains. Mains voltage can cause fatal electric shock or start a fire. Always de-energize the circuit at the main breaker panel, use a non-contact voltage tester and a multimeter to verify the wires are dead before touching them. If you are unsure about local electrical codes, consult a licensed electrician. This guide provides NEC-style guidance; your local Authority Having Jurisdiction (AHJ) has final authority.

Low-Voltage Pin Mapping (ESP32 to Relay)

The ESP32 has strict boot-strapping requirements. Pins like GPIO 0, 2, 12, and 15 must be in specific states during boot. We use GPIO 26 because it is a safe, general-purpose output pin with no boot restrictions. You can review the full pin restrictions in the official ESP32-WROOM-32E Datasheet.

ESP32 DevKit V1 Pin Relay Module Pin Wire Color (Recommended) Function
GPIO 26 IN (Signal) Green 3.3V logic trigger (Active LOW on most optocoupler modules)
VIN (5V) JD-VCC (after removing jumper) Red Provides 5V power directly to the relay coil
GND GND Black Common ground reference
3V3 VCC (Logic side) Orange Powers the optocoupler LED (Requires JD-VCC jumper removed)

Step-by-Step Wiring Procedure

  1. Isolate the Logic: Locate the blue jumper labeled JD-VCC on the relay module. Remove it. This breaks the internal connection between the logic side and the coil side, preventing 5V back-feed into the ESP32.
  2. Wire the Low Voltage: Connect ESP32 3V3 to Relay VCC. Connect ESP32 GND to Relay GND. Connect ESP32 VIN (5V) to the Relay JD-VCC pin. Connect GPIO 26 to Relay IN.
  3. Prepare the Mains Load: Strip 1/2 inch of insulation from your 14 AWG THHN wires. Tin the ends if using stranded wire to prevent fraying in the screw terminals.
  4. Wire the High Voltage (Normally Open): Connect your AC Hot (Line) wire to the COM (Common) terminal on the relay. Connect a wire from the NO (Normally Open) terminal to the Hot input of your load (e.g., a light bulb socket). Connect the AC Neutral wire directly to the Neutral input of your load. Never switch the Neutral wire with a relay; always switch the Hot wire.
  5. Verify Connections: Tug test all screw terminals. Ensure no stray copper strands are bridging the COM and NO terminals.

Complete Compilable Code (ESP32 DevKit V1)

This code targets the ESP32 DevKit V1 (ESP32-WROOM-32E). It implements a non-blocking HTTP web server to toggle the relay, includes a Hardware Watchdog Timer (WDT) to recover from WiFi stack lockups, and features explicit error handling for WiFi connection failures.


#include <WiFi.h>
#include <WebServer.h>
#include <esp_task_wdt.h>

// --- HARDWARE PIN DEFINITIONS ---
// GPIO 26 is safe for output and has no boot-strapping conflicts
const int RELAY_PIN = 26; 
const int STATUS_LED = 2; // Built-in LED on most DevKit V1 boards

// --- NETWORK CREDENTIALS ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

WebServer server(80);
bool relayState = false;
unsigned long lastWifiCheck = 0;
const unsigned long wifiCheckInterval = 30000; // Check WiFi every 30s
int wifiFailCount = 0;
const int MAX_WIFI_FAILS = 10;

// --- WEB PAGE HTML ---
const char* htmlPage = R"rawliteral(
<!DOCTYPE html><html><head><title>ESP32 Relay x1</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>body{font-family:sans-serif;text-align:center;margin-top:50px;}
.btn{padding:20px 40px;font-size:24px;color:white;border:none;border-radius:8px;cursor:pointer;}
.on{background-color:#2ecc71;} .off{background-color:#e74c3c;}</style></head>
<body><h1>ESP32 Relay Control</h1>
<p>State: <span id="state">OFF</span></p>
<button class="btn off" id="toggleBtn" onclick="toggle()">Turn ON</button>
<script>function toggle(){fetch('/toggle').then(r=>r.text()).then(s=>{
document.getElementById('state').innerText=s;
let btn=document.getElementById('toggleBtn');
if(s==='ON'){btn.innerText='Turn OFF';btn.className='btn on';}
else{btn.innerText='Turn ON';btn.className='btn off';}});}</script></body></html>
)rawliteral";

void handleRoot() {
  server.send(200, "text/html", htmlPage);
}

void handleToggle() {
  relayState = !relayState;
  // Most optocoupler relay modules are Active LOW
  digitalWrite(RELAY_PIN, relayState ? LOW : HIGH); 
  digitalWrite(STATUS_LED, relayState ? HIGH : LOW);
  server.send(200, "text/plain", relayState ? "ON" : "OFF");
  Serial.printf("Relay toggled to: %s\n", relayState ? "ON" : "OFF");
}

void setupWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi");
  
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 20) {
    delay(500);
    Serial.print(".");
    attempts++;
    esp_task_wdt_reset(); // Feed watchdog during blocking delay
  }
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.printf("\nConnected! IP: %s\n", WiFi.localIP().toString().c_str());
    wifiFailCount = 0;
  } else {
    Serial.println("\nERROR: WiFi connection failed.");
    wifiFailCount++;
    if (wifiFailCount >= MAX_WIFI_FAILS) {
      Serial.println("CRITICAL: Max WiFi fails reached. Rebooting ESP32...");
      delay(1000);
      ESP.restart();
    }
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("\n--- ESP32 Relay x1 Boot Sequence ---");

  // Initialize Hardware Watchdog Timer (5 second timeout)
  esp_task_wdt_init(5, true);
  esp_task_wdt_add(NULL);

  // Configure Pins
  pinMode(RELAY_PIN, OUTPUT);
  pinMode(STATUS_LED, OUTPUT);
  
  // Default to SAFE state (Relay OFF). HIGH = OFF for active-low modules.
  digitalWrite(RELAY_PIN, HIGH); 
  digitalWrite(STATUS_LED, LOW);

  setupWiFi();

  // Setup Web Server Routes
  server.on("/", HTTP_GET, handleRoot);
  server.on("/toggle", HTTP_GET, handleToggle);
  server.begin();
  Serial.println("HTTP Server started.");
}

void loop() {
  esp_task_wdt_reset(); // Feed the watchdog to prevent panic resets
  server.handleClient();

  // Non-blocking WiFi reconnect logic
  if (millis() - lastWifiCheck > wifiCheckInterval) {
    lastWifiCheck = millis();
    if (WiFi.status() != WL_CONNECTED) {
      Serial.println("WARNING: WiFi dropped. Attempting reconnect...");
      setupWiFi();
    }
  }
}

Debugging: First 3 Things to Check When It Fails

When an ESP32 relay circuit fails, the serial monitor usually tells you exactly what went wrong, provided you know how to read the kernel panics. Here are the first three things to check, ranked by frequency.

1. The Brownout Reset Loop

Exact Error String: Brownout detector was triggered (followed by a continuous reboot loop).

  • Cause: The relay coil requires a sudden spike of current (often 100mA+) when energizing. If your USB power supply or the ESP32's onboard AMS1117 voltage regulator cannot deliver this, the 3.3V rail sags below 2.4V, triggering the ESP32's hardware brownout detector.
  • Fix: Ensure you are using a high-quality 5V 2A power supply. Solder a 1000µF electrolytic capacitor across the 5V and GND pins on the ESP32 DevKit to act as a local energy buffer. Verify the flyback diode (usually a 1N4148) is present across the relay coil on the module to suppress inductive kickback.

2. The Watchdog Panic

Exact Error String: Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout)

  • Cause: The ESP32 runs the WiFi stack on Core 0 and your loop() on Core 1. If your code blocks Core 1 for too long (e.g., using delay(5000) or a tight while loop waiting for a sensor), the Interrupt Watchdog Timer assumes the system has locked up and force-resets the chip.
  • Fix: Never use blocking delays in the main loop. Use non-blocking timing with millis() as shown in the code above. If you must run a long computation, insert yield(); or esp_task_wdt_reset(); inside your loop to feed the watchdog.

3. The Boot-Strapping Failure

Exact Error String: rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) or the board simply hangs with a blank serial monitor after a relay click.

  • Cause: You wired the relay to a strapping pin (like GPIO 12) and the relay module's internal pull-up resistor is pulling the pin HIGH during boot. According to the Espressif Bootloader documentation, GPIO 12 must be LOW on boot to select the correct flash voltage (3.3V). If it is HIGH, the ESP32 attempts to run the flash at 1.8V and crashes.
  • Fix: Move the relay signal wire to a safe GPIO like 26, 27, 14, or 25. If you absolutely must use GPIO 12, you have to burn an eFuse to change the flash voltage expectation, which is irreversible and not recommended for beginners.

Extending and Simplifying the Build

Once your baseline ESP32 Relay x1 circuit is stable, you have two clear paths forward depending on your project goals.

How to Extend (Add Smart Home Integration):
The HTTP server provided above is great for local testing, but for a permanent installation, you should migrate to MQTT. Install the PubSubClient library via the Arduino Library Manager. Replace the WebServer logic with an MQTT client that subscribes to a topic like home/livingroom/light/set. This allows seamless integration with Home Assistant, Node-RED, or AWS IoT Core without polling an HTTP endpoint.

How to Simplify (Eliminate Wiring):
If dealing with jumper wires and JD-VCC isolation tricks feels overly complex for your use case, simplify the hardware. Purchase an integrated board like the LilyGO T-Relay (ESP32 + 4x Relays) or a dedicated ESP32-S3 Relay Shield. These boards feature the ESP32, logic-level shifters, and relays on a single PCB with proper trace isolation. They cost roughly $18 to $25, but they completely eliminate the risk of miswiring the optocoupler logic and save hours of bench debugging.