ESP32 Based Web Server

The ESP32 Web Server project demonstrates how to build and host a web page directly from an ESP32 microcontroller. This allows users to control devices or monitor sensors from any browser on the same Wi-Fi network.

How It Works

An ESP32 running a web server turns the board itself into the user interface. Rather than printing to a serial console that requires a cable and a computer, the board serves an HTML page that any phone or laptop on the same network can open — no app, no driver, no cable.

The board can operate in two modes. In station mode it joins an existing WiFi network and receives an IP address from the router. In access point mode it creates its own network, which is useful for field devices with no infrastructure. It can also do both at once.

A web server is fundamentally a request router: the client asks for a path such as / or /data, and the sketch decides what to send back. Serving the page and serving the data separately is the pattern that matters — the HTML is static and loads once, while JavaScript on the page polls a lightweight /data endpoint for fresh values.

That separation avoids the beginner mistake of regenerating the entire page on every update, which is slow, flickers, and wastes the ESP32's limited heap. With 520 KB of RAM there is room for a reasonable page, but not for repeatedly concatenating large strings.

Components Needed

  • ESP32
  • ESP32
  • Jumper Wires
  • LEDs (optional)
  • Power Supply or USB Cable

Wiring to the ESP32

No special wiring is needed beyond power and whatever sensor supplies the data. Connect a sensor to GPIO34 if you want real values to serve rather than a simulated one.

WiFi transmission draws current in short bursts of several hundred milliamps. A weak USB supply or a thin cable causes brownouts that look like random reboots — use a good cable and, ideally, a 500 mA or better supply.

Note the IP address printed to the serial monitor on first boot. Without it you cannot reach the server, though mDNS can give the board a fixed name such as esp32.local instead.

RequirementDetailNotes
WiFi network2.4 GHz onlyThe ESP32 does not support 5 GHz
Sensor inputGPIO34Any data source to publish
Power5 V via USB or 3.3 V regulatedWiFi draws current in bursts
Serial monitor115200 baudNeeded once to discover the assigned IP

Build and Upload

Open the Arduino IDE, select the ESP32 board, and paste the provided code. Update the Wi-Fi SSID and password in the sketch before uploading.

Example Code

A web server serving a page once and live values from a separate endpoint. Upload it with the board set to ESP32 and open the Serial Monitor at 115200 baud.

A web server serving a page once and live values from a separate endpoint
#include <WiFi.h>
#include <WebServer.h>

const char* WIFI_SSID     = "YOUR_SSID";
const char* WIFI_PASSWORD = "YOUR_PASSWORD";

WebServer server(80);

// Static page — sent once, then JavaScript polls /data for fresh values
const char PAGE[] PROGMEM = R"HTML(
<!DOCTYPE html><html><head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ESP32 Monitor</title>
<style>
 body{font-family:system-ui;margin:0;padding:2rem;background:#0f172a;color:#e2e8f0}
 .card{background:#1e293b;border-radius:12px;padding:2rem;max-width:420px;margin:auto}
 .value{font-size:3rem;font-weight:700;color:#38bdf8}
</style></head><body>
<div class="card">
  <h2>Live Reading</h2>
  <div class="value" id="v">--</div>
  <p id="t">waiting...</p>
</div>
<script>
setInterval(async () => {
  const r = await fetch('/data');
  const j = await r.json();
  document.getElementById('v').textContent = j.value;
  document.getElementById('t').textContent = 'uptime ' + j.uptime + ' s';
}, 1000);
</script></body></html>
)HTML";

void handleRoot() { server.send_P(200, "text/html", PAGE); }

void handleData() {
  char json[96];
  snprintf(json, sizeof(json), "{\"value\":%d,\"uptime\":%lu}",
           analogRead(GPIO34), millis() / 1000);
  server.send(200, "application/json", json);
}

void setup() {
  Serial.begin(115200);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  Serial.print("connecting");
  while (WiFi.status() != WL_CONNECTED) { delay(400); Serial.print("."); }

  Serial.println();
  Serial.print("Open http://");
  Serial.println(WiFi.localIP());

  server.on("/", handleRoot);
  server.on("/data", handleData);
  server.begin();
}

void loop() {
  server.handleClient();
}

Applications

A esp32 web server turns up in a wide range of projects. These are the uses where it is the right choice rather than a compromise:

  • Sensor dashboards readable from any phone on the network
  • Configuration interfaces for headless devices
  • Home automation control panels
  • Remote switching of relays and outputs
  • Field instruments in access-point mode with no infrastructure

Working with the ESP32

The ESP32 is built around the ESP32 and runs on 3.3 V logic with 520 KB of SRAM and 4 MB typically of program flash. These details change how this circuit is wired and what the sketch can do, so they are worth stating plainly before you build.

The ESP32 is a 3.3 V part; 5 V sensor outputs need a divider or level shifter before they touch a GPIO.

Its ADC is 12-bit, so readings span 0–4095 rather than the 0–1023 an Arduino returns.

ADC2 pins stop working once WiFi is active — keep analog sensors on ADC1 (GPIO32–GPIO39) in any connected project.

GPIO34–GPIO39 are input-only and have no internal pull-ups.

ESP32 characteristicValueWhy it matters here
Logic voltage3.3 VSensor outputs above this level need a divider or level shifter
ADC resolution12-bit (0–4095)Sets how finely an analog reading can be resolved
Analog inputsADC1 on GPIO32–GPIO39 and ADC2 on several other pinsDetermines how many analog sensors can share the board
PWM outputsany GPIO through the LEDC peripheralNeeded for brightness, speed and tone control
I²C pinsGPIO21 (SDA) and GPIO22 (SCL) by defaultFixed by hardware — wiring copied from another board may not match
Interrupt pinsany GPIORequired for counting fast or asynchronous events
Serialthree hardware UARTsMonitor runs at 115200 baud by default

Troubleshooting

Most problems with this module fall into a handful of categories. Work through these before suspecting the part itself:

  • It never connects — the network is 5 GHz. The ESP32 supports 2.4 GHz only.
  • The board reboots repeatedly — the supply cannot deliver WiFi current bursts. Use a better cable and supply.
  • The page loads but values never update — the /data endpoint is failing; open it directly in the browser to see the error.
  • The IP address changes between boots — the router is assigning dynamically. Reserve an address, or use mDNS.
  • Memory runs out after a while — the page HTML is being built with String concatenation. Keep it in PROGMEM as shown.
  • The sketch compiles but the board resets or behaves erratically — a 5 V module output is being driven into a 3.3 V pin. Measure the signal before connecting it.
  • Readings differ from an Arduino tutorial for the same part — the 12-bit ADC returns 0–4095, not 0–1023, so any constant copied from an Uno example needs rescaling.

Taking It Further on the ESP32

Once the basic reading works, where you go next depends very much on which board you are using. These are the directions that suit the ESP32 specifically:

With WiFi and Bluetooth built in, the natural extension is to publish readings over MQTT or expose them as a BLE characteristic that a phone app can subscribe to directly.

The ESP32’s dual cores let acquisition and networking run independently. Pin a FreeRTOS task reading the sensor to one core and the WiFi stack to the other, and network delays stop disturbing sample timing.

Deep sleep with the RTC timer brings average current into the microamp range, and the ULP co-processor can keep watching an input while the main cores stay asleep — practical for battery sensors that must last months.

Notes and Practical Limits

Serving the page from PROGMEM rather than building it as a String is the single most important detail here. String concatenation fragments the heap and eventually crashes a long-running server.

For anything beyond a handful of clients, the asynchronous ESPAsyncWebServer library handles concurrent connections far better than the blocking WebServer, which processes one request at a time.