Self-Hosted Public Website Running On A 10 Esp32 On My Wall
Self-Hosted Public Website Running On A $10 ESP32 On My Wall
Introduction
In the world of homelabs and self-hosted infrastructure, we often think of powerful servers, cloud instances, or Raspberry Pis as the backbone of our projects. But what if I told you that a $10 microcontroller could serve a public website entirely by itself, running 24/7 on your wall? This isn’t science fiction—it’s the reality of running a public-facing website on an ESP32, a device that typically costs less than a cup of coffee.
This setup challenges our conventional understanding of web infrastructure. We’re accustomed to nginx servers, Apache instances, containerized applications, or cloud services handling our web traffic. The idea of a tiny microcontroller—with its limited processing power and memory—serving real HTTP requests to the public internet seems almost absurd. Yet, with the right architecture and some creative engineering, it’s not only possible but surprisingly practical.
The ESP32-based web server represents a fascinating intersection of embedded systems and web infrastructure. It’s a testament to how far microcontroller technology has come and how creative DevOps thinking can push the boundaries of what’s possible with minimal hardware. This approach isn’t about replacing traditional web infrastructure for production workloads, but rather exploring the limits of what’s achievable and understanding the fundamental principles of how web services operate.
In this comprehensive guide, we’ll dive deep into how this ESP32 web server works, why someone would build such a system, and how you can create your own wall-mounted web server. We’ll explore the technical architecture, discuss the challenges overcome, and provide practical insights for anyone interested in pushing the boundaries of homelab experimentation.
Understanding the ESP32 Web Server Architecture
The Core Concept
At its heart, this ESP32 web server is a clever workaround to the limitations of microcontroller hardware. The ESP32 itself cannot directly handle public internet traffic—it lacks the necessary networking stack, security features, and processing power to serve HTTPS requests at scale. Instead, the architecture uses a reverse proxy pattern where Cloudflare Workers act as the public-facing endpoint, forwarding requests to the ESP32 via WebSocket connections.
This design is brilliant in its simplicity. Cloudflare Workers provide the HTTPS termination, DDoS protection, and global edge caching, while the ESP32 handles only the application logic and response generation. The WebSocket connection maintains a persistent, low-latency link between the cloud proxy and the local device, allowing real-time communication without the overhead of HTTP polling.
Technical Deep Dive
The ESP32 runs a custom firmware that establishes an outbound WebSocket connection to a Cloudflare Worker. This outbound connection is crucial—it means the ESP32 initiates the connection rather than waiting for inbound requests, which would be blocked by most home routers’ NAT. Once the WebSocket connection is established, the Cloudflare Worker can forward HTTP requests to the ESP32, which processes them and sends back responses through the same WebSocket channel.
The firmware typically uses the Arduino framework or ESP-IDF (ESP32’s native development framework) with WebSocket libraries. The code handles HTTP request parsing, response generation, and connection management—all within the tight constraints of the ESP32’s resources. With only 520KB of SRAM and limited flash storage, every byte of code and every processing cycle counts.
The Cloudflare Worker script is relatively simple JavaScript that intercepts HTTP requests, forwards them to the ESP32 via WebSocket, waits for the response, and returns it to the client. This script also handles error cases, timeouts, and connection failures, ensuring the system remains robust even when the ESP32 is temporarily unavailable.
Why This Approach Works
Several factors make this architecture viable:
Resource Efficiency: The ESP32 consumes minimal power (around 100-500mA depending on activity) and can run for months on a small battery or USB power adapter. Its passive cooling means no fans or moving parts.
Network Simplicity: By using outbound connections only, the ESP32 avoids complex port forwarding or dynamic DNS setups. It works seamlessly behind any NAT without router configuration.
Cloud Integration: Cloudflare provides enterprise-grade security, SSL termination, and global distribution without any additional infrastructure. The ESP32 only needs to maintain one WebSocket connection.
Scalability Boundaries: While the ESP32 itself is limited to handling a few concurrent connections, the Cloudflare Worker can cache responses and handle traffic spikes, effectively scaling the system beyond the hardware’s native capabilities.
Real-World Performance
The original implementation mentioned in the Reddit post ran for approximately 500 days continuously before hardware failure—an impressive feat for a $10 device. This longevity demonstrates the reliability of the ESP32 platform and the robustness of the architecture. The system handled regular web traffic, likely serving static content or simple dynamic pages, without any manual intervention for over a year.
The relaunch of the project shows that this isn’t just a one-time experiment but a viable approach for certain use cases. Whether serving a personal homepage, a status dashboard, or a simple API endpoint, the ESP32 web server proves that powerful infrastructure isn’t always necessary for basic web services.
Prerequisites and Requirements
Hardware Requirements
ESP32 Development Board: Any ESP32-based board will work, but the ESP32-WROOM-32 module is recommended for its balance of performance and power consumption. Popular development boards include the ESP32 DevKitC, NodeMCU ESP32, or Wemos LOLIN32.
Power Supply: A 5V USB power adapter or battery pack. The ESP32 typically draws 100-500mA during operation, so a 1A supply is sufficient.
Network Connection: Ethernet or Wi-Fi connectivity. Wi-Fi is more common for wall-mounted installations, but Ethernet provides more stability if the ESP32 is near a network port.
Optional Components: For wall mounting, you’ll need mounting hardware, a small enclosure for protection, and possibly a temperature sensor or other peripherals depending on your application.
Software and Development Tools
ESP32 Development Environment:
- Arduino IDE with ESP32 board support package
- OR PlatformIO for more advanced development
- OR ESP-IDF for native C/C++ development
WebSocket Libraries:
- ArduinoWebsockets library for Arduino IDE
- OR esp32-websocket-client for ESP-IDF
Cloudflare Account: Free tier is sufficient for most use cases. You’ll need to configure Workers and potentially a Workers KV for caching.
Programming Knowledge: Basic understanding of C/C++ for ESP32 firmware, JavaScript for Cloudflare Workers, and HTTP/WebSocket protocols.
Network and Security Considerations
Firewall Configuration: While the ESP32 uses outbound connections only, your network firewall should allow HTTPS (port 443) outbound traffic. Most networks allow this by default.
Security Hardening: The ESP32 firmware should validate all incoming requests, implement rate limiting, and handle malformed data gracefully. Since the ESP32 is behind Cloudflare, it’s protected from direct internet exposure.
Monitoring and Alerting: Implement health checks and error logging. The ESP32 can send status updates to a monitoring service or log to a local file system if storage is available.
Pre-Installation Checklist
Before beginning the installation, ensure you have:
- A working ESP32 development board with USB cable
- The ESP32 board support package installed in your development environment
- A Cloudflare account with Workers enabled
- Network connectivity for both development and the final installation
- Basic tools for wall mounting (drill, screws, level)
- A test web application or content to serve
Installation and Setup
ESP32 Firmware Development
The ESP32 firmware is the heart of this system. We’ll create a simple web server that maintains a WebSocket connection to Cloudflare and serves HTTP requests.
1
2
3
4
# Install Arduino IDE and ESP32 board support
# Download from https://www.arduino.cc/en/software
# Add ESP32 board URL in Preferences: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
# Install "ESP32 Dev Module" from Board Manager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// ESP32 Web Server Firmware
#include <Arduino.h>
#include <WiFi.h>
#include <ArduinoWebsockets.h>
using namespace websockets;
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* cloudflareWorkerURL = "wss://your-worker.cloudflareworkers.com";
WebsocketsClient client;
bool connectedToCloudflare = false;
void onMessageCallback(WebsocketsMessage message) {
// Handle HTTP request from Cloudflare
String request = message.data();
// Parse HTTP request and generate response
String response = handleHTTPRequest(request);
// Send response back through WebSocket
client.send(response);
}
void connectToWiFi() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
}
}
void connectToCloudflare() {
if (client.connect(cloudflareWorkerURL)) {
connectedToCloudflare = true;
client.onMessage(onMessageCallback);
}
}
void setup() {
Serial.begin(115200);
connectToWiFi();
connectToCloudflare();
}
void loop() {
if (connectedToCloudflare && client.available()) {
client.poll();
} else if (!connectedToCloudflare) {
connectToCloudflare();
}
delay(500);
}
String handleHTTPRequest(String request) {
// Simple HTTP request parser
if (request.indexOf("GET / HTTP/1.1") >= 0) {
return "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>Hello from ESP32!</h1>";
}
return "HTTP/1.1 404 Not Found\r\n\r\n";
}
Cloudflare Worker Configuration
The Cloudflare Worker acts as the reverse proxy, handling public HTTP requests and forwarding them to the ESP32 via WebSocket.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Cloudflare Worker Script
const ESP32_WEBSOCKET_URL = 'wss://your-worker.cloudflareworkers.com';
async function handleRequest(request) {
// Establish WebSocket connection to ESP32
const ws = new WebSocket(ESP32_WEBSOCKET_URL);
return new Promise((resolve, reject) => {
ws.onmessage = (event) => {
// ESP32 responded with HTTP response
const response = new Response(event.data, {
headers: { 'Content-Type': 'text/html' }
});
resolve(response);
ws.close();
};
ws.onopen = () => {
// Forward HTTP request to ESP32
const httpRequest = [
`${request.method} ${request.url} HTTP/1.1`,
`Host: ${request.headers.get('host')}`,
`User-Agent: ${request.headers.get('user-agent')}`
].join('\r\n') + '\r\n\r\n';
ws.send(httpRequest);
};
ws.onerror = () => {
reject(new Response('ESP32 connection failed', { status: 502 }));
};
setTimeout(() => {
if (ws.readyState === WebSocket.CONNECTING) {
ws.close();
reject(new Response('Timeout connecting to ESP32', { status: 504 }));
}
}, 5000);
});
}
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
Deployment and Testing
1
2
3
4
5
6
7
8
9
10
11
# Upload ESP32 firmware using Arduino IDE
# Sketch > Upload
# Deploy Cloudflare Worker
# Log into Cloudflare dashboard
# Navigate to Workers > Manage Workers
# Create new worker and paste the JavaScript code
# Deploy and test
# Test the setup
curl https://your-domain.com
Configuration and Optimization
ESP32 Performance Tuning
The ESP32’s limited resources require careful optimization:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Memory optimization
#pragma GCC optimize ("Os") // Optimize for size
// Connection pooling
#define MAX_SIMULTANEOUS_REQUESTS 3
// Timeout settings
#define WEBSOCKET_TIMEOUT 10000 // 10 seconds
#define HTTP_REQUEST_TIMEOUT 5000 // 5 seconds
// Buffer management
#define REQUEST_BUFFER_SIZE 2048
char requestBuffer[REQUEST_BUFFER_SIZE];
// Power management
void enterDeepSleep() {
ESP.deepSleep(300 * 1000000); // Sleep for 5 minutes
}
Security Hardening
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Request validation
bool validateRequest(String request) {
// Check for valid HTTP methods
if (!request.startsWith("GET") && !request.startsWith("POST")) {
return false;
}
// Check for header injection attempts
if (request.indexOf("\r\n\r\n") == -1) {
return false;
}
return true;
}
// Rate limiting
unsigned long lastRequestTime = 0;
const unsigned long minRequestInterval = 1000; // 1 second
bool isRateLimited() {
unsigned long currentTime = millis();
if (currentTime - lastRequestTime < minRequestInterval) {
return true;
}
lastRequestTime = currentTime;
return false;
}
Cloudflare Worker Optimization
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Caching strategy
const CACHE_TTL = 60; // Cache responses for 60 seconds
async function handleRequest(request) {
// Check cache first
const cache = caches.default;
let response = await cache.match(request);
if (!response) {
// Forward to ESP32 if not cached
response = await forwardToESP32(request);
event.waitUntil(cache.put(request, response.clone(), { ttl: CACHE_TTL }));
}
return response;
}
// Error handling and fallback
async function forwardToESP32(request) {
try {
const ws = new WebSocket(ESP32_WEBSOCKET_URL);
return await websocketWithTimeout(ws, request);
} catch (error) {
return new Response('Service unavailable', { status: 503 });
}
}
Usage and Operations
Daily Operations
The ESP32 web server requires minimal day-to-day management once deployed:
1
2
3
4
5
6
7
8
9
10
11
12
13
# Monitor ESP32 status
# Use serial monitor in Arduino IDE
# Tools > Serial Monitor
# Set baud rate to 115200
# Check connection status
# ESP32 will print connection messages to serial
# Look for "Connected to WiFi" and "Connected to Cloudflare"
# Monitor Cloudflare Worker
# Use wrangler CLI for advanced debugging
npm install -g @cloudflare/wrangler
wrangler tail --format=json
Maintenance Procedures
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Firmware update procedure
void checkForUpdates() {
if (shouldCheckForUpdates()) {
// Download new firmware from server
// Verify checksum
// Perform OTA update
}
}
// Health monitoring
void sendHeartbeat() {
// Send periodic status updates to monitoring service
// Include uptime, memory usage, connection status
}
Backup and Recovery
Since the ESP32 serves content dynamically, backups focus on the configuration and firmware:
1
2
3
4
5
6
7
8
9
# Backup ESP32 configuration
# Save WiFi credentials and settings to file
# Store in secure location
# Firmware version control
# Use Git to track firmware changes
git init
git add .
git commit -m "Initial ESP32 firmware commit"
Troubleshooting
Common Issues and Solutions
Connection Failures:
1
2
3
4
# Check WiFi credentials
# Verify network connectivity
# Test WebSocket connection manually
curl -v wss://your-worker.cloudflareworkers.com
Memory Issues:
1
2
3
4
5
6
// Monitor free memory
Serial.print("Free Heap: ");
Serial.println(ESP.getFreeHeap());
// Reduce buffer sizes if needed
#define REQUEST_BUFFER_SIZE 1024 // Reduce from 2048
Performance Problems:
1
2
3
// Check for blocking operations
// Optimize string handling
// Use PROGMEM for static data
Debug Commands
1
2
3
4
5
6
7
8
9
# ESP32 debugging
# Serial output for troubleshooting
Serial.println("Debug message");
# WiFi status
WiFi.printDiag(Serial);
# Memory diagnostics
Serial.printf("Flash size: %u bytes\n", ESP.getFlashChipSize());
Performance Tuning
1
2
3
4
5
6
7
8
9
10
// Optimize string operations
String response = String("HTTP/1.1 200 OK\r\n") +
"Content-Type: text/html\r\n" +
"\r\n" +
"<h1>Hello</h1>";
// Use const char* for static strings
const char* htmlTemplate = "<h1>%s</h1>";
char responseBuffer[512];
snprintf(responseBuffer, 512, htmlTemplate, "Hello from ESP32");
Conclusion
The ESP32 wall-mounted web server represents a fascinating exploration of what’s possible when we push the boundaries of embedded systems and web infrastructure. For just $10 in hardware and some creative engineering, we’ve created a public-facing web server that runs continuously, requires minimal maintenance, and demonstrates the power of modern microcontroller platforms.
This project isn’t about replacing traditional web infrastructure—it’s about understanding the fundamental principles of how web services work and exploring the limits of what’s achievable with minimal resources. The combination of ESP32’s low-power capabilities, Cloudflare’s edge computing platform, and WebSocket technology creates a robust, scalable solution that would have been impossible just a few years ago.
The success of running this system for 500+ days continuously proves that with the right architecture, even the simplest hardware can provide reliable services. Whether you’re a homelab enthusiast, a DevOps engineer looking to understand infrastructure fundamentals, or simply someone who enjoys pushing technological boundaries, this ESP32 web server offers valuable insights into efficient system design.
As microcontroller technology continues to advance and edge computing becomes more prevalent, we’ll likely see more innovative approaches to infrastructure that challenge our assumptions about what’s required to run web services. The $10 wall-mounted web server is just the beginning of this exploration.
For those interested in diving deeper, I recommend exploring the ESP32’s advanced features like Bluetooth connectivity, deep sleep modes, and peripheral integration. The combination of these capabilities with cloud services opens up endless possibilities for creative infrastructure projects.
The future of web infrastructure may not be in larger, more powerful servers, but in the intelligent combination of minimal hardware, cloud services, and efficient software design. This ESP32 project demonstrates that sometimes, the most elegant solutions come from thinking small rather than thinking big.