Display your Pi-hole stats on your TRMNL e-ink display.
- DNS Stats: Total requests, blocked requests, blocking percentage, query frequency (queries/second)
- System Health: CPU usage, RAM usage, temperature, uptime
- Connected Clients: Number of devices using your Pi-hole
- Top Blocked Domains: Most frequently blocked domains
- Historical Chart: Recent data points showing blocked, cached, and forwarded queries
This guide assumes you already have Pi-hole installed and running on your device.
You'll need:
- A Raspberry Pi (or similar device) with Pi-hole installed
- SSH access to your Pi-hole (root or sudo privileges required for installation)
- A TRMNL account
Tested on:
- Raspberry Pi 3B+ with DietPi (DietPi_RPi234-ARMv8-Bookworm.img.xz)
- Raspberry Pi 4 with Raspberry Pi OS
Pi-hole v6 requires authentication for API access. The installer will automatically detect if your Pi-hole needs a password and prompt you if so.
You can use either your Pi-hole login password or an app password. App passwords are recommended since they can be revoked independently without changing your main login, and they bypass 2FA.
To generate an app password:
- Open your Pi-hole Admin UI
- Go to Settings → Web Interface / API
- Switch from Basic to Expert mode
- Click Configure app password
- Copy the generated password (shown only once)
If you don't have a password set on Pi-hole, the installer will skip authentication entirely.
The plugin uses a smart alternating pattern to stay within TRMNL's free tier limits (12 requests/hour, 2KB per request):
Every 15 minutes (default):
- Always sends: Stats, System metrics (CPU/RAM/Temp), Host info
- Alternates: History chart OR Top blocked domains
Example hourly schedule:
:00 → Stats + History (2 requests)
:15 → Stats + Domains (2 requests)
:30 → Stats + History (2 requests)
:45 → Stats + Domains (2 requests)
Total: 8 requests/hour ✅ (well within 12/hour limit)
The installer creates a state file (~/.pihole-trmnl-state) that automatically tracks what was sent last:
- State shows
history→ Next run sends Domains - State shows
domains→ Next run sends History - No manual intervention needed!
Data is carefully optimized to stay within TRMNL's 2KB limit:
- Stats payload: ~600 bytes (essential metrics only)
- History payload: ~600 bytes (4 data points)
- Domains payload: ~800 bytes (top 10 domains)
- Total combined: ~1,700 bytes ✅ (well under 2KB)
Uses TRMNL's deep_merge strategy for efficient updates.
SSH into your Pi-hole and run:
bash <(curl -fsSL https://raw.githubusercontent.com/rishikeshsreehari/trmnl-pihole/main/install.sh)- Checks dependencies - Installs
jqif needed - Asks for your TRMNL webhook URL
- Asks for Pi-hole URL - Defaults to
http://localhost - Auto-detects authentication - Tests the API, only asks for password if needed
- Validates Pi-hole responses - Ensures real data is received before sending to TRMNL
- Sends initial data - Establishes complete data structure
- Creates state file - Starts alternating tracking
- Sets up cron job - Defaults to every 15 minutes (customizable)
- Creates log file - Track all updates at
~/trmnl-push.log
- Go to TRMNL Private Plugins
- Click "New Private Plugin"
- Set strategy to webhook
- Copy the markup from
template.liquidin this repository - Copy your Webhook URL (looks like:
https://trmnl.com/api/custom_plugins/xxxxx-xxxx-xxxx)
Video instructions by RelfWolf: https://www.youtube.com/watch?v=OnRFtMqgquk
View logs:
tail -f ~/trmnl-push.logCheck what's being sent:
tail -20 ~/trmnl-push.logCheck state file:
cat ~/.pihole-trmnl-stateTest manually:
~/push-pihole-to-trmnl.shChange update frequency:
crontab -eChange */15 * * * * to your preferred interval:
- Every 5 minutes:
*/5 * * * *(12 requests/hour - at free tier limit) - Every 10 minutes:
*/10 * * * *(12 requests/hour - at free tier limit) - Every 15 minutes:
*/15 * * * *(8 requests/hour - recommended ✅) - Every 20 minutes:
*/20 * * * *(6 requests/hour) - Every 30 minutes:
*/30 * * * *(4 requests/hour)
Note: Each run sends 2 webhooks (Stats + alternating chart data), so frequency × 2 = requests/hour.
Update password (if your Pi-hole password changes):
echo 'your_new_password' > ~/.pihole-trmnl-credsLogs show detailed information for each update:
2026-01-13 00:15:02 - TRMNL Push Started
🔐 Authenticated successfully
Fetching stats data...
2026-01-13 00:15:03 - Stats Update
Payload size: 532 bytes
Sending: IDX_0 (Stats), IDX_1 (System), IDX_2 (Sensors), IDX_5 (Host)
HTTP Status: 200
✅ Success
---
2026-01-13 00:15:04 - Domains Update
Payload size: 817 bytes
Sending: IDX_4 (Top 10 blocked domains)
HTTP Status: 200
✅ Success
---
🔐 Session closed
If authentication or data fetching fails, the script will log clear error messages and abort rather than sending empty data to TRMNL.
Stats Payload (every 15 min):
- Total queries, blocked queries, % blocked, frequency
- CPU usage, RAM usage, temperature, uptime
- Hostname, active clients
History Payload (every 30 min):
- Last 4 data points for chart (optimized for size)
- Blocked, cached, forwarded counts per interval
Domains Payload (every 30 min):
- Top 10 blocked domains with request counts
This plugin reads data from the following Pi-hole v6 API endpoints:
| Endpoint | Data | IDX |
|---|---|---|
/api/stats/summary |
Queries, blocked count, clients, gravity | IDX_0 |
/api/info/system |
CPU usage, RAM usage, uptime | IDX_1 |
/api/info/sensors |
CPU temperature | IDX_2 |
/api/history |
Historical query data (last 4 points) | IDX_3 |
/api/stats/top_domains?blocked=true |
Top 10 blocked domains | IDX_4 |
/api/info/host |
Hostname | IDX_5 |
/api/auth |
Session authentication (POST) | — |
All endpoints are read-only. The plugin never modifies your Pi-hole configuration.
If you'd like to build your own integration or adapt this for a different display, feel free to use these endpoints directly. The Pi-hole API documentation has full details, and your Pi-hole also serves its own API docs at http://<your-pihole>/api/docs.
{
"IDX_0": {/* Essential Pi-hole stats (queries, blocked, cached, percent) */},
"IDX_1": {"system": {/* CPU, RAM, uptime */}},
"IDX_2": {"sensors": {/* temperature */}},
"IDX_3": {"history": [/* 4 data points */]},
"IDX_4": {"domains": [/* 10 domains */]},
"IDX_5": {"host": {/* hostname */}}
}The deep_merge strategy lets us update only parts of the data:
- Initial setup sends everything once
- Each update only sends changed sections
- TRMNL merges new data with existing data
- Reduces bandwidth and stays within 2KB limit
TRMNL free tier: 12 requests/hour, 2KB per request
- Send Stats every 15 min (always needed for display)
- Alternate History/Domains every 30 min (chart data doesn't need constant updates)
- 2 webhooks per run × 4 runs/hour = 8 requests/hour
- Leaves 4 requests/hour buffer for manual tests
tail -f ~/trmnl-push.logLook for ✅ Success messages and HTTP Status 200. If you see ❌ Failed to fetch or ❌ Authentication failed, check the suggestions below.
~/push-pihole-to-trmnl.shcurl http://localhost/api/stats/summaryIf this returns {"error":{"key":"unauthorized"...}}, your Pi-hole requires authentication. Re-run the installer and enter your password or app password when prompted.
If this fails entirely, verify Pi-hole is running: pihole status
| Problem | Cause | Fix |
|---|---|---|
| Zeros on TRMNL screen | Pi-hole auth required but not configured | Re-run installer, enter password when prompted |
❌ Authentication failed in logs |
Password changed or incorrect | Update: echo 'new_pass' > ~/.pihole-trmnl-creds |
HTTP Status: 429 |
Too many requests (TRMNL or Pi-hole rate limit) | Wait a few minutes and try again |
❌ Empty response |
Pi-hole not reachable | Check base URL and that Pi-hole is running |
| Script works manually but not via cron | PATH or environment issue | Check crontab -l for the correct script path |
- Verify your password works:
curl -s -X POST "http://localhost/api/auth" -H "Content-Type: application/json" -d '{"password":"your_password"}' - You should see
"valid": trueand asidin the response - If using 2FA, you must use an app password (regular password requires a TOTP code)
- If you get
429, wait a minute — Pi-hole rate-limits login attempts
SSH into your Pi-hole and run:
bash <(curl -fsSL https://raw.githubusercontent.com/rishikeshsreehari/trmnl-pihole/main/uninstall.sh)This removes the push script, cron job, state file, log file, and credentials file. It does not affect your Pi-hole installation or settings.
If you prefer to remove components manually:
# Remove the cron job
crontab -l | grep -v "push-pihole-to-trmnl.sh" | crontab -
# Remove all files
rm ~/push-pihole-to-trmnl.sh
rm ~/.pihole-trmnl-state
rm ~/.pihole-trmnl-creds
rm ~/trmnl-push.log
# Verify
crontab -l | grep pihole # Should return nothingContributions, ideas, and feedback are welcome! Feel free to open an issue or submit a pull request.
For issues or setup help, feel free to reach out via:
- Discord: rishikeshs
- Email: hello@rishikeshs.com
- GitHub Issues: Open an issue
Need a custom TRMNL plugin? Get in touch!
Use this link or the code Rishikesh10 on checkout to get $10 off on your TRMNL purchase.
If you find this useful, support at: r1l.in/s
- Built for TRMNL
- Pi-hole API documentation: Pi-hole Docs
- Created by Rishikesh
MIT License - see LICENSE file for details