Overview
An ESP32 web server lets the board serve a web page directly to a browser over Wi-Fi. It can be as simple as a static HTML page or expanded into a control panel for outputs and sensor data.
This example uses the Arduino IDE with the WiFi.h and WebServer.h libraries. You’ll set up the ESP32 in both Station mode and Access Point mode, test each one from a browser, and see how URL routes map to handler functions in code.
If you’re new to the platform, start with getting started with the ESP32 on Arduino IDE.
Key Takeaways
- ESP32 web server workflow and HTTP request/response basics
- Choosing between STA, AP, and AP+STA modes
- Building and testing the STA mode example
- Building and testing the AP mode example
- Mapping URL routes to ESP32 actions
- Troubleshooting browser and Wi‑Fi connection issues
What is a Web Server?
A web server waits for a browser request, processes it, and returns a response. With an ESP32, that usually means the browser requests a URL like /, and the ESP32 sends back HTML.
HTTP is the protocol that carries those requests and responses. A successful response often returns status code 200. If the requested page does not exist, the server can return 404.
That request/response flow is the basis of every ESP32 web interface: the browser sends a URL, the ESP32 runs the matching handler, and then returns a page or message.
| Mode | ESP32 role | Internet access | How you connect to the page | Best use case |
|---|---|---|---|---|
| STA | Client on an existing Wi-Fi network | Yes, through the router | Connect your phone or PC to the same router, then open the ESP32 IP | Home or lab network projects |
| AP | Creates its own Wi-Fi network | No internet access by default; requires additional NAT/routing code to bridge to an upstream network | Connect your phone or PC directly to the ESP32 SSID, then open its AP IP | Direct local control without a router |
| AP+STA / Dual | Connects to a router and creates its own AP at the same time | Yes on the STA side; AP-side internet access requires additional NAT/forwarding code | Devices can connect through the router side or the ESP32 access point | Bridge-like setups or mixed access needs |
ESP32 Wi‑Fi Operating Modes for a Web Server
The Wi-Fi mode determines how you reach the page. Choose the mode based on the network you already have and how you want users to connect.
Station Mode (STA)
In STA mode, the ESP32 joins an existing Wi-Fi network as a client. Your router assigns the ESP32 an IP address, and you open that IP in a browser.
Use this mode when the ESP32 should stay on the same network as your laptop, phone, or other devices. For development, this is usually the easiest option.
Access Point Mode (AP)
In AP mode, the ESP32 creates its own Wi-Fi network. Your phone or PC connects directly to that network, then opens the ESP32 page at the configured IP address.
Use this mode when no router is available or when you want a self-contained local interface.
AP+STA / Dual Mode
In dual mode, the ESP32 can connect to an existing Wi-Fi network and create its own access point at the same time.
This is useful when you want router-based access during normal operation but still need a direct local access path. This article does not implement AP+STA mode, but it is a supported ESP32 Wi-Fi operating mode.
Prerequisites
Before uploading code, make sure these basics are in place:
- Arduino IDE installed
- ESP32 board package installed in Arduino IDE using Installing the ESP32 Board in Arduino IDE
- An ESP32 development board
- Wi-Fi SSID and password for the STA example
- Serial Monitor available at
115200baud
If you are choosing hardware for this project, an ESP32 development board with ESP-WROOM-32 is enough for both examples.
ESP32 Web Server: Setting up ESP32 in Station Mode (STA)
In this mode, the ESP32 connects to your Wi-Fi router and gets an IP address from it. You then open that IP in a browser.
boards based on the ESP-WROOM-32 module, including the ESP-WROOM-32 module board and the ESP-WROOM-32 development board with CP2102.
ESP32 Web Server Code in Arduino IDE
Upload this code to start the ESP32 web server in STA mode:
/*
ESP32 Web Server - STA Mode
modified on 25 MAy 2019
by Mohammadreza Akbari @ Electropeak
Home
*/
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
// SSID & Password
const char *ssid = "*****"; // Enter your SSID here
const char *password = "*****"; // Enter your Password here
WebServer server(80); // Object of WebServer(HTTP port, 80 is default)
// HTML & CSS contents which display on web server
const char HTML[] PROGMEM = R"=====(
<!DOCTYPE html>
<html>
<body>
<h1>My First Web Server with ESP32 - Station Mode 😊</h1>
</body>
</html>
)=====";
// Handle root url (/)
void handle_root()
{
server.send(200, "text/html", HTML);
}
// Handle unknown URLs with a 404 response
void handle_NotFound()
{
server.send(404, "text/plain", "Not found");
}
void setup()
{
Serial.begin(115200);
Serial.println("Try Connecting to ");
Serial.println(ssid);
// Connect to your WiFi modem
WiFi.begin(ssid, password);
// Check WiFi is connected to WiFi network
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected successfully");
Serial.print("Got IP: ");
Serial.println(WiFi.localIP()); // Show ESP32 IP on serial
server.on("/", handle_root);
server.onNotFound(handle_NotFound);
server.begin();
Serial.println("HTTP server started");
delay(100);
}
void loop()
{
server.handleClient();
}
The sketch also includes server.onNotFound(handle_NotFound) to return a 404 response for unknown URLs.
Upload the Code and Check Serial Monitor Output
Open the Serial Monitor at 115200 baud after upload. If the SSID and password are correct, the ESP32 connects to the router and prints its assigned IP address.
You should see a sequence like this:
- dots while connecting
WiFi connected successfullyGot IP: ...HTTP server started
Info
Your browser device must be connected to the same router network as the ESP32 before you open the assigned IP address.
Open the ESP32 Web Server in Your Browser
Copy the IP address printed in Serial Monitor and enter it in your browser.
If the ESP32 received an address from your router, that is the address to use. Do not use 192.168.1.1 here unless your router actually assigned that address to the ESP32.
How the STA Example Works
The sketch uses two libraries:
#include
#include
WiFi.h handles the network connection. WebServer.h handles HTTP routes and responses.
These two lines store the router credentials:
const char* ssid = "****";
const char* password = "****";
The server object listens on port 80:
WebServer server(80);
This line starts the Wi-Fi connection:
WiFi.begin(ssid, password);
This loop waits until the ESP32 connects:
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Once connected, the sketch prints the IP address:
Serial.print("Got IP: ");
Serial.println(WiFi.localIP());
This line registers a route handler for /:
server.on("/", handle_root);
When a browser opens the root URL, handle_root() runs and returns the HTML string with status 200 and content type text/html.
The loop() function keeps servicing browser requests:
server.handleClient();
ESP32 Web Server: Setting up ESP32 in Access Point Mode (AP)
In AP mode, the ESP32 creates its own Wi-Fi network and serves the page directly to devices that join it.
ESP32 Web Server Code in Arduino IDE
Upload this code to run the ESP32 in AP mode:
/*
ESP32 Web Server - AP Mode
modified on 25 May 2019
by Mohammadreza Akbari @ Electropeak
Home
*/
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
// SSID & Password
const char *ssid = "Electropeak"; // Enter your SSID here
const char *password = "123456789"; // Enter your Password here
// IP Address details
IPAddress local_ip(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
WebServer server(80); // Object of WebServer(HTTP port, 80 is default)
const char HTML[] PROGMEM = R"=====(
<!DOCTYPE html>
<html>
<body>
<h1>My First Web Server with ESP32 - AP Mode 😊</h1>
</body>
</html>
)=====";
// Handle root url (/)
void handle_root()
{
server.send(200, "text/html", HTML);
}
// Handle unknown URLs with a 404 response
void handle_NotFound()
{
server.send(404, "text/plain", "Not found");
}
void setup()
{
Serial.begin(115200);
// Create SoftAP
WiFi.softAPConfig(local_ip, gateway, subnet);
WiFi.softAP(ssid, password);
Serial.print("Connect to My access point: ");
Serial.println(ssid);
server.on("/", handle_root);
server.onNotFound(handle_NotFound);
server.begin();
Serial.println("HTTP server started");
delay(100);
}
void loop()
{
server.handleClient();
}
server.onNotFound(handle_NotFound) for 404 handling.
Upload the Code and Connect to the ESP32 Access Point
After upload, open Serial Monitor at115200 baud. The sketch prints the SSID so you know which network to join.
Next, connect your phone or PC to the ESP32 Wi-Fi network created by the sketch Warning
For ESP32 SoftAP mode, use a valid WPA2 password with at least 8 characters. The example password `123456789` meets this requirement.
Open the ESP32 Web Server in Your Browser
Once your device is connected to the ESP32 access point, open 192.168.1.1 in the browser. That matches the local_ip configured in the sketch.
If you change the AP IP in code, use that address instead.
How the AP Example Works
These lines define the SSID and password for the ESP32-created network:
const char* ssid = "Electropeak";
const char* password = "123456789";
IPAddress local_ip(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
These two calls create and configure the access point:
WiFi.softAPConfig(local_ip, gateway, subnet);
WiFi.softAP(ssid, password);
The rest of the sketch follows the same server pattern used in STA mode: define the route with server.on("/"), start the server with server.begin(), and handle requests with server.handleClient().
| Mode | Device connection requirement | Address to open in browser | Where to find the address |
|---|---|---|---|
| STA | Your device must be on the same router network as the ESP32 | The IP printed by WiFi.localIP() |
Serial Monitor |
| AP | Your device must connect to the ESP32 SSID first | 192.168.1.1 in this example |
local_ip in the sketch |
How HTTP Requests Map to ESP32 Actions
For web server projects, the practical model is simple: each URL route maps to a function. When the browser requests that route, the ESP32 runs the matching handler.
In this article, the only route is /, which maps to handle_root(). Many tutorials extend the same pattern to routes that control outputs or return different content.
| URL route | Example handler | ESP32 action | Typical use |
|---|---|---|---|
/ |
handle_root or handle_OnConnect |
Return the main page | Home page |
/led1on |
handle_led1on |
Turn an output on, then return HTML | LED or relay control |
/led1off |
handle_led1off |
Turn an output off, then return HTML | LED or relay control |
/26/on |
Manual request parsing example | Set GPIO state to on | GPIO control |
| invalid route | handle_NotFound |
Return 404 Not Found |
Error handling |
The useful takeaway is that you do not need a different server for every page. One server can expose multiple URLs, and each URL can run different ESP32 code.
What You Can Build with This ESP32 Web Server
The current sketch returns a fixed HTML page, but the same structure scales well.
Use the root page to show status, add extra routes for control actions, or generate HTML dynamically before calling server.send().
Common patterns include:
- output control through routes like
/led1on,/led1off,/26/on, and/27/off - returning a
404message for invalid routes withserver.onNotFound(...) - generating changing pages, such as a visitor counter or sensor readout, before sending the HTML
That means the main design pattern is already here:
- connect Wi-Fi
- define routes
- map each route to a handler
- send HTML or plain text from the handler
Basic HTML and CSS Commands for ESP32 Web Server
The ESP32 sends plain text to the browser. If that text is HTML, the browser renders it as a page.
Basic HTML Structure
The sketch starts with this line:
<!DOCTYPE html>
That tells the browser to parse the page as HTML.
A minimal structure is:
<html>
<body>
<h1>My First Web Server with ESP32</h1>
</body>
</html>
The code stores this markup in a PROGMEM character array and sends it with:
server.send(200, "text/html", HTML);
Common HTML Elements Used in ESP32 Pages
A heading:
<h1>This is heading 1</h1>
Paragraphs:
<p>Your first paragraph.</p>
<p>Your second paragraph.</p>
Bold text:
<b>This is bold text</b>
Link:
<a href="https://electropeak.com/">Visit Electropeak</a>
Image:
<img src="image URL" alt="Smiley face" width="42" height="42">
Button:
With the <button> tag, you can add a button to your page.
If you split a long C++ string across multiple lines, The sketch uses a backslash at the end of each line.
Mobile Responsiveness Basics
If you expect users to open the page on a phone, add a viewport meta tag in the HTML <head>.
The competitor examples use:
<meta name="viewport" content="width=device-width, initial-scale=1">
This tells the browser to scale the page correctly on smaller screens. Without it, a simple ESP32 page can still load, but the layout may not display as intended on mobile devices.
WebServer vs WiFiServer: Which Approach This Article Uses
This article uses WebServer:
#include
WebServer server(80);
That gives you route handlers like server.on("/", handle_root);, which is a higher-level interface.
Some examples use WiFiServer instead. In that approach, you accept a client connection and manually parse the HTTP request string.
If you want simple routing and cleaner code for basic pages, WebServer is the approach used here. If you want lower-level control over request parsing, WiFiServer is another valid pattern.
Static vs Dynamic ESP32 Web Pages
The example sketches in this article serve static pages. The HTML string is fixed, so the page content does not change unless you edit the sketch.
Dynamic pages generate content at request time. Many tutorials show that pattern with visitor counters, sensor readings, and output state pages.
Two common update methods are:
| Method | How it works | When to use it |
|---|---|---|
| Static HTML | ESP32 returns the same page every time | Simple demos and fixed info |
| Auto-refresh | Browser reloads the whole page at intervals using a meta refresh tag | Basic live updates |
| AJAX polling | Browser requests only the changing data repeatedly | More efficient live sensor or status updates |
A simple auto-refresh example is:
<meta http-equiv="refresh" content="1" >
AJAX polling is a more efficient next step because it updates only the changing part of the page instead of reloading everything.
Troubleshooting ESP32 Web Server Setup
Most failures come down to setup, not the server itself. Start with the network path first: is the ESP32 connected, and is your browser on the right network?
Info
If upload succeeds but the page does not load, press EN/reset and recheck board and port selection first.
| Problem | Likely cause | What to check |
|---|---|---|
| ESP32 keeps printing dots | Wrong SSID or password, or router not reachable | Recheck credentials in code and verify the Wi-Fi network is available |
| No IP shown in Serial Monitor | ESP32 never connected | Confirm Serial Monitor is at 115200, then verify Wi-Fi credentials |
| Browser cannot open STA page | Browser device is on a different network | Make sure the phone or PC is connected to the same router as the ESP32 |
| Browser cannot open AP page | Device did not join the ESP32 SSID first | Connect to the ESP32 access point before opening 192.168.1.1 |
| Upload worked but page still fails | Network mismatch, wrong IP address, or ESP32 needs reset | Confirm browser device is on the correct network, verify the IP address from Serial Monitor, then press EN/reset |
| Wrong page or 404 behavior unclear | Missing route handling | Confirm the route exists and add server.onNotFound(...) if needed |
A dedicated fix for upload connection issues is available in [SOLVED] ESP32 packet header timeout troubleshooting.
Conclusion
An ESP32 web server comes down to three main decisions: which Wi-Fi mode to use, which routes to expose, and whether the page should be static or dynamic.
Use STA mode when the ESP32 should join your existing Wi-Fi network. Use AP mode when the ESP32 should create its own network. If you need both behaviors, AP+STA mode is the next option to evaluate. For follow-on setup and board details, see getting started with the ESP32 development board.
FAQ
What is the difference between ESP32 Station mode, Access Point mode, and AP+STA mode for a web server?
Station mode makes the ESP32 join an existing Wi-Fi network and serve pages on the router-assigned IP. Access Point mode makes the ESP32 create its own Wi-Fi network and serve pages directly. AP+STA mode does both at once, which is useful when you need router access and direct local access together.
Why can’t I open the ESP32 web server page in my browser after uploading the code?
Usually the ESP32 is either not connected to Wi-Fi, your browser device is on the wrong network, or you opened the wrong IP address. Check Serial Monitor for the assigned IP, verify board and port settings, and press EN/reset if the upload finished but the server did not start cleanly.
How do I find the ESP32 IP address in Arduino IDE Serial Monitor?
Open Serial Monitor at 115200 baud after uploading the sketch. In STA mode, the code prints the IP address using WiFi.localIP() after a successful connection. That printed address is the one you enter in your browser. In AP mode, the address is typically the configured local AP IP.
Why does the ESP32 AP mode web server require my phone or PC to connect to the ESP32 Wi‑Fi network first?
In AP mode, the ESP32 is the network. It does not join your home router; it creates its own Wi-Fi access point. Your phone or PC must join that ESP32 network first, otherwise there is no network path to reach the ESP32 page at the configured AP IP address.
How do I add more pages or URLs to an ESP32 web server using server.on()?
Add another route and map it to a handler function. For example, server.on("/status", handle_status); tells the ESP32 to run handle_status() when a browser opens /status. Inside that function, return HTML or text with server.send(...) based on the behavior you want.
How do I return a 404 page when a user enters the wrong ESP32 web server URL?
Use server.onNotFound(...) and point it to a handler that returns a 404 response. The handler typically calls server.send(404, "text/plain", "Not found");. This makes invalid URLs behave like a normal web server instead of failing silently or returning the wrong page.
How can I make an ESP32 web page update sensor values automatically without refreshing?
Two common options are auto-refresh and AJAX polling. Auto-refresh reloads the entire page at intervals using a meta refresh tag. AJAX polling is more efficient because the browser repeatedly requests only the changing data, so the rest of the page stays loaded and unchanged.
What should I check if the ESP32 never connects to Wi‑Fi or keeps printing dots in Serial Monitor?
Start with the SSID and password in the sketch, because that loop usually means the connection never completed. Then verify the Wi-Fi network is available, confirm the board is powered and running, and make sure Serial Monitor is set to 115200 so you can read the connection status messages correctly.
Comments (32)
Hi, my name Nur Ahmad. You can call me Wiwid like others.
I try your code to build the web server inside the esp, i think this code unique. because use String HTML to create the Web Server.
I had try code with Client.println() to build the web server like Github share what they did.
But, your code not give feedback when client connect to ur server.
And i combine both, the coe from Github and your code to build script that give feedback when client connect to your server.
But, combination not working. Because different style script when build the HTML web server
with your style script , can you help me to script ESP32 to give feedback when client connect to our server?
thank you
Hi Wiwid.
We will check it 🙂
Very interesting! Now. as a newbie, my question is related to:
Click Me! …
is there a way to send a digitalWrite command to the ESP32 board when the button is clicked?
Thanks in advance 🙂
Hi Antonio. It’s very easy to do that. Follow this link.
https://randomnerdtutorials.com/esp32-web-server-arduino-ide/
Hello
I’m not able to insert the images, do you have any examples that can help me? I used the same code from the reference above and inserted a line of code for the image I want, but it doesn’t work, can you help me?
In the code
img src="image URL", you should replace “image URL” with a real valid image URL. This one should work:https://electropeak.com/learn/wp-content/uploads/2019/07/webserver-cover-400x300-1.jpgBit confused about inserting an image. If this is an access point then an image can only be displayed from SPIFFS how would you integrate a request to get the image from the SPIFFS?
Hi james,
If you’re creating a webpage on an ESP, I recommend using Fully SPIFFS (SPI Flash File System). This entails organizing all your HTML, CSS, images, and other files into a single folder and uploading them to SPIFFS. Then, you’ll call the main page (like index.html) in your code. To reference your files correctly, for example, for a font, you’ll add a line like this in your code:
server.on("/font/Vazir-FD-WOL.woff", HTTP_GET, font);In the font function, you’ll have:
void favicon(){
Serial.println("-Font");
server.send(200, "text/html", readFile("/html/font/Vazir-FD-WOL.woff")); // Send web page
}
And in the readFile function:
String readFile(const char *path){
String value = "";
File file = SPIFFS.open(path);
if (!file)
{
Serial.println("Failed to open file for reading");
return "";
}
while (file.available())
{
value = file.readString();
}
file.close();
return value;
}
This ensures that your files are read and served correctly from SPIFFS.
Thanks for the examples. I found that the AP mode required me to set a manual IP address. The line:
WiFi.softAPConfig(local_ip, gateway, subnet);
Seems to disable DHCP. Commenting it out allowed DHCP to work, but I had to add the lines:
Serial.print(“My IP address: “);
Serial.print(WiFi.softAPIP());
To confirm what IP Address the ESP32 gave itself
Thanks for sharing.
Hello, thats a great example but I’ve one question? Where I can find the libary “WebServer.h”? I’ve tried to find in web but I’ve no access. Is it a standard libary, if yes what the name in the libary manager?
Thanks, Marcel.
Hi Marcel K. “WebServer.h” will automatically install when you install esp32 on Arduino IDE. If not, follow the link below to install it again.
https://electropeak.com/learn/getting-started-with-the-esp32/
I hope this tutorial helps you to install it.
Is it possible to have a files repo stored on the board so that I can host an existing webpage? (HTML, CSS and some images) Or maybe link the file tree to the board? Thanks in advance!
Yes, it’s possible. You can do that based on the tutorial explained in this article. But in order to make a public webpage, you will need static IP and config your rooter/modem.
An execllent posting from you, all helpful in stuffing my head
up with helpful knowledge. I’ve also taken the time to
share this on Twitter 🙂
You’re most welcome! And thanks for sharing.
Your website has provided me a lot of useful info, and for that I thank you very much.
This informative article is one of the best I’ve read up to now on your
web site, so I felt I needed to take the time to comment.
You have given myself a variety of tips to help me in the foreseeable future.
You’re most welcome! We’re filled up with energy by such motivating comments.
I’m having a problem after uploading the code in the board.
It says: “leaving… Hard resetting via RTS pin”
what should I do?
I’m connecting the esp32 directly at the USB of my computer, with no resistance and stuff.
I’m trying to make the web server to work, with ssid and password filled correctly.
Thanks for the explaining, it was pretty good.
Hi Vinicius,
You’ve got no problem actually! This message means that the uploading has been successfully done.
I see… thanks for the information. One more thing: Is it normal that the serial monitor is completely empty? It is completely white… and i can’t access the server even with my correct ip address.
Wow! It worked! I just had to press the RTS button to the module run the uploades code.
Thanks a lot Mehran! Have a nice life!
You’re quite welcome!
You too!
very intersting
Thanks!?
hi..
it is amazing
but i have a question please
how can i handle Input Fields with webserver.h esp32? and reading query parameters in url?
is there a clear documents for webserver ?
and thank you so much
Hi,
I need to publish this web page globally from internet so how we can explore ? Locally it is accessible as you have mention so how it would be accessible from anywhere from internet
Hello
To connect different types of network modules to the Internet through your own internal network, you need to do the following.
1. Having a static ip from your internet provider.
2. Set up the port forward in the modem / router settings (which external port of yours should be connected to which internal port and ip of yours.
After setting these items, you can access your esp page through your external static ip address and the set port.
Of course, depending on the country, you can also use ddns for this issue.
I’ve tested the awesome Station Mode Access Point and it works. My need is…
1) Multiple ESP32 camera servers (not sending at the same time).
2) One receiver at my PC that receives photos and saves them to my hard drive.
3) The receiver does not initiate the capture – it just receives and saves photos.
What would you suggest for the receiver: access point – station mode or something else?
I Like it
So interesting!!
Thanks for the tutorial. My code works most time but sometimes the server does not respond.
After server.begin(); I get reply “HTTP server started” but it will not respond to the web browser or to a ping and I have to reset. Most times that will get it working.
Does server.begin(); return any error codes and if not is there a way in the code that I can test if the server is functioning correctly ?
Hello,
In the library file “WiFiServer.cpp,” we encounter a void function, which indicates that this function doesn’t return any information.
This could hint at another issue, such as a poor connection to the network. I recommend conducting a ping test to verify the connection or adding a function to test the connection or any other potential problem. You can make a access to this function from the serial port and use it to test each section of your code.