esp32-c3-adblock: A $2 DNS ad blocker holding 537k domains
A home-network DNS blocker for ESP32-C3 that stores domain hashes in flash instead of RAM.
GitHub M-Abozaid/esp32-c3-adblock Updated 2026-10-06 Branch main Stars 1.3K Forks 120
C++ Python C DNS ad blocking ESP32-C3 PlatformIO FNV-1a Home networking

🧭 Decision Guide

Try it if you

  • You have a 4 MB ESP32-C3 SuperMini and want to run the roughly 100k default blocklist without PSRAM.
    README sections Hardware and Build & flash (PlatformIO): C3 SuperMini, 4 MB flash, no PSRAM, and a default list of about 100k entries.
  • You need to place the DNS blocker on a router USB port and can accept roughly 10 ms hash lookup latency.
    README sections Hardware and Why this is interesting: a USB-A → USB-C dongle can connect it to a router, and lookup takes about 10 ms.
  • You want to flash over USB once, then maintain the blocklist and firmware through c3adblock.local.
    README sections Build & flash (PlatformIO) and Over-the-air updates: firmware and blocklist can both be updated over WiFi afterward.

Skip it if you

  • You must use the 537k ultimate list and firmware OTA on hardware with only 4 MB of flash.
    README section Over-the-air updates: OTA needs two app slots and limits the blocklist to about 250k; the 537k list requires the single-app partition table.
  • Your threat model includes LAN traffic sniffing and requires an encrypted management interface.
    README section Security: all traffic uses plain HTTP on :80, and Basic Auth credentials are sent as base64 rather than encrypted.
  • You plan to expose the device to an untrusted network or run with the default CHANGE_ME credentials.
    README section Security: the default credentials are public, and the project explicitly describes Basic Auth as a LAN-trust-boundary control.
  • You only have an old apt PlatformIO package such as 4.3.4.
    README section Build & flash (PlatformIO): the distro/apt PlatformIO package, such as 4.3.4, fails with AttributeError: ... 'resultcallback'.

Requirements

  • An ESP32-C3 board, tested on a C3 SuperMini, with 4 MB flash and no PSRAM needed
  • A stable USB source, such as a phone charger or router's USB port
  • Current PlatformIO; the README says apt PlatformIO 4.3.4 is too old
  • Configure WEB_USER, WEB_PASS, and OTA_PASS in src/secrets.h first
  • The first USB flash requires pio run -t upload and pio run -t uploadfs

First step (verbatim from README)

cp src/secrets.example.h src/secrets.h

Watch out

  • Cheap or loose USB-C→A adapters can cause a brownout during WiFi transmission.
    README section Hardware: cheap/loose USB-C→A adapters can brown out the radio during WiFi transmit.
  • With unchanged secrets.h, the device still boots but logs a warning and shows a public-password banner.
    README section Security: CHANGE_ME_WEB_PASSWORD and CHANGE_ME_OTA_PASSWORD trigger a warning, but the firmware still runs.
  • The C3-AdBlock-XXXX WiFi setup access point remains open and unencrypted.
    README section Security: the setup portal access point is still open (unencrypted) by design.
  • Custom /addblock domains are HTML-escaped, but network OTA still requires OTA_PASS.
    README section Security: custom names are HTML-escaped, and ArduinoOTA requires OTA_PASS.

Alternatives

  • Traditional ESP32 + PSRAM DNS sinkhole:It is better when you need to keep domain strings directly in RAM or can accept ESP32 + PSRAM hardware costing about $8.
    README section Why this is interesting

Not stated in the README

  • The README does not specify concurrent DNS query volume or throughput limits during long-term operation.
  • The README does not specify the default upstream resolver, timeout, or retry behavior.
  • The README does not provide complete flashing commands for the 537k ultimate list under each partition table.
  • The README omits the detailed Enclosure, Gotchas, and Done / how it could grow sections.
  • The README does not provide a per-model compatibility list for ESP32-C3 boards beyond the C3 SuperMini.

💡 Deep Analysis

6
It depends I need the Web dashboard, blocklist upload, and firmware OTA on a home LAN. Is the security boundary sufficient if the device still uses HTTP Basic Auth and an open setup AP?
For: An embedded maintainer placing the DNS device on a trusted home LAN and needing Web-based blocklist and firmware OTA updates

It depends: it is acceptable on a trusted home LAN, but not on public WiFi, an exposed network, or a LAN with potential traffic sniffers.

  • The README’s Security section requires WEB_USER/WEB_PASS for state-changing endpoints, OTA_PASS for network OTA, and the custom X-Requested-With: c3-adblock header to reduce common CSRF attacks.
  • However, the dashboard is plain HTTP on port 80 and Basic Auth is only Base64. The README explicitly says open/guest WiFi, ARP spoofing, or an on-path sniffer can read credentials.
  • If CHANGE_ME_WEB_PASSWORD or CHANGE_ME_OTA_PASSWORD remains in place, the public placeholder values from the example file still work. The C3-AdBlock-XXXX setup AP is also intentionally open and unencrypted.
  • The security model is therefore “trusted-LAN protection against ordinary unauthenticated API access,” not TLS or public-facing administration security.
  • README, “Security”: mutating endpoints require HTTP Basic Auth; OTA requires `OTA_PASS`; `X-Requested-With: c3-adblock` is required
  • README, “Security”: everything is plain HTTP on port 80 and Basic Auth credentials are Base64
  • README, “Security”: default placeholder passwords are public and the WiFi setup AP remains open and unencrypted
Not stated in the README:The README does not say whether an external reverse proxy can provide TLS for the dashboard;The README does not document failed-login throttling, audit logs, or account lockout
Yes I need to handle 141k to 537k domains on a no-PSRAM ESP32-C3 while controlling RAM and lookup latency. Is a 40-bit FNV-1a hash table with Flash binary search suitable?
For: An embedded algorithm developer researching low-memory indexes and maintaining 141k-to-537k domain lists on a no-PSRAM ESP32-C3

Yes, because it is designed specifically around the RAM and Flash limits of a no-PSRAM device, but you must accept the possibility of false blocking from hash collisions.

  • The README says domains are converted into sorted 40-bit hashes stored in Flash; more than 141k domains use about 0.7 MB, with roughly 50 KB of RAM at runtime.
  • The device computes FNV-1a, checks parent suffixes, and binary-searches the Flash table. The README cites about 18 Flash reads and roughly 10 ms including WiFi RTT.
  • Its collision estimate is about zero at 141k domains and about one at 537k, making 40 bits a tradeoff among storage, lookup cost, and false-blocking risk rather than a collision-free guarantee.
  • The bucketed prefix index is still marked incomplete and aims to reduce about 18 reads to 1–2; that improvement is not part of the current implementation.
  • README, “Why this is interesting”: sorted 40-bit hashes in Flash, about 50 KB RAM, and 141,000+ domains in about 0.7 MB
  • README, “Why 40 bits?”: about zero collisions at 141k and about one at 537k
  • README, “Done / how it could grow”: bucketed prefix index is unchecked, with a target of reducing about 18 reads to 1–2
dig @<c3-ip> doubleclick.net   # -> 0.0.0.0  (blocked)
Not stated in the README:The README does not explain how to identify the original domain or remove a false block after a collision;The README does not provide a full latency distribution across WiFi conditions, upstream resolvers, and query concurrency
No I want all home and small-office clients to use the ESP32-C3 for DNS-based ad blocking. Does it meet my requirements if the device briefly loses power or clients use DoH/DoT?
For: A home or small-office network administrator who wants the ESP32-C3 to be the sole DNS server for browsers and smart-home devices

No, it is not suitable as the sole, non-bypassable DNS infrastructure because device outages can affect resolution and encrypted DNS can bypass it.

  • The project insight identifies the ESP32 as a single point of failure: power loss, WiFi interruption, firmware faults, or Flash damage can affect dependent DNS clients, so a fallback is needed when it is the only DNS server.
  • The README’s “Use it” only requires clients to point DNS at the device or use it as a secondary resolver; automatic DHCP DNS distribution is not provided, and acting as a DHCP server is still an unchecked item.
  • The project insight states that DoH/DoT, hard-coded IPs, or alternate DNS paths can bypass UDP-DNS filtering. The implementation only sinkholes matches and forwards misses.
  • It is therefore better suited to home, small-office, and experimental networks as an auxiliary resolver than as an enterprise enforcement point.
  • README, “Done / how it could grow”: “Act as the DHCP server” remains unchecked
  • README, “Use it”: point a device’s DNS at the C3’s IP or add it as a secondary resolver
  • Project insight, usage_limitations: DoH/DoT, hard-coded IPs, caches, or alternate DNS can bypass filtering; the ESP32 is a single point of failure
dig @<c3-ip> doubleclick.net   # -> 0.0.0.0  (blocked)
Not stated in the README:The README does not say whether clients automatically fail over when the device is offline;The README does not provide network-side controls to block DoH/DoT, IPv6 DNS, or hard-coded DNS
No I only have a 4 MB-flash, no-PSRAM ESP32-C3 SuperMini. Can I use this project with about 537k domains while keeping firmware OTA?
For: An embedded developer using a 4 MB-flash, no-PSRAM ESP32-C3 SuperMini who wants 537k-domain blocking and WiFi OTA for a home network

No, not for both goals at once: a 4 MB Flash layout cannot hold the largest list and a standard firmware OTA layout simultaneously.

  • The project targets an ESP32-C3 without PSRAM; 40-bit hashes store about 537k domains in Flash while using about 50 KB of RAM.
  • The project data states that enabling dual application partitions for firmware OTA leaves about 1.3 MB for the blocklist, limiting it to roughly 250k domains. The roughly 537k-domain list requires a single-application layout.
  • You therefore have to trade coverage for remote firmware maintenance: keep OTA with a smaller list, or use the larger list and retain a USB recovery path.

The README does not provide the complete PlatformIO partition definitions, so the exact capacity of a custom layout is unknown.

  • README, “Why this is interesting”: 537,000 domains, 40-bit hashes, and about 50 KB RAM
  • Project insight, common_pitfalls: dual-app OTA on 4 MB Flash leaves about 1.3 MB for the blocklist, or roughly 250k domains
  • Project insight, best_practices: the largest blocklist requires a larger blocklist partition and a USB recovery path
Not stated in the README:The README does not include complete partition tables or exact blocklist limits for every layout;The README does not state the exact binary size of the 537k list for a specific firmware revision
Yes I only have a roughly $2 no-PSRAM ESP32-C3 and want to plug it into my router’s USB port for home ad blocking. Is this project more suitable operationally than a Raspberry Pi setup?
For: A PlatformIO-familiar DIY enthusiast with a roughly $2 ESP32-C3, stable USB power, and a router USB port who wants to avoid a Raspberry Pi or separate server

Yes, it suits a home deployment focused on low cost, small size, and no separate server, but it still requires PlatformIO, USB flashing, and router DNS configuration skills.

  • The README explicitly describes a roughly $2, no-PSRAM ESP32-C3 Pi-hole-style DNS ad blocker; a USB-A-to-USB-C dongle can plug it into a spare USB port on many routers.
  • If WiFi cannot connect or secrets.h is not configured, it starts the open C3-AdBlock-XXXX AP and captive portal, so initial WiFi setup does not require reflashing.
  • It includes a Web dashboard, statistics, manual bans, custom domains, blocklist updates, and firmware OTA, shortening maintenance compared with repeated USB reflashing.
  • The tradeoff is that deployment is not fully plug-and-play: the README still requires pointing client DNS at the device, and stable power matters because cheap or loose USB-C adapters can cause brownouts during WiFi transmission.
  • README opening: Pi-hole-style DNS ad blocker on a $2 ESP32-C3 with no PSRAM required
  • README, “Hardware”: USB-A-to-USB-C dongle can plug into many router USB ports; stable USB source is required
  • README, “WiFi setup”: failure to connect starts the `C3-AdBlock-XXXX` captive portal
  • README, “Done / how it could grow”: Web dashboard, OTA, and scheduled remote blocklist pulls are complete
dig @<c3-ip> doubleclick.net   # -> 0.0.0.0  (blocked)
Not stated in the README:The omitted Build & flash section does not provide the complete command sequence from repository checkout to first flash;The README does not quantify long-term power consumption, router USB compatibility, or recovery time after power loss
It depends My board is not the primary tested ESP32-C3 but a 4 MB-flash Classic ESP32 DevKit/WROOM. Can I use this project directly as my home-network DNS?
For: An embedded developer familiar with Arduino and PlatformIO who wants to turn a 4 MB Classic ESP32 DevKit/WROOM board into a DNS sinkhole

It depends: Classic ESP32 builds are supported, but the ESP32-C3 is the primary test target, and effective home DNS blocking depends on clients actually sending queries to the device.

  • The README’s Hardware section says Classic ESP32 DevKit/WROOM boards with 4 MB can build and gives pio run -e esp32dev -t upload; it also says this is community-contributed and compile-tested, while the C3 is the tested target.
  • The architecture returns 0.0.0.0 for blocked domains and forwards misses to an upstream resolver, providing both sinkholing and normal DNS resolution.
  • Use requires pointing clients at the device IP or placing it as a secondary resolver behind the main DNS. DoH/DoT, hard-coded DNS, or alternate resolvers can bypass filtering.

The README does not specify Classic ESP32 latency, maximum list size, or long-term stability.

  • README, “Hardware”: Classic ESP32 DevKit/WROOM, 4 MB, builds with `pio run -e esp32dev -t upload`; the C3 is the tested target
  • README, “Use it”: point a device’s DNS at the C3’s IP or add it as a secondary resolver
  • README flow diagram: hits return 0.0.0.0 and misses are forwarded to the upstream resolver
pio run -e esp32dev -t upload
Not stated in the README:The README does not provide measured RAM use, Flash blocklist capacity, or throughput for Classic ESP32;The README does not fully document IPv6 DNS, router DHCP behavior, or client fallback DNS behavior

✨ Highlights

  • Runs a 537k-domain list on an ESP32-C3 without PSRAM
  • 40-bit FNV-1a hashes use about 50 KB of RAM
  • Flash binary search matches DNS queries in about 10 ms
  • With OTA enabled, 4 MB flash limits the list to about 250k domains

🔧 Engineering

  • The ESP32-C3 uses a UDP DNS sinkhole to block domains and forward misses
  • PlatformIO flashes firmware and blocklist.bin, with WiFi OTA updates afterward
  • http://c3adblock.local provides DNS stats, list upload, and firmware OTA

⚠️ Risks

  • HTTP Basic Auth protects endpoints over plain HTTP :80 and does not provide TLS
  • The two-app partition required for OTA limits blocklist capacity to about 250k on 4 MB flash
  • At 537k domains, 40-bit hashes are expected to have one collision and may over-block a domain
  • Leaving CHANGE_ME passwords in secrets.h exposes the LAN interface with public credentials

👥 For who?

  • Home-network developers with a 4 MB ESP32-C3 SuperMini using PlatformIO
  • Embedded developers needing to host the roughly 100k default list without PSRAM
  • Network users wanting a lightweight DNS secondary resolver for blocking