TECHNOLOGY
Open-Source Project esp32 c3 adblock Runs a Pi-hole-Style DNS Ad-Blocker on a $2 ESP32-C3 Chip by Storing Hashes in Flash
The esp32-c3-adblock project stores blocklisted domains as sorted 40-bit hashes in flash and binary-searches them, letting a PSRAM-less ESP32-C3 act as a DNS sinkhole with a web dashboard. The developer says it uses about 50 KB of RAM.
Image: IntraGoals Media · Uploaded by IntraGoals — usage rights confirmed
A new open-source project shows that a DNS ad-blocker in the style of Pi-hole can run on an ESP32-C3, a microcontroller board that costs about $2 and has no PSRAM. The project, called esp32-c3-adblock, was shared on Hacker News and is hosted on GitHub. Its README says it has been featured by Tom's Hardware, XDA Developers and Korben; that coverage has not been independently verified here and should be checked against the primary sources before publication.
The central idea is that the blocklist does not need to sit in RAM. Many ESP32 DNS sinkholes load blocklisted domain names as strings into memory, which typically requires a board with PSRAM, at a cost the README puts at about $8. This project instead stores each domain as a fixed 5-byte (40-bit) FNV-1a hash in a sorted table in flash memory and searches it with a binary search. According to the README, more than 140,000 domains fit in roughly 0.7 MB of flash, lookups take about 10 ms including a WiFi round trip, and the device uses around 50 KB of RAM.
When a DNS query arrives, the firmware extracts the domain, hashes it along with its parent suffixes, and searches the flash table. A match is answered with 0.0.0.0, which sinkholes the request. A miss is forwarded to an upstream resolver and the reply is relayed to the client. A blocked domain also blocks its subdomains. The README says a lookup takes about 18 flash reads.
The author explains the choice of 40 bits as a trade-off governed by the birthday bound. At 141,000 domains the project reports no hash collisions, and at 537,000 domains it reports about one, meaning a single domain could be blocked by mistake. The README says 32-bit hashes would save about 20 percent of flash but cause around seven collisions at 250,000 domains, while 64-bit hashes would waste three bytes per domain. It also says the approach is not limited to the C3: on a 16 MB ESP32-S3, the hashes could hold about 2.7 million domains, versus about 466,000 strings in 8 MB of PSRAM.
The device is tested on an ESP32-C3 SuperMini with 4 MB of flash. A classic ESP32 build is available as a community contribution and is described as compile-tested only. The project includes a web dashboard showing per-client block and allow counts, with options to ban a client and add custom domains, plus mDNS discovery at c3adblock.local. A printable enclosure file is provided, along with advice to keep the antenna end clear and the vents open. The README says the board idles at about 45 to 55 degrees Celsius.
Setup uses PlatformIO. Users copy a secrets template, build a blocklist with a Python script, and flash the firmware and filesystem over USB once. The default list combines StevenBlack's base list and Hagezi Light, at roughly 100,000 entries. The build script also accepts hosts files, plain domain lists and basic AdGuard or Adblock rules, and skips rules a hash list cannot express, such as regular expressions and wildcards. If the device cannot join WiFi, it opens a captive-portal access point for setup.
After the first flash, both firmware and blocklist can be updated over WiFi. The device can pull a prebuilt blocklist on a schedule, and the project says GitHub Actions rebuilds a default list every Monday. There is a storage trade-off on 4 MB boards: firmware OTA requires two app slots, leaving about 1.3 MB for the blocklist, or roughly 250,000 domains. The larger 537,000-domain list fits only with a single-app partition table, which rules out firmware OTA.
The README devotes considerable space to security. State-changing dashboard endpoints require HTTP Basic Auth, network OTA requires a password, and mutating requests need a custom X-Requested-With header to block cross-site request forgery. Custom domain names are HTML-escaped to close a stored cross-site scripting path. The author cautions that everything runs over plain HTTP, so Basic Auth protects against other LAN devices and CSRF but not against an attacker who can sniff network traffic. If users leave the placeholder passwords from the example file, the device still boots, logs a warning and shows a dashboard banner. The setup access point is intentionally open.
The README also lists practical gotchas, including ModemManager on Fedora and Ubuntu interfering with the serial port, and a requirement that blocked DNS replies contain only the question and answer to avoid being malformed when clients send EDNS records. Planned work includes a bucketed prefix index to cut lookups to one or two flash reads and the ability to act as a DHCP server. The project credits s60sc/ESP32_AdBlocker as inspiration for the idea of answering 0.0.0.0 for blocklisted domains, and describes itself as an independent from-scratch implementation.
The figures in this article come from the project's own README and have not been independently benchmarked. Editors should verify them against the linked repository and video before publication.