Using a raspberry pi to block ads via Pi-hole is the most effective way to scrub trackers and video pre-rolls from your entire network. But when a legitimate site breaks because of an overzealous blocklist, logging into a web dashboard from your phone to toggle the DNS filter is tedious. This guide builds a dedicated Pi-hole appliance with a physical hardware kill-switch and an I2C OLED status monitor, giving you instant visual feedback and one-button troubleshooting.
The Verdict: Which Raspberry Pi to Block Ads?
Pi-hole is incredibly lightweight. It does not need the raw compute of a desktop replacement. Below is the decision matrix for selecting your board in 2026.
| Board Variant | Price (Approx) | Power Draw | Verdict |
|---|---|---|---|
| Raspberry Pi Zero 2 W | $15 | ~1.2W | Best for advanced users. Requires soldering or micro-USB dongles for Ethernet. |
| Raspberry Pi 4 Model B (2GB) | $45 | ~2.7W | Default Pick. Gigabit Ethernet, full-size GPIO, runs cool without a fan. |
| Raspberry Pi 5 (4GB) | $60 | ~5.5W+ | Overkill. Requires active cooling and a 27W USB-C PD supply. Waste of silicon for DNS. |
Exact Parts List
- Compute: Raspberry Pi 4 Model B (2GB)
- Storage: 32GB SanDisk Extreme microSD (U3, A2 rated for high IOPS)
- Display: 0.96" I2C OLED (SSD1306 driver, 128x64, 4-pin)
- Switch: 6x6mm Tactile Pushbutton (Normally Open)
- Resistor: 10kΩ through-hole (for external pull-up, optional but recommended)
- Power: 5V 3A USB-C Power Supply (Official Raspberry Pi or Anker)
Hardware Assembly and Pin Mapping
The code provided below targets the Raspberry Pi 4 Model B, but the GPIO mapping is identical for the Pi Zero 2 W and Pi 5. We are using the hardware I2C bus for the display and a standard GPIO pin with an internal pull-up for the button.
| Component | Pi Physical Pin | GPIO / Bus | Function |
|---|---|---|---|
| OLED VCC | 1 | 3V3 | Logic Power |
| OLED GND | 6 | GND | Common Ground |
| OLED SDA | 3 | GPIO 2 (I2C1) | I2C Data |
| OLED SCL | 5 | GPIO 3 (I2C1) | I2C Clock |
| Button Leg 1 | 11 | GPIO 17 | Toggle Input (Active Low) |
| Button Leg 2 | 9 | GND | Switch Ground |
Software Setup: Pi-hole and Python Monitor
Flash Raspberry Pi OS Lite (64-bit, Bookworm) to your SD card using the official Imager. Enable SSH and set your WiFi/hostname in the imager settings. Boot the Pi, SSH in, and install Pi-hole via the official automated script:
curl -sSL https://install.pi-hole.net | bash
Once Pi-hole is running, install the Python dependencies for our hardware monitor. We use luma.oled for the display and gpiozero for the button.
sudo apt update
sudo apt install python3-pip python3-full i2c-tools
sudo raspi-config nonint do_i2c 0
sudo pip3 install --break-system-packages luma.oled gpiozero requests
The Compilable Python Monitor Script
Create a file named pihole_monitor.py. This script queries the local Pi-hole CLI to avoid API token rotation issues in v6, updates the OLED, and listens for the button press to toggle blocking.
#!/usr/bin/env python3
"""
Pi-hole Hardware Monitor & Kill-Switch
Target: Raspberry Pi 4 Model B (2GB) / Pi Zero 2 W
Requires: luma.oled, gpiozero
"""
import time
import subprocess
import json
from gpiozero import Button
from luma.core.interface.serial import i2c
from luma.oled.device import ssd1306
from luma.core.render import canvas
from PIL import ImageFont
# --- PIN & HARDWARE DEFINITIONS ---
BUTTON_PIN = 17 # GPIO 17 (Physical Pin 11)
I2C_PORT = 1 # /dev/i2c-1
I2C_ADDR = 0x3C # Standard SSD1306 address
# Initialize Hardware
try:
serial = i2c(port=I2C_PORT, address=I2C_ADDR)
device = ssd1306(serial)
button = Button(BUTTON_PIN, pull_up=True, bounce_time=0.2)
except Exception as e:
print(f"Hardware Init Failed: {e}")
exit(1)
def get_pihole_stats():
"""Fetches stats via Pi-hole CLI JSON output to bypass v6 API auth."""
try:
result = subprocess.run(['pihole', '-c', '-j'], capture_output=True, text=True, check=True)
data = json.loads(result.stdout)
return {
"status": "ACTIVE" if data.get("FTL", {}).get("query") else "IDLE",
"blocked": data.get("FTL", {}).get("blocked", 0),
"percent": data.get("FTL", {}).get("percent", 0.0)
}
except subprocess.CalledProcessError:
return {"status": "ERROR", "blocked": 0, "percent": 0.0}
def toggle_pihole():
"""Toggles Pi-hole blocking state."""
stats = get_pihole_stats()
try:
if stats["status"] == "ACTIVE":
subprocess.run(['pihole', 'disable'], check=True)
else:
subprocess.run(['pihole', 'enable'], check=True)
except subprocess.CalledProcessError as e:
print(f"Toggle failed: {e}")
button.when_pressed = toggle_pihole
# --- MAIN LOOP ---
while True:
stats = get_pihole_stats()
with canvas(device) as draw:
# Using default font for maximum compatibility
draw.text((0, 0), f"Status: {stats['status']}", fill="white")
draw.text((0, 20), f"Blocked: {stats['blocked']}", fill="white")
draw.text((0, 40), f"Ratio: {stats['percent']}%", fill="white")
draw.text((0, 55), "[BTN] Toggle", fill="white")
time.sleep(2)
Run the script with root privileges: sudo python3 pihole_monitor.py. To make it run on boot, create a systemd service file at /etc/systemd/system/pihole-monitor.service.
Debugging: Exact Error Strings and Ranked Fixes
When working with I2C and system-level subprocess calls on Pi OS Bookworm, you will hit specific permission and bus errors. Here is the exact decision path for the three most common failures.
1. "OSError: [Errno 121] Remote I/O error"
- Cause A (Most Likely): I2C is disabled in the OS. Fix: Run
sudo raspi-config-> Interface Options -> I2C -> Enable. - Cause B: Wrong I2C address. Fix: Run
i2cdetect -y 1. If your OLED shows up as0x3Dinstead of0x3C, update theI2C_ADDRvariable in the script. - Cause C: SDA/SCL swapped. Fix: Verify physical pins 3 and 5.
2. "subprocess.CalledProcessError: Command '['pihole', 'disable']' returned non-zero exit status 1"
- Cause A: Script is not running as root. The
piholeCLI requires sudo to modify DNS services. Fix: Execute withsudo python3 pihole_monitor.py. - Cause B: Pi-hole FTL service is crashed. Fix: Run
sudo systemctl restart pihole-FTL.
3. "gpiozero.exc.BadPinFactory: Unable to load any default pin factory"
- Cause A: Missing backend library in your Python environment. Fix:
sudo apt install python3-rpi.gpio. - Cause B: Running inside a standard user venv without passing system site-packages. Fix: Run the script in the global system Python environment or use
--system-site-packageswhen creating your venv.
First Three Things to Check When the Network Fails
If you set your router's DHCP to point to the Pi and suddenly the entire house loses internet, don't panic. Run this diagnostic sequence:
- Verify the Pi-hole FTL Service: SSH into the Pi and run
sudo systemctl status pihole-FTL. If it's dead, your DNS is dead. Restart it withsudo systemctl restart pihole-FTL. - Check Upstream Resolution: Pi-hole might be running, but its upstream DNS (e.g., Cloudflare 1.1.1.1) might be failing. Run
ping 1.1.1.1from the Pi. If it times out, your Pi's internet connection is down, not Pi-hole itself. - Inspect the Router DHCP Scope: Ensure your router is handing out the Pi's static IP (e.g.,
192.168.1.10) as the primary DNS, and a public DNS (like8.8.8.8) as the secondary. If you put the Pi as both primary and secondary, a Pi reboot takes the whole house offline.
Extending or Simplifying the Build
How to Simplify
If you don't want to wire hardware, strip the Python script down to just the get_pihole_stats() function and output the data to an MQTT broker (like Mosquitto). You can then read the stats in Home Assistant without needing an OLED or GPIO button. Just run Pi-hole headless and manage it via the web UI.
How to Extend (The Smart TV Bypass)
Modern Smart TVs (Samsung Tizen, LG WebOS) often ignore DNS settings and hardcode their own tracking IPs, bypassing Pi-hole entirely. To fix this, extend the build by adding a 5V Relay Module to GPIO 27. Wire the TV's power line through the relay's Normally Closed (NC) contacts. Update the Python script to trigger the relay for 5 seconds if the TV's specific MAC address is detected on the network making direct DNS requests to 8.8.8.8. It's aggressive, but it guarantees compliance.
For official network architecture guidance, refer to the Pi-hole documentation and the Raspberry Pi hardware guides.






