This tutorial is a step-by-step guide that covers how to build a standalone ESP32 or ESP8266 NodeMCU Web Server that controls any relay module. We’ll create an ESP32/ESP8266 Web Server that is mobile responsive and it can be accessed with any device with a browser in your local network.
Introducing Relays
A relay is an electrically operated switch and like any other switch, it that can be turned on or off, letting the current go through or not. It can be controlled with low voltages, like the 3.3V provided by the ESP GPIOs and allows us to control high voltages like 12V, 24V or mains voltage (230V in Europe and 120V in the US).
There are different relay modules with a different number of channels. You can find relay modules with one, two, four, eight and even sixteen channels. The number of channels determines the number of outputs you can control.
Get a relay module:
- 5V 2-channel relay module (with optocoupler)
- 5V 1-channel relay module (with optocoupler)
- 5V 8-channel relay module (with optocoupler)
- 5V 16-channel relay module (with optocoupler)
- 3.3V 1-channel relay module (with optocoupler)
For an in-depth guide on how a relay module works, read:
- Guide: ESP32 Relay Module – Control AC Appliances using Arduino IDE (Web Server)
- Guide: ESP8266 NodeMCU Relay Module – Control AC Appliances using Arduino IDE (Web Server)
Control Relay Modules with Multiple Channels using an ESP32/ESP8266 Web Server
With this web server code, you can control as many relays as you want via web server whether they are configured as normally opened or as normally closed. You just need to change a few lines of code to define the number of relays you want to control and the pin assignment.
To build this web server, install the ESP32/ESP8266 board add-on and the next libraries in your Arduino IDE.
Installing the ESPAsyncWebServer, AsyncTCP, and ESPAsyncTCP Libraries
- Click here to download the ESPAsyncWebServer library
- Click here to download the AsyncTCP library (ESP32)
- Click here to download the ESPAsyncTCP library (ESP8266 NodeMCU)
In your Arduino IDE, to install the libraries go to Sketch > Include Library > Add .ZIP library… and select the library you’ve just downloaded.
After installing the required libraries, copy the following code to your Arduino IDE.
/*********
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp8266-relay-module-ac-web-server/
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*********/
// Import required libraries
#include "ESP8266WiFi.h"
#include "ESPAsyncWebServer.h"
// Set to true to define Relay as Normally Open (NO)
#define RELAY_NO true
// Set number of relays
#define NUM_RELAYS 5
// Assign each GPIO to a relay
int relayGPIOs[NUM_RELAYS] = {5, 4, 14, 12, 13};
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
const char* PARAM_INPUT_1 = "relay";
const char* PARAM_INPUT_2 = "state";
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h2 {font-size: 3.0rem;}
p {font-size: 3.0rem;}
body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
.switch {position: relative; display: inline-block; width: 120px; height: 68px}
.switch input {display: none}
.slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 34px}
.slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 68px}
input:checked+.slider {background-color: #2196F3}
input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
</style>
</head>
<body>
<h2>ESP Web Server</h2>
%BUTTONPLACEHOLDER%
<script>function toggleCheckbox(element) {
var xhr = new XMLHttpRequest();
if(element.checked){ xhr.open("GET", "/update?relay="+element.id+"&state=1", true); }
else { xhr.open("GET", "/update?relay="+element.id+"&state=0", true); }
xhr.send();
}</script>
</body>
</html>
)rawliteral";
// Replaces placeholder with button section in your web page
String processor(const String& var){
//Serial.println(var);
if(var == "BUTTONPLACEHOLDER"){
String buttons ="";
for(int i=1; i<=NUM_RELAYS; i++){
String relayStateValue = relayState(i);
buttons+= "<h4>Relay #" + String(i) + " - GPIO " + relayGPIOs[i-1] + "</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"" + String(i) + "\" "+ relayStateValue +"><span class=\"slider\"></span></label>";
}
return buttons;
}
return String();
}
String relayState(int numRelay){
if(RELAY_NO){
if(digitalRead(relayGPIOs[numRelay-1])){
return "";
}
else {
return "checked";
}
}
else {
if(digitalRead(relayGPIOs[numRelay-1])){
return "checked";
}
else {
return "";
}
}
return "";
}
void setup(){
// Serial port for debugging purposes
Serial.begin(115200);
// Set all relays to off when the program starts - if set to Normally Open (NO), the relay is off when you set the relay to HIGH
for(int i=1; i<=NUM_RELAYS; i++){
pinMode(relayGPIOs[i-1], OUTPUT);
if(RELAY_NO){
digitalWrite(relayGPIOs[i-1], HIGH);
}
else{
digitalWrite(relayGPIOs[i-1], LOW);
}
}
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
// Print ESP8266 Local IP Address
Serial.println(WiFi.localIP());
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});
// Send a GET request to <ESP_IP>/update?relay=<inputMessage>&state=<inputMessage2>
server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
String inputMessage;
String inputParam;
String inputMessage2;
String inputParam2;
// GET input1 value on <ESP_IP>/update?relay=<inputMessage>
if (request->hasParam(PARAM_INPUT_1) & request->hasParam(PARAM_INPUT_2)) {
inputMessage = request->getParam(PARAM_INPUT_1)->value();
inputParam = PARAM_INPUT_1;
inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
inputParam2 = PARAM_INPUT_2;
if(RELAY_NO){
Serial.print("NO ");
digitalWrite(relayGPIOs[inputMessage.toInt()-1], !inputMessage2.toInt());
}
else{
Serial.print("NC ");
digitalWrite(relayGPIOs[inputMessage.toInt()-1], inputMessage2.toInt());
}
}
else {
inputMessage = "No message sent";
inputParam = "none";
}
Serial.println(inputMessage + inputMessage2);
request->send(200, "text/plain", "OK");
});
// Start server
server.begin();
}
void loop() {
}
Define Relay Configuration
Modify the following variable to indicate whether you’re using your relays in normally open (NO) or normally closed (NC) configuration. Set the RELAY_NO variable to true for normally open os set to false for normally closed.
#define RELAY_NO true
Define Number of Relays (Channels)
You can define the number of relays you want to control on the NUM_RELAYS variable. For demonstration purposes, we’re setting it to 5.
#define NUM_RELAYS 5
Define Relays Pin Assignment
In the following array variable you can define the ESP GPIOs that will control the relays.
int relayGPIOs[NUM_RELAYS] = {5, 4, 14, 12, 13};
The number of relays set on the NUM_RELAYS variable needs to match the number of GPIOs assigned in the relayGPIOs array.
Network Credentials
Insert your network credentials in the following variables.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Wiring a Relay Module to an ESP Board
For demonstration purposes, we’re controlling 5 relay channels. Wire the ESP boards to the relay module as shown in the next schematic diagrams.
ESP32 Wiring
ESP8266 NodeMCU Wiring
Warning: most relay projects deal with mains voltage. Misuse can result in serious injuries. If you’re not familiar with mains voltage ask someone who is to help you out. While programming the ESP or wiring your circuit make sure everything is disconnected from mains voltage. Alternatively, you can use a 12V power source to control 12V appliances.
Demonstration
After making the necessary changes, upload the code to your ESP. Open the Serial Monitor at a baud rate of 115200 and press the ESP RST button to get its IP address (make sure that you circuit is unplugged from mains voltage).
Open a browser in your local network and type the ESP IP address to get access to the web server. You should get something as follows with as many buttons as the number of relays you’ve defined in your code.
Now, you can use the buttons to control your relays remotely using your smartphone.
Enclosure for Safety
For a final project, make sure you place your relay module and ESP inside an enclosure to avoid any AC pins exposed.
Watch the Video
Watch the following video for a complete tutorial and live demonstration.
Wrapping Up
Using relays with the ESP is a great way to control AC household appliances remotely. You can also read our complete guides to learn more about relay modules:
- ESP32 Relay Module – Control AC Appliances using Arduino IDE (Web Server)
- ESP8266 NodeMCU Relay Module – Control AC Appliances using Arduino IDE (Web Server)
Learn more about the ESP boards with our resources:
- Home Automation using ESP8266
- Learn ESP32 with Arduino IDE
- Free ESP32 Projects, Tutorials and Guides
- Free ESP8266 NodeMCU Projects, Tutorials and Guides
Thanks for reading.
Lots of good info. Tks.
One issue I’ve been grappling with is how to make projects like the above securely visible on the internet. I’ve tried building an https server but without any success.
A tutorial on https would be greatly appreciated.
Hi Peter.
Note that this project is only accessible in your network.
So, whether you use HTTP or HTTPS, if someone is in your network they can access the web server.
However, in the future, we’ll be posting authentication and https guides.
Thanks for the suggestion.
Regards,
Sara
Dear Sara, this is really a great starting point for many projects. But I’m looking out for a way to have this controllable via a hosted server – ie outside my home network. With a one-time authentication ideally!
thanks
Keshav
Hi.
The most similar project we have about what you’re looking for is this: https://randomnerdtutorials.com/control-esp32-esp8266-gpios-from-anywhere/
I hope this helps.
Regards,
Sara
I found the solution curl “http://IPadress/update?relay=5&state=1HTTP/1.1”
For ESP8266 expansion via MCP23017 for controlling 16 channel tutorial will be useful.
Saving state to EEPROM during power cycle would be an added advantage.
Also, 🤔 feedback of current relay status in webserver.
Please help with tutorial for the above
Thank a lot
-Kumaran
Hi Rui and Sara
Instead of on/off button if I want to control like push button.Once button pressed then relay trigger for 1 sec.
What is code need to change?
Hello Razu,
I recommend following one of these guides:
The three files I downloaded, one of them ESP8266WiFi does not load me, what can I do?
What do you mean?
I didn’t understand your issue. Can you elaborate?
Regards,
Sara
Hi again.
Its working fine, thanks.
My board has 8 relay, can please help me witch 3 more GPIO can i add to make the 8 relay working in my ESP32.
Thanks for everything.
Hi Paulo.
Take a look at the ESP32 GPIO guide to choose your GPIOs: https://randomnerdtutorials.com/esp32-pinout-reference-gpios/
Regards,
Sara
Thanks Sara i will take a look
I have noticed that on older devices such as android froyo 2.2 the button would only work one time and after that, would operate erratically if at all. I solved the problem by adding:
after the tag.
I have noticed that on older devices such as android froyo 2.2 the button would only work one time and after that, would operate erratically if at all. I solved the problem by adding:
meta http-equiv=’cache-control’ content=’no-cache’
meta http-equiv=’expires’ content=’0′
meta http-equiv=’pragma’ content=’no-cache’
inside html tags, after the head tag.
Hi.
Tanks for sharing that solution.
Regards,
Sara
Hi Sara.
I have found that the no-cache tags are not as reliable as I had hoped for in older cell browsers. Adding Math.random() seems to be a more reliable way to prevent reusing cached responses such as:
if(element.checked){ xhr.open(“GET”, “/update?relay=” + element.id + “&state=1” + Math.random(), true); xhr.send();
I would be interested in learning better ways.
Love your blog.
Regards
Hola, muchas gracias por tu trabajo, en una hora tengo controlada mi casa.
Solo una consulta que probablemente solo me suceda a mi, al conectarme a la web los botones parten encendidos, (en azul) al apagarlos activan los relés, quiere decir que están configurados al reves.
¿Me ayudas a corregiro por favor?
Nuevamente muchas gracias y feliz año nuevo.
Me auto respondo:
// Set to true to define Relay as Normally Open (NO)
#define RELAY_NO false
Era solo eso, jajaja.
Great!
Hi,
What is the best way to auto toogle off ater X seconds?
Thank you very much for sharing.
Hi.
You need to create a JavaScript function to do that.
Maybe this project may help: https://randomnerdtutorials.com/esp32-esp8266-web-server-timer-pulse/
Regards,
Sara
Obrigado Sara.
Mais do que perfeito.
Este também serve para mim:
https://randomnerdtutorials.com/esp32-esp8266-web-server-outputs-momentary-switch/
O vosso trabalho está excelente!
Hola.
Quisiera añadirle un BMP180 al servidor.
Lo que no sé es como inserto los datos obtenidos del BMP180 dentro de la definicion de la pagina web:
const char index_html[] PROGMEM = R”rawliteral(
html {font-family: Arial; display: inline-block; text-align: center;}
h2 {font-size: 3.0rem;}
p {font-size: 3.0rem;}
body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
.switch {position: relative; display: inline-block; width: 120px; height: 68px}
.switch input {display: none}
.slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 34px}
.slider:before {position: absolute; content: “”; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 68px}
input:checked+.slider {background-color: #2196F3}
input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
Control 4 zonas
%BUTTONPLACEHOLDER%
function toggleCheckbox(element) {
var xhr = new XMLHttpRequest();
if(element.checked){ xhr.open(“GET”, “/update?relay=”+element.id+”&state=1”, true); }
else { xhr.open(“GET”, “/update?relay=”+element.id+”&state=0″, true); }
xhr.send();
}
)rawliteral”;
Gracias.
Hi Sara,
I try to put two scripts in one. This one with “ESP32 DHT11/DHT22 Web Server – Temperature and Humidity using Arduino IDE”. I need to check dynamicaly temperature and remote control relay in one web page. I think ESPAsyncWebServer library is good for it, but I try many various and one thing is working and other not.
The update function not working like before. Can you please explain how to put both scripts in one to work it good.
Thank you very much
Peter
Hi dear,
Here is very great 🙂
My question is, how can i do side by side?
Thank you!
hi Sara
Why when i open serial montitor, it constantly saying connecting to wifi, then when i press reset button, it prints
rll�r$�n�l�b|���rb�b�nnlnnbbp�$blrlp�n�l�bn�n��b��nn’l�l
�nn$
nr���nrr�p�n�r�bbn�nb��nn'l
�nn$nr���nrl
r��nrl�$rl
��n�`Hi.
Check the baud rate of your serial monitor.
It must be 115200.
Regards,
Sara
Please add auto update code. Like Toggle switch pressed in one device automatically updates on other device.
Thanks and Regards..
Hi.
For that, you need to use websockets.
You can use this tutorial as a reference: https://randomnerdtutorials.com/esp32-websocket-server-arduino/
Regards,
Sara
Hi Sara,
Thank you very very much for this code.
I have made 2 esp8266 boards to control 2 relays each, total 4 relays.
I have assigned IP address A to board 1 and IP address B to board 2.
Those 2 boards are connected to my wifi router, and I access the IP A and IP B from my phone to control those relays.
Now I am setting up a third esp8266 board (say that the IP Address will be C) to become Access Point and Webserver to control another 2 relays.
I wish that board 1 and board 2 can connect to board 3, and expecting to see all 6 buttons when accessing board 3 through IP address C to control all 6 relays. So that I can get rid of the router.
All 3 boards are placed in 3 different location.
Would you please help me? I am still learning those html css and javascript but it may take few months untill I can write good code for this purpose.
Thanks
Javen
Hi Sara and Rui,
I have used this example in two projects and they work great. I also called-up the webpage on my IPad and that also worked great in Safari, but since my IPad updated to IOS 18, it does not communicate anymore. In Safari I get “page not opened” & “cannot parse response”. In Chrome on my IPad it still works, but not in Safari. Did you hear of the problem and is there a solution/patch for?
Thanks in advance,
Siebe
Hi.
I’m not sure what the problem might be.
Do you get more specific errors on the console?
Regards,
SAra
Hi Sara,
I only have an IPad and not a Mac, so I cannot get a console visible. I do have web-inspector, but that ony works if the page get loaded and that fails. I presume that Apple has changed something in IOS18 that expects something more then send by the ESP8266 webserver. I even reduced the “html” to only 1 line of text, but even that failed. Luckily it still works on Chrome, but I found it annoying that it stop working on Safari, but yeah thats with Apple and Microsoft.
I’m sorry for the issue.
I think it’s probably because the web server doesn’t have a certificate.
Maybe it doesn’t allow you to access web pages without certificate for security reasons, even though they are on the same network.
Regards,
Sara