Shekhar Vaidya is a veteran technology journalist and computer science engineer. He is the founder of TechLatest, where he has spent years providing technical analysis on hardware and Windows ecosystems. Now a Computing Writer at XDA, Shekhar leverages his deep background in NAS, storage solutions, and PC internals to help readers master their tech.
I have been hosting websites and web apps for more than half a decade. I started with shared web hosting, and today I rent multiple VPSs. My formula was simple: build the app locally, once done, rent a VPS as per its requirements, install an OS (usually Ubuntu or Debian), and host it.
For the last few weeks, I have been building and testing small IoT projects on microcontrollers like ESP32. A couple of days ago, while reading about the board, I came across async web servers. It instantly clicked: if an ESP32 can run an asynchronous web server, then I can easily host a website on it. I built it, and it turned out it sure can host a website. I expected it to break after a handful of visitors.
A tiny test page proved the idea, then showed me where it breaks
It didn’t crash; it just stopped answering
I’ve hosted on everything from shared web hosting to large VPSs, but a microcontroller for hosting a website? I couldn’t have believed it a month ago. It was just a concept for me. So, before hosting anything real, I wanted to test the theory. The question was whether an ESP32 can even host a website and serve HTTP requests reliably.
I could have used PlatformIO, but Arduino IDE was already set up on my PC. It needed two more libraries, ESPAsyncWebServer and AsyncTCP, other than the usual ones, to flash a sketch to an ESP32. One sketch, one simple HTML file, and a load-testing Python script were all it took.
I compiled the sketch and uploaded it to the board. It was up and running within a few seconds. It was my first success. It proved what I had only read about. The next question was whether it could handle more than one user. The 520KB SRAM was making me think otherwise. Then came the load testing script.
The script was programmed to ramp through 1, 5, 10, 25, 50, and 100 concurrent connections, spending 15 seconds at each level. The sketch also exposed two small endpoints – /ping and /stats. The ping endpoint measured just the connection, and the stats endpoint kept all the statistics of each connection, like uptime, free heap, and total hits.
After I ran the same load test script against three parameters, this was the actual picture:
|
Metric |
Result |
|---|---|
|
Page size sent |
~2.3KB (uncompressed) |
|
Peak throughput, the page |
~200 requests/s |
|
Peak throughput, /ping |
~250 requests/s |
|
Median latency at 1 connection |
~7.8ms |
|
Throughput peaks at |
~10 concurrent connections |
|
First errors appear at |
25 concurrent connections |
|
Errors at 100 connections |
458 |
|
Minimum free heap |
137–140KB |
|
Reboots |
None |
1, 5, and 10 concurrent connections didn’t sweat the board. The first meaningful performance problems started appearing at 25 connections. On the main HTML page, the test recorded 6 errors at 25 connections and 458 errors at 100 connections. The latency also rose with each level.
But the good thing was that the board never rebooted, nor did the free heap memory ever drop below the expected range of 137–140KB. So, the board only struggled with the concurrent connections and not the HTTP responses, which I initially assumed. It was a good starting point for hosting a real website on the board.
I swapped in my real portfolio, and the heavier page cost me a third of the speed
The page tripled in size, and the errors went down
The test page proved that the board could host a webpage, but it wasn’t a real-world use case. There was a real constraint: no disk like a server, just 4 MB of flash, and the SRAM allowed only 320 KB as usual data. So, I couldn’t pick any of the full-stack apps and move one onto the board. I needed a simple website that could be hosted easily.
Then I remembered I had my custom portfolio built on Next.js hosted on Cloudflare Pages. It was the best one to move to the board. But obviously, I couldn’t just host a Next JS app on an ESP32. So, I decided to convert the app to a simple HTML/CSS project and then host it.
My current portfolio has multiple pages, but I started by moving only the homepage to see how it went. When it felt alright, I moved the fuller version with a homepage, projects index, seven case studies, and a blog.
Note that I didn’t move each and every element. For example, the hero image and the three project screenshots on the homepage were over 500 KB together. So, I skipped all the images. I used inline SVG icons and system fonts. And finally, I pre-gzipped all the pages and served them from flash.
Then I started with the same load tests I had with the test page. And the results were promising:
|
Mertric |
Test Page |
Portfolio |
|---|---|---|
|
Page size sent |
~2.3KB |
~6.9KB (gzipped; ~20 KB raw) |
|
Home page throughput (5–10 connections) |
~200 requests/s |
~125–130 requests/s |
|
Median latency at 1 connection |
~7.8ms |
~13ms |
|
Errors at 100 connections |
458 |
~111–128 |
|
Minimum free heap |
~137–140KB |
~80–87KB |
|
Reboots |
None |
None |
Yes, if I compare these to the test page, it fell by about a third, because the page tripled from ~2.3 KB to ~6.9KB over the wire (~20KB raw). And more importantly, it was a real website that could be used on a daily basis.
There was one interesting thing: the homepage throughput fell by 35–40%, and the median response time rose from 7.8ms to 13ms, but the errors at 100 concurrent connections fell from 458 on the test page to 111–128 on the portfolio. And even the memory headroom dropped from 135-140KB to 80-87KB, the board never rebooted, and that was the actual win.
By the end, when I looked at the page hits, it was around 70k requests, at about 125 per second on the home page. It was already far beyond what a normal portfolio or even mine usually needs. But I was performing these tests on my LAN. So, I decided to put it on the internet and check how it performed.
I took the test public, and the numbers stopped telling the whole story
4,530 requests in, the board had logged about a dozen
Moving it to the internet was a simple job for me. I already had a public IP from my ISP, and Nginx Proxy Manager (NPM) was already there on my home Debian server. So, it took me a few minutes to set it up.
DHCP reservation on my ER605 gateway, a new DNS-only subdomain record on Cloudflare, a new host on NPM with a Let's Encrypt certificate, a new cache zone for the ESP32 proxy host on my home server, and done. Caching was an important part of this whole setup. I kept the cached pages for 5 minutes and cached /stats for 10 seconds. And since it was publicly accessible, I blocked access to /ping.
And to make it more real, I decided to move the load test to a new machine. I chose my Hetzner VPS in Nuremberg to exercise the public internet path. I ran two sets of tests, the first with caching and the second with --bust (each request with a unique query string) to bypass the cache.
In the cached run, the VPS generated around 4,530 requests, and the board’s hit counter moved by only a dozen, and out of those 12, 7-8 were me refreshing the page. But this was all cache doing its magic. The board stats were surprising too: min heap unchanged at around 80KB, median latency around 158ms (even after the wider network route), and only one error at 25 concurrent connections and ten at 50.
The bust run was promising too. It generated 1810 successful client responses with the medium latency at 180ms, zero errors at 10 connections, and ten at 25.
I noticed one unexpected behavior too. The server stalled a few times when making connections from the VPS, to be precise, around four of 100 fresh connections. One while connecting, and three after the TLS handshake, before the first byte. And all of them were on the cache as well as the bust run, so I didn’t identify the actual reasons.
Zooming out from individual tests, the proxy cache was most helpful when the site was public, as the cache absorbed almost all requests without burdening the board. But even without the cache, it was able to handle the traffic comfortably. And that is the whole point.
It’s not replacing my hosting, and that was never the point
Coming back to the question I started with: can a $5 ESP32 host a real website? The answer is yes, and the portfolio is the proof. The board’s resources are a real constraint, but the portfolio requires far fewer resources than it offers, so it was a smooth test.
But this doesn’t mean it can replace traditional hosting. A static portfolio has a much smaller workload than a full-stack React application with a database, backend, and auth. I don't know how it will perform when I keep it online for a month or a year, but I am at least keeping it live for the next few weeks to do a soak test.
In the end, I didn’t build it to replace free hosting like Cloudflare Pages and GitHub Pages, but to find out how far inexpensive hardware could go when the workload is chosen carefully.
- AITRIP
- UART, USB
The ESP32 is a fantastic development board that combines solid specs with an affordable price. Despite being cheaper than Arduino and Raspberry Pi Pico, it outperforms most of its rivals. Plus, the ESP32 even has Wi-Fi and Bluetooth functionality built into every board, making it great for projects where you can't physically keep the microcontroller connected to your PC at all times.















