Project Overview & Difficulty Rating
The most effective way to deploy a network-wide ad block Raspberry Pi is to pair a low-power board with Pi-hole v6 and add physical hardware telemetry. While a headless Pi-hole works fine, adding a 0.96-inch I2C OLED display and a PWM-controlled cooling fan transforms it from a hidden utility into a visible, bench-grade network appliance. This build targets the Raspberry Pi Zero 2 W, leveraging its 1.0GHz quad-core processor and 512MB RAM to handle DNS filtering for networks up to 50 devices without breaking a sweat.
Time to Complete: 90 minutes
Estimated Cost: $28 - $35 USD
Hardware Spec Sheet & Parts List
Do not use a standard Raspberry Pi 4 or 5 for this unless you need to run additional heavy services (like a local media server). The Pi Zero 2 W draws roughly 1.2W under load, making it ideal for 24/7 DNS operations. Furthermore, standard microSD cards will burn out from Pi-hole's constant query logging; you must use an A2-rated high-endurance card.
| Component | Exact Variant / Model | Estimated Price |
|---|---|---|
| Microcontroller | Raspberry Pi Zero 2 W (v1.0) | $15.00 |
| Storage | Samsung EVO Plus 32GB microSD (A2 rated) | $8.00 |
| Display | 0.96" SSD1306 I2C OLED (128x64, 4-pin) | $4.50 |
| Cooling | 5V 30x30x10mm PWM Cooling Fan (4-pin) | $3.00 |
| Power | Official Raspberry Pi 5.1V 2.5A Micro-USB PSU | $8.00 |
Pin Mapping & Wiring Diagram
The Raspberry Pi Zero 2 W only exposes hardware PWM on specific GPIO pins. We must use BCM 18 (Physical Pin 12) for the fan to ensure smooth speed control without CPU-spiking software PWM. The OLED connects to the primary I2C bus.
| Component | Pi Zero 2 W Pin (BCM) | Physical Pin # | Function |
|---|---|---|---|
| OLED VCC | 3V3 Power | 1 | 3.3V Logic Power |
| OLED GND | GND | 6 | Ground Reference |
| OLED SCL | GPIO 3 (SCL1) | 5 | I2C Clock |
| OLED SDA | GPIO 2 (SDA1) | 3 | I2C Data |
| Fan PWM (Blue) | GPIO 18 | 12 | Hardware PWM Signal |
| Fan VCC (Red) | 5V Power | 2 or 4 | 5V Fan Power |
| Fan GND (Black) | GND | 9 | Ground Reference |
Step-by-Step Build & Installation
- Flash the OS: Use Raspberry Pi Imager to flash Raspberry Pi OS Lite (64-bit) to your Samsung EVO Plus card. In the advanced settings (Ctrl+Shift+X), pre-configure your WiFi, enable SSH, and set a static IP address (e.g., 192.168.1.10).
- Install Pi-hole: SSH into the Pi and run the official installer:
curl -sSL https://install.pi-hole.net | bash. Select your upstream DNS provider (Quad9 or Cloudflare recommended) and enable the web interface. - Enable I2C: Run
sudo raspi-config, navigate to Interface Options > I2C, and enable it. Reboot the Pi. - Install Python Dependencies: Install the required libraries for the OLED and hardware control:
sudo apt update && sudo apt install python3-pip python3-pil i2c-tools -ypip3 install Adafruit-SSD1306 gpiozero requests --break-system-packages - Verify I2C: Run
i2cdetect -y 1. You should see3cin the grid, confirming the SSD1306 is wired correctly.
Python Telemetry Script
This script queries the local Pi-hole API, calculates CPU temperature, drives the PWM fan based on thermal thresholds, and renders the stats to the OLED. Save this as pihole_monitor.py.
import time
import requests
import Adafruit_SSD1306
from PIL import Image, ImageDraw, ImageFont
from gpiozero import PWMOutputDevice, CPUTemperature
# Pin Definitions
OLED_I2C_ADDR = 0x3C
FAN_PWM_PIN = 18 # BCM 18 supports hardware PWM
# Hardware Initialization
disp = Adafruit_SSD1306.SSD1306_128_64(rst=None, i2c_bus=1, gpio_scl=3, gpio_sda=2)
disp.begin()
disp.clear()
disp.display()
fan = PWMOutputDevice(FAN_PWM_PIN)
cpu = CPUTemperature()
width = disp.width
height = disp.height
image = Image.new('1', (width, height))
draw = ImageDraw.Draw(image)
font = ImageFont.load_default()
def get_pihole_stats():
# Pi-hole v6 requires local API access; fallback handled in except block
try:
r = requests.get('http://127.0.0.1/admin/api.php?summary', timeout=3)
r.raise_for_status()
return r.json()
except Exception as e:
return {'error': str(e)}
try:
while True:
stats = get_pihole_stats()
temp = cpu.temperature
# Thermal Fan Control Logic
if temp > 60.0:
fan.value = 1.0 # 100% duty cycle
elif temp > 50.0:
fan.value = 0.6 # 60% duty cycle
elif temp > 40.0:
fan.value = 0.3 # 30% duty cycle
else:
fan.value = 0.0 # Fan off
draw.rectangle((0, 0, width, height), outline=0, fill=0)
if 'error' in stats:
draw.text((0, 0), 'API OFFLINE', font=font, fill=255)
draw.text((0, 15), 'Check FTL Svc', font=font, fill=255)
else:
blocked = stats.get('ads_blocked_today', '0')
percent = stats.get('ads_percentage_today', '0.0')
draw.text((0, 0), f'Blocked: {blocked}', font=font, fill=255)
draw.text((0, 15), f'Ratio: {percent}%', font=font, fill=255)
draw.text((0, 30), f'CPU: {temp:.1f}C', font=font, fill=255)
draw.text((0, 45), f'Fan: {fan.value*100:.0f}%', font=font, fill=255)
disp.image(image)
disp.display()
time.sleep(5)
except KeyboardInterrupt:
disp.clear()
disp.display()
fan.off()
print('Monitor stopped safely.')
Debugging: Ranked Causes for Common Failures
When the script crashes or the OLED stays blank, check these three things first: (1) I2C wiring continuity, (2) Pi-hole FTL service status (sudo systemctl status pihole-FTL), and (3) Python environment paths.
1. OSError: [Errno 121] Remote I/O error
Cause: The Pi cannot communicate with the SSD1306 over the I2C bus. This is almost always a physical wiring fault or a missing pull-up resistor (though most OLED modules have them onboard).
Fix: Run i2cdetect -y 1. If the grid is empty, check your SDA/SCL solder joints. If you see 3c but still get this error, your I2C bus is locked. Reboot the Pi and ensure no other Python script is holding the I2C bus open in the background.
2. requests.exceptions.ConnectionError: HTTPConnectionPool
Cause: The Python script cannot reach the Pi-hole API at 127.0.0.1. This happens if Pi-hole's FTL (Faster Than Light) DNS service has crashed, or if you are running Pi-hole v6 with strict API token enforcement enabled without passing the header.
Fix: Verify FTL is running with pihole status. If you upgraded to Pi-hole v6 and enabled API authentication, you must add the token header to the Python script: headers = {'X-FTL-API-Token': 'YOUR_TOKEN_HERE'} and pass it into the requests.get() call.
3. ModuleNotFoundError: No module named 'Adafruit_SSD1306'
Cause: Raspberry Pi OS Bookworm (and newer) enforces PEP 668, preventing global pip install commands to protect system packages.
Fix: Use the --break-system-packages flag as shown in the setup steps, or preferably, create a virtual environment: python3 -m venv ~/pihole_env, activate it, and install the dependencies inside it.
Extending or Simplifying the Build
To Simplify: If you don't want to mess with GPIO and I2C, drop the OLED and fan. Install Pi-hole on the Pi Zero 2 W, plug it directly into your router's LAN port via a Micro-USB to Ethernet adapter, and run it completely headless. It will draw less than 1W and remain entirely silent.
To Extend: For networks with over 100 devices or heavy local DNS caching, upgrade to a Raspberry Pi 5 (4GB). Add the Unbound recursive DNS resolver to eliminate reliance on third-party upstream DNS providers entirely. You can also integrate a UPS HAT (like the PiSugar 3) to ensure your network's DNS survives brief power outages without corrupting the SD card database.
Frequently Asked Questions
Can I use an ad block Raspberry Pi on WiFi instead of Ethernet?
Yes, but it is highly discouraged for DNS servers. WiFi introduces latency spikes and packet loss during RF interference, which manifests as "slow internet" or timed-out web pages for your users. If you must use WiFi, ensure the Pi Zero 2 W is within 10 feet of the router on a clear 5GHz channel, and disable WiFi power management in /etc/NetworkManager/conf.d/default-wifi-powersave-on.conf by setting wifi.powersave = 2.
Does a Pi-hole ad block Raspberry Pi setup slow down my internet?
No. A properly configured Pi-hole actually decreases page load times. By blocking ad-serving domains at the DNS level, your browser never attempts to download heavy tracking scripts or video ads. The Pi Zero 2 W can process over 10,000 DNS queries per second, which is far beyond the throughput of any residential broadband connection.
How do I bypass the ad block Raspberry Pi for specific devices like a smart TV?
Do not disable Pi-hole globally. Instead, assign a static IP to the smart TV in your router's DHCP settings, and configure that specific IP to use your ISP's default DNS servers (or Cloudflare's 1.1.1.1) directly in the TV's network menu. Alternatively, use Pi-hole's built-in "Group Management" feature to create a client group that bypasses the blocklists entirely based on the device's MAC address.






