ESP32 Webserver einrichten: LED, Relais & Ausgänge per Browser steuern
Stell dir vor, du zückst dein Handy, öffnest den Browser und schaltest damit die Lampe im Wohnzimmer, den Ventilator oder die Kaffeemaschine ein – ganz ohne App, ohne Cloud, ohne monatliche Gebühr. Genau das kann dein ESP32 von Haus aus. In diesem Tutorial baust du einen kompletten Webserver, der eine schicke Bedienoberfläche direkt aus dem Chip ausliefert. Wir fangen mit einer einzelnen LED an, bauen die Steuerung Schritt für Schritt aus, machen sie per WebSocket in Echtzeit reaktiv und schalten am Ende echte 230-V-Geräte über ein Relais. Fertiger Code, saubere Verkabelung und alle Stolperfallen inklusive.
Warum ein Webserver auf dem ESP32?
Der große Vorteil gegenüber fertigen Smart-Home-Systemen: Der ESP32 ist Server und Steuerung in einem. Er spannt ein eigenes kleines Web-Interface auf, das jedes Gerät in deinem WLAN im Browser öffnen kann – Handy, Tablet, Laptop, egal. Keine App-Installation, keine Registrierung, keine Daten, die irgendwo in einer fremden Cloud landen.
Das macht den ESP32 perfekt für alles, was du lokal und zuverlässig schalten willst:
- Licht, Lüfter oder Pumpen ein- und ausschalten
- Ein Garagentor oder eine Rollo-Steuerung auslösen
- Ein Modell, eine Beleuchtung oder eine Deko fernsteuern
- Sensoren auslesen und die Werte live im Browser anzeigen
Es gibt zwei Betriebsarten, die du kennen solltest. Im Station-Modus (STA) hängt sich der ESP32 in dein vorhandenes WLAN ein und bekommt eine IP-Adresse aus deinem Router – so ist er von allen Geräten im Heimnetz erreichbar. Im Access-Point-Modus (AP) spannt er ein eigenes WLAN auf, in das du dich direkt einwählst – praktisch für Projekte ohne Router, etwa im Garten oder unterwegs. Wir starten mit dem Station-Modus, weil er im Alltag am häufigsten gebraucht wird.
ℹ️ Was ist eigentlich ein Webserver? Klingt groß, ist aber simpel: Dein Browser schickt eine Anfrage ("gib mir die Seite" oder "schalte GPIO 2 ein"), und der ESP32 antwortet mit HTML oder führt die Aktion aus. Der ganze "Server" ist am Ende nur ein Sketch, der auf Anfragen an bestimmten Adressen (Routen) reagiert.
Das brauchst du
Für den Einstieg reicht der ESP32 allein – für die LED- und Relais-Beispiele kommt etwas Kleinkram dazu:
⚠️ Bibliotheken installieren: Wir nutzen die schnellen, nicht-blockierenden Bibliotheken ESPAsyncWebServer und AsyncTCP. Installiere sie über den Bibliotheksverwalter der Arduino IDE oder als ZIP von GitHub. Für die WebSocket-Beispiele brauchst du keine weitere Bibliothek – sie ist im AsyncWebServer schon enthalten.
1Der erste Schritt: ESP32 ins WLAN bringen
Bevor wir Webseiten ausliefern, muss der ESP32 ins Netz. Der folgende Minimal-Sketch verbindet ihn mit deinem WLAN und gibt die zugewiesene IP-Adresse im seriellen Monitor aus. Diese IP tippst du später im Browser ein.
#include <WiFi.h>
const char* ssid = "DEIN_WLAN_NAME";
const char* password = "DEIN_WLAN_PASSWORT";
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
Serial.print("Verbinde mit WLAN");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("Verbunden! IP-Adresse: ");
Serial.println(WiFi.localIP());
}
void loop() {
// hier passiert noch nichts
}
Lade den Sketch hoch, öffne den seriellen Monitor bei 115200 Baud und drücke die Reset-Taste am ESP32. Nach ein paar Sekunden erscheint eine Zeile wie IP-Adresse: 192.168.178.52. Merk sie dir – das ist die Adresse deines Servers.
ℹ️ Immer dieselbe IP: Standardmäßig verteilt der Router die IP dynamisch, sie kann sich also ändern. In deinem Router (z. B. Fritzbox) kannst du dem ESP32 anhand seiner MAC-Adresse eine feste IP zuweisen – dann ist er immer unter derselben Adresse erreichbar. Sehr zu empfehlen für dauerhafte Projekte.
2LED per Browser schalten – der erste echte Webserver
Jetzt wird es spannend. Wir schließen eine LED an GPIO 2 an (auf vielen Boards sitzt dort ohnehin die blaue Onboard-LED, du kannst also erstmal ganz ohne externe Bauteile testen). Der Sketch liefert eine kleine HTML-Seite mit zwei Buttons aus und reagiert auf die Routen /an und /aus.
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
const char* ssid = "DEIN_WLAN_NAME";
const char* password = "DEIN_WLAN_PASSWORT";
#define LED_PIN 2
AsyncWebServer server(80);
bool ledStatus = false;
// Baut die HTML-Seite je nach LED-Zustand zusammen
String seiteBauen() {
String s = "<!DOCTYPE html><html><head>";
s += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
s += "<title>ESP32 Steuerung</title></head><body>";
s += "<h1>ESP32 LED-Steuerung</h1>";
s += "<p>LED ist aktuell: <b>";
s += ledStatus ? "AN" : "AUS";
s += "</b></p>";
s += "<a href='/an'><button>Einschalten</button></a> ";
s += "<a href='/aus'><button>Ausschalten</button></a>";
s += "</body></html>";
return s;
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println();
Serial.print("IP-Adresse: ");
Serial.println(WiFi.localIP());
// Startseite
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/html", seiteBauen());
});
// LED an
server.on("/an", HTTP_GET, [](AsyncWebServerRequest *request){
ledStatus = true;
digitalWrite(LED_PIN, HIGH);
request->send(200, "text/html", seiteBauen());
});
// LED aus
server.on("/aus", HTTP_GET, [](AsyncWebServerRequest *request){
ledStatus = false;
digitalWrite(LED_PIN, LOW);
request->send(200, "text/html", seiteBauen());
});
server.begin();
}
void loop() {
// AsyncWebServer arbeitet im Hintergrund – loop bleibt frei
}
Lade den Sketch hoch, öffne im Browser http://192.168.178.52 (deine IP) und du siehst die Seite mit zwei Buttons. Ein Klick auf "Einschalten" ruft die Route /an auf, der ESP32 setzt GPIO 2 auf HIGH und schickt die aktualisierte Seite zurück. Das ist im Kern schon alles, was einen Webserver ausmacht.
⚠️ Warum AsyncWebServer statt WebServer? Die klassische WebServer-Bibliothek blockiert: Während sie eine Anfrage bearbeitet, steht dein loop() still. Der ESPAsyncWebServer arbeitet ereignisgesteuert im Hintergrund – dein Sketch bleibt jederzeit reaktionsfähig, auch wenn mehrere Geräte gleichzeitig zugreifen. Für alles, was nebenher noch Sensoren liest oder blinkt, ist das Gold wert.
3Eine schöne Oberfläche mit einem Umschalt-Button
Zwei nackte Buttons sind funktional, aber hässlich. Mit ein bisschen CSS direkt im Sketch bekommst du eine moderne Oberfläche mit einem einzigen Umschalt-Button (Toggle), der seinen Zustand farblich anzeigt. Die Seite lädt sich nach jedem Klick selbst neu und bleibt dadurch immer synchron.
String seiteBauen() {
String s = "<!DOCTYPE html><html><head>";
s += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
s += "<title>ESP32 Steuerung</title><style>";
s += "body{font-family:sans-serif;text-align:center;background:#101520;color:#e0e6ed;padding-top:60px;}";
s += "h1{color:#fff;} ";
s += ".btn{display:inline-block;padding:20px 50px;font-size:22px;border:none;";
s += "border-radius:12px;color:#fff;text-decoration:none;cursor:pointer;}";
s += ".an{background:#4ade80;} .aus{background:#f59e0b;}";
s += "</style></head><body>";
s += "<h1>ESP32 Steuerung</h1>";
if (ledStatus) {
s += "<p>Status: EINGESCHALTET</p>";
s += "<a class='btn aus' href='/aus'>Ausschalten</a>";
} else {
s += "<p>Status: AUSGESCHALTET</p>";
s += "<a class='btn an' href='/an'>Einschalten</a>";
}
s += "</body></html>";
return s;
}
Ersetze einfach die seiteBauen()-Funktion aus dem vorigen Sketch durch diese Version – der Rest bleibt gleich. Jetzt zeigt die Seite je nach Zustand einen grünen "Einschalten"- oder einen orangefarbenen "Ausschalten"-Button. Sauber, groß genug für den Daumen und selbsterklärend.
💡 Tipp für später: HTML als String im Sketch zusammenzukleben wird bei größeren Seiten schnell unübersichtlich. Für umfangreiche Oberflächen kannst du das HTML in eine separate Datei auslagern und mit dem LittleFS-Dateisystem vom ESP32-Flash ausliefern. Für den Anfang ist der Inline-Weg aber völlig okay und braucht keine zusätzliche Einrichtung.
4Echtzeit mit WebSockets – kein Neuladen mehr
Die bisherigen Versionen laden bei jedem Klick die ganze Seite neu. Das funktioniert, wirkt aber altbacken und ist ungeeignet, wenn mehrere Handys gleichzeitig steuern (Handy A sieht nicht, dass Handy B gerade umgeschaltet hat). Die elegante Lösung heißt WebSocket: eine dauerhafte Verbindung zwischen Browser und ESP32, über die beide Seiten jederzeit Nachrichten schicken können. Schaltet ein Gerät, sehen es alle sofort – ohne Neuladen.
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
const char* ssid = "DEIN_WLAN_NAME";
const char* password = "DEIN_WLAN_PASSWORT";
#define LED_PIN 2
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
bool ledStatus = false;
const char index_html[] PROGMEM = R"HTML(
<!DOCTYPE html><html><head>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<title>ESP32 Live</title><style>
body{font-family:sans-serif;text-align:center;background:#101520;color:#e0e6ed;padding-top:60px;}
.btn{padding:22px 55px;font-size:24px;border:none;border-radius:14px;color:#fff;cursor:pointer;}
.an{background:#4ade80;} .aus{background:#f59e0b;}
</style></head><body>
<h1>ESP32 Live-Steuerung</h1>
<p>Status: <b id='status'>--</b></p>
<button class='btn' id='toggle' onclick='umschalten()'>Umschalten</button>
<script>
let ws = new WebSocket('ws://' + location.host + '/ws');
ws.onmessage = e => {
let an = (e.data === '1');
document.getElementById('status').innerText = an ? 'EIN' : 'AUS';
let b = document.getElementById('toggle');
b.className = 'btn ' + (an ? 'aus' : 'an');
};
function umschalten(){ ws.send('toggle'); }
</script>
</body></html>
)HTML";
void sendeStatus() {
ws.textAll(ledStatus ? "1" : "0");
}
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client,
AwsEventType type, void *arg, uint8_t *data, size_t len) {
if (type == WS_EVT_CONNECT) {
sendeStatus(); // neuem Client den aktuellen Stand schicken
} else if (type == WS_EVT_DATA) {
String msg = "";
for (size_t i = 0; i < len; i++) msg += (char)data[i];
if (msg == "toggle") {
ledStatus = !ledStatus;
digitalWrite(LED_PIN, ledStatus ? HIGH : LOW);
sendeStatus(); // allen Clients den neuen Stand melden
}
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println();
Serial.println(WiFi.localIP());
ws.onEvent(onWsEvent);
server.addHandler(&ws);
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html);
});
server.begin();
}
void loop() {
ws.cleanupClients(); // getrennte Clients aufraeumen
}
Öffne diese Seite auf zwei Handys gleichzeitig und tippe auf einem "Umschalten" – der Status springt auf beiden Geräten sofort um. Das ist das Fundament für jedes ernsthafte Live-Dashboard, egal ob du Ausgänge schaltest oder Sensorwerte in Echtzeit anzeigst.
ℹ️ So funktioniert es: Der Browser öffnet beim Laden eine WebSocket-Verbindung zu /ws. Ein Tippen schickt den Text toggle an den ESP32. Der dreht den Zustand um und ruft ws.textAll() auf – das schickt den neuen Stand an alle verbundenen Browser gleichzeitig. Deshalb bleiben alle Geräte synchron.
5Vom Kleinsignal zum echten Verbraucher: das Relais
Eine LED ist nett zum Üben, aber der eigentliche Reiz ist, echte Geräte zu schalten. Dafür brauchst du ein KY-019 Relais-Modul. Es trennt den kleinen 3,3-V-Steuerkreis des ESP32 galvanisch vom Laststromkreis und kann darüber Lampen, Lüfter oder Pumpen schalten. Der Anschluss ist einfach: Das Relais bekommt Strom vom ESP32, und ein GPIO steuert, ob es anzieht.
| KY-019 Relais | ESP32 | Bemerkung |
|---|---|---|
| VCC (+) | 5V (VIN) | Relais-Spule braucht 5 V zum sicheren Anziehen |
| GND (-) | GND | gemeinsame Masse |
| Signal (S) | GPIO 26 | Steuerleitung, High-Level-Trigger |
Im Code ändert sich fast nichts – du steuerst statt der LED einfach den Relais-Pin. Hier die relevanten Zeilen, die du in den WebSocket-Sketch einbaust:
#define RELAIS_PIN 26
bool relaisStatus = false;
void setup() {
// ...
pinMode(RELAIS_PIN, OUTPUT);
digitalWrite(RELAIS_PIN, LOW); // Start: Relais aus
// ...
}
// im WebSocket-Handler bei msg == "toggle":
relaisStatus = !relaisStatus;
digitalWrite(RELAIS_PIN, relaisStatus ? HIGH : LOW);
⚠️ Netzspannung ist kein Spielzeug: Solange du Kleinspannung (LED, 5-V-Lüfter, 12-V-Streifen) schaltest, ist alles harmlos. Sobald 230 V im Spiel sind, gilt: Arbeiten an Netzspannung dürfen nur von einer Elektrofachkraft ausgeführt werden. Fasse spannungsführende Teile niemals an, und baue Netz-Projekte in ein berührungssicheres Gehäuse. Zum Lernen und für ungefährliche Niederspannungs-Lasten kannst du das Relais aber bedenkenlos einsetzen.
💡 High-Level- vs. Low-Level-Trigger: Das KY-019 ist ein High-Level-Trigger, es zieht also bei HIGH am Signalpin an. Viele blaue Standard-Relaismodule sind umgekehrt (Low-Level, aktiv bei LOW). Zieht dein Relais genau falschherum an, drehe im Code einfach HIGH und LOW um. Mehr zum Schalten von Lasten liest du im Artikel Relais-Modul KY-019 mit Arduino & ESP32 schalten.
6Mehrere Ausgänge auf einer Seite
In der Praxis willst du selten nur einen Kanal schalten. Der folgende Ansatz verwaltet mehrere GPIOs in einem Array und generiert automatisch für jeden einen Button. So erweiterst du die Steuerung auf 4, 6 oder 8 Kanäle, ohne den Code jedes Mal neu zu schreiben.
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
const char* ssid = "DEIN_WLAN_NAME";
const char* password = "DEIN_WLAN_PASSWORT";
// Beliebig erweiterbar: hier 4 Ausgaenge
const int pins[] = {26, 27, 32, 33};
const char* namen[] = {"Licht", "Luefter", "Pumpe", "Steckdose"};
const int ANZAHL = 4;
bool zustand[ANZAHL] = {false, false, false, false};
AsyncWebServer server(80);
String seiteBauen() {
String s = "<!DOCTYPE html><html><head>";
s += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
s += "<style>body{font-family:sans-serif;text-align:center;background:#101520;color:#e0e6ed;}";
s += ".btn{display:block;width:80%;margin:12px auto;padding:18px;font-size:20px;";
s += "border:none;border-radius:12px;color:#fff;text-decoration:none;}";
s += ".an{background:#4ade80;}.aus{background:#334155;}</style></head><body>";
s += "<h1>Haussteuerung</h1>";
for (int i = 0; i < ANZAHL; i++) {
s += "<a class='btn ";
s += zustand[i] ? "an' href='/set?kanal=" : "aus' href='/set?kanal=";
s += String(i) + (zustand[i] ? "&wert=0'" : "&wert=1'") + ">";
s += String(namen[i]) + ": " + (zustand[i] ? "AN" : "AUS") + "</a>";
}
s += "</body></html>";
return s;
}
void setup() {
Serial.begin(115200);
for (int i = 0; i < ANZAHL; i++) {
pinMode(pins[i], OUTPUT);
digitalWrite(pins[i], LOW);
}
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println(WiFi.localIP());
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/html", seiteBauen());
});
// /set?kanal=1&wert=1
server.on("/set", HTTP_GET, [](AsyncWebServerRequest *request){
if (request->hasParam("kanal") && request->hasParam("wert")) {
int k = request->getParam("kanal")->value().toInt();
int w = request->getParam("wert")->value().toInt();
if (k >= 0 && k < ANZAHL) {
zustand[k] = (w == 1);
digitalWrite(pins[k], zustand[k] ? HIGH : LOW);
}
}
request->send(200, "text/html", seiteBauen());
});
server.begin();
}
void loop() {}
Diese Version nutzt URL-Parameter: Ein Klick ruft z. B. /set?kanal=2&wert=1 auf, was Kanal 2 (die Pumpe) einschaltet. Über das Array namen[] vergibst du sprechende Bezeichnungen, die direkt auf den Buttons erscheinen. Willst du einen fünften Kanal, ergänzt du Pin, Name und ein weiteres false im Zustands-Array – fertig.
Ohne Router: der Access-Point-Modus
Manchmal gibt es kein WLAN – im Garten, in der Werkstatt, auf dem Feld. Dann lässt du den ESP32 sein eigenes Netz aufspannen. Statt WiFi.begin() nutzt du WiFi.softAP(). Dein Handy verbindet sich mit diesem WLAN, und die Steuerseite erreichst du unter der festen Adresse 192.168.4.1.
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
const char* ap_ssid = "MAKEROO-Steuerung";
const char* ap_pass = "12345678"; // mind. 8 Zeichen
AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
WiFi.softAP(ap_ssid, ap_pass);
Serial.print("AP gestartet. IP: ");
Serial.println(WiFi.softAPIP()); // meist 192.168.4.1
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/html", "<h1>Laeuft im AP-Modus!</h1>");
});
server.begin();
}
void loop() {}
Nach dem Hochladen taucht in der WLAN-Liste deines Handys ein Netz namens MAKEROO-Steuerung auf. Verbinde dich mit dem Passwort, öffne 192.168.4.1 im Browser – und die Steuerung läuft völlig autark, ganz ohne Internet und Router.
Troubleshooting: die häufigsten Stolperfallen
⚠️ "Seite nicht erreichbar" im Browser: Prüfe, ob Handy und ESP32 im selben WLAN sind (2,4-GHz-Netz – der ESP32 kann kein 5 GHz!). Kontrolliere die IP im seriellen Monitor und tippe sie exakt ein, inklusive http://.
⚠️ ESP32 verbindet sich nicht mit dem WLAN: Der ESP32 funkt nur im 2,4-GHz-Band. Heißt dein 5-GHz-Netz anders, wähle das 2,4-GHz-Netz. Kontrolliere SSID und Passwort auf Tippfehler – auch Groß-/Kleinschreibung zählt.
⚠️ Board startet ständig neu (Brownout): Speist du Relais und ESP32 aus einem schwachen USB-Port, bricht die Spannung beim Anziehen des Relais ein. Nutze ein kräftigeres Netzteil oder versorge das Relais getrennt. Mehr zur sauberen Stromversorgung im Artikel Stabile Stromversorgung für Arduino & ESP32.
⚠️ Kompilierfehler bei ESPAsyncWebServer: Die Bibliothek braucht zwingend auch AsyncTCP (für ESP32). Fehlt sie, bricht der Compiler ab. Beide zusammen installieren.
Wie geht es weiter?
Du hast jetzt das komplette Handwerkszeug, um deinen ESP32 per Browser zu steuern. Von hier aus gibt es viele spannende Richtungen:
- Sensorwerte anzeigen: Kombiniere die WebSocket-Seite mit einem Temperatursensor und zeige die Werte live an
- Ins Smart Home einbinden: Statt einer eigenen Seite lässt du den ESP32 per MQTT mit Home Assistant sprechen
- ESP8266 als günstige Alternative: Für einfache Ein-Kanal-Projekte reicht oft ein Wemos D1 Mini als Webserver
- Timer und Automatik: Lass Ausgänge zeitgesteuert schalten, etwa das Licht abends automatisch an
🚀 Alles für dein ESP32-Webserver-Projekt
Diese Bauteile brauchst du, um direkt loszulegen:
- ESP32 WROOM-32 Entwicklungsboard (USB-C) – WLAN-Zentrale
- KY-019 Relais-Modul – echte Verbraucher schalten
- 3-mm-LED-Sortiment – zum Testen der Ausgänge
- Widerstands-Sortiment – Vorwiderstände für die LEDs
- Breadboard 400 Kontakte – für den steckbaren Aufbau
- ESP32-Acryl-Gehäuse – für den festen Einbau
Fazit
Ein Webserver auf dem ESP32 klingt nach großer Sache, ist aber in wenigen Zeilen Code aufgebaut – und unglaublich vielseitig. Du hast gesehen, wie du vom simplen "LED an/aus" über eine schicke Oberfläche bis zur echtzeitfähigen WebSocket-Steuerung kommst und am Ende echte Geräte über ein Relais schaltest. Der Clou: Alles läuft lokal in deinem WLAN, ohne Cloud, ohne Abo, ohne dass Daten dein Haus verlassen.
Der ESPAsyncWebServer ist dabei dein wichtigster Verbündeter, weil er im Hintergrund arbeitet und deinen Sketch reaktionsfähig hält. Fang mit der Onboard-LED an, erweitere auf das Relais und dann auf mehrere Kanäle – Schritt für Schritt baust du dir so deine eigene, maßgeschneiderte Haussteuerung. Und das Beste: Du verstehst jede Zeile davon selbst.