A $2 Chip Is Now a Credible Resolver
I have run Pi-hole on a Raspberry Pi 3B, on a Pi 4 with an SSD hat, and currently in a Proxmox LXC with two vCPUs and 512MB allocated. Every one of those deployments is overkill for what DNS filtering fundamentally is: a lookup against a sorted list. Developer M-Abozaid's `esp32-c3-adblock` project makes that point uncomfortably well. It holds **537,000 blocked domains**, answers a blocked query in about **10 milliseconds**, uses roughly **50KB of RAM**, and runs on an ESP32-C3 SuperMini with 4MB of flash and *no PSRAM at all*.
Tom's Hardware pegged the hardware at $5. The project README says $2. Either number is a rounding error next to a Pi and an SD card. The repo sat at 577 stars when I checked it, which tells you the homelab crowd understood immediately what they were looking at.
The Trick: Don't Store Domains, Store Hashes
Every other ESP32 sinkhole I have looked at hits the same wall — the blocklist lives in RAM as strings, so you need PSRAM, so you need a bigger board. s60sc's long-running `ESP32_AdBlocker` states it plainly in its own docs: an ESP32-S3 with 8MB of PSRAM can host a current blocklist, while a plain ESP32 with 4MB "may truncate" one. That is the accepted cost of entry.
`esp32-c3-adblock` refuses to pay it. It never stores the domain strings. Each blocklist entry is reduced to a **40-bit FNV-1a hash** — five bytes — written into flash as a single immutable sorted table. A query arrives, the firmware extracts the domain, hashes it plus every parent suffix (so a lookup for `ads.tracker.example.com` also tests `tracker.example.com` and `example.com`, giving you wildcard behavior for free), then binary-searches the flash table. A hit returns `0.0.0.0` immediately. A miss forwards upstream.
The engineering numbers behind that: roughly **0.7MB of flash per 140,000 domains**, about **18 flash reads per lookup**, and ~50KB of RAM total. The blocklist doesn't need memory because there is nothing in memory to need.
The hash width is the interesting gamble. At 141,000 domains the author measured **zero collisions**. At 537,000 domains, about **one**. That is not zero, and it matters operationally — one legitimate domain, somewhere in the world, resolves to `0.0.0.0` on your network and the dashboard will cheerfully tell you it was on the blocklist. It wasn't. Good luck reasoning about that at 11pm. It is a genuinely acceptable trade at this price point, but it is a trade, and anyone telling you a hash table is lossless is selling something.
Flash Layout Is the Real Config Decision
Here is the fork in the road that will actually affect your deployment: the partition scheme. A **dual-app layout**, which keeps firmware OTA working, caps you at about **250,000 domains**. A **single-app layout**, which drops firmware OTA, gets you the full **537,000**. Blocklist OTA updates still work either way — you only lose the ability to push new firmware remotely.

My take: on a device that costs two dollars and sits on the desk next to your rack, dual-app OTA is a luxury you are paying 287,000 domains for. Take the domains and keep a USB-C cable in the drawer. On the unit you shipped to your parents' house, keep OTA.
Latency Is Never the Lookup — It's the Transport
That ~10ms figure includes Wi-Fi round-trip time, and that framing matters. Compare it to `ESP32_AdBlocker_Reborn`, a PSRAM-based fork running on a LilyGO T-ETH-Elite with a wired W5500 Ethernet controller. Its published benchmarks: **1.8ms p50** for a blocked domain, with a floor of **0.66ms**, and **~40ms** for a cold query that has to be forwarded upstream. Throughput plateaus at **~2,200 queries per second with zero drops** — and the author is explicit that the constraint is the W5500's 40MHz SPI link, *not the CPU*.
The actual binary search? About **64 microseconds** in an -O2 release build. s60sc measures its PSRAM lookups at **under 50µs** on an S3 and **under 100µs** on a plain ESP32.
So the compute is three orders of magnitude below the network cost. Your DNS filter is not slow because it is checking a list. It is slow because it is on Wi-Fi. The 8ms delta between the wireless C3 and the wired S3 is the single most important number in this entire article, because every device on your LAN hits your resolver thousands of times an hour. If you can put your sinkhole on copper, put it on copper.
The PSRAM Route Is Faster, Wired, and Holds Less
The `Reborn` build is the enthusiast configuration: ESP32-S3-WROOM-1, 16MB flash, 8MB of OPI PSRAM, W5500 Ethernet, an SD slot, 32-bit MurmurHash3 hashes held in PSRAM with the same binary-search-plus-suffix-walk approach, a 1024-slot forward cache eating about 545KB, and an SD instant-boot path that reloads the sorted table in roughly one second instead of a multi-minute HTTPS download. Its blocklist is the full OISD big list at about **333,000 wildcard domains**.
Notice the inversion. The $2 board with no PSRAM holds **537,000** domains. The board with 8MB of PSRAM holds **333,000**. Flash is simply bigger than RAM, and once you accept a hash table you stop caring that flash is slower. Obvious in hindsight. Nobody did it for years.
What You Actually Give Up
Credit where it is due — this is not as bare-bones as I expected. The dashboard does per-client block/allow counts, lets you ban a client outright, and supports custom domains. Setup is captive-portal, discovery is mDNS at `c3adblock.local`, blocklists update over the air, and it is MIT licensed.

What is missing is still real. **No DHCP server** — it is listed as a future item, which means your existing DHCP infrastructure has to hand out its address as the resolver. **No documented DNS-over-TLS or DNS-over-HTTPS upstream.** Read that one twice. If your forwarded misses leave the box as plaintext UDP/53 to your ISP's resolver, you have blocked the ad networks and handed your full query stream to the one party with the strongest commercial interest in it. You have relocated the privacy problem, not solved it. Pi-hole and AdGuard Home both fix this with an encrypted upstream, and it is the main reason I would not make this my primary resolver today.
You also lose regex rules, conditional forwarding, and any real query log. A five-byte hash can tell you *that* something matched. It can never tell you *what list it came from* or *why*.
And one deployment warning that has nothing to do with the hardware: do not hand this out as a secondary resolver alongside an unfiltered one via DHCP option 6. Clients do not fail over the way you think — many race both resolvers or pin whichever answers first, and you will get intermittent, unreproducible blocking that costs you a Saturday. It is the primary resolver for a network segment, or it is nothing.
Where It Belongs in a Real Homelab
Not as a drop-in replacement for a properly configured AdGuard Home with per-VLAN policy and DoH upstream. But there are slots in my rack this fills better than anything else at any price:
**The IoT VLAN.** A segment where you want everything filtered aggressively, nothing needs nuanced policy, and you frankly do not care if a smart plug's telemetry endpoint gets a collision-induced `0.0.0.0`. Give the VLAN its own resolver and stop routing IoT DNS through your main box.
**The guest network.** Same logic. Isolated, disposable, filtered.
**A cold spare.** SD card death is the number one killer of Pi-based Pi-hole deployments — I have lost three. A C3 with an immutable flash table has no SD card to corrupt and no filesystem to journal. Keep one flashed in a drawer, and when the Pi dies mid-week your household DNS is back in ninety seconds.
**Remote support duty.** A relative's house where you want filtering, cannot justify a Pi, and need something you can power off the router's own USB port. Keep dual-app OTA enabled on that one.
Verdict
This does not replace Pi-hole. It redefines the floor. The bar for entry into DNS filtering just dropped from "a spare Raspberry Pi, an SD card, and a Docker install" to "two dollars and a USB port," and the resulting device answers faster than the software stack it is undercutting. Every homelab should have one running on a segment where the stakes are low and the query volume is high.
Then fix the upstream encryption story, because a filtered resolver leaking plaintext queries is a half-finished job. If it's not on your own hardware, it's not really yours — but it also has to actually be *private* to count.
Loading comments...