- CLI method (SSH to Pi-hole, edit custom.list) - Web admin method (http://pi.hole/admin) - API method (Pi-hole v6+ REST API with session auth) - SDK options (pyhole6, python-hole) - Workflow pattern for adding internal services QGIS configured on port 3141 (π for science) Compass audit report created - phantom files identified
6.0 KiB
6.0 KiB
description
| description |
|---|
| Pi-hole local DNS management workflow - adding internal domains |
Pi-hole Local DNS Management
Pi-hole Location: 10.10.20.5 (Pi-hole VM or container) Access: Requires SSH key authentication Last Updated: 2026-03-28
Quick Reference
Add Local DNS Record
Method 1: CLI (SSH to Pi-hole)
# SSH to Pi-hole
ssh root@10.10.20.5
# Add DNS entry
echo "10.10.20.XXX domain.wiuf.net" >> /etc/pihole/custom.list
# Restart DNS
pihole restartdns
# Verify
pihole -q domain.wiuf.net
Method 2: Web Admin
- Navigate to: http://10.10.20.5/admin (or pi.hole/admin)
- Login with admin password
- Local DNS DNS Records
- Domain:
domain.wiuf.net - IP Address:
10.10.20.XXX - Click Add
- Verify in Custom List section
Common Internal Domains
| Domain | IP | Service | Added |
|---|---|---|---|
qgis.wiuf.net |
10.10.20.120 | QGIS Server | 2026-03-28 |
deluge.wiuf.net |
10.10.20.120 | Deluge WebUI | TBD |
plex.wiuf.net |
10.10.20.120 | Plex Media | TBD |
Remove DNS Record
CLI:
# Remove specific line
sed -i '/domain.wiuf.net/d' /etc/pihole/custom.list
pihole restartdns
Web Admin:
- Click Remove next to entry in Custom List
Verify DNS Resolution
# From any client using Pi-hole
dig @10.10.20.5 domain.wiuf.net
# Or nslookup
nslookup domain.wiuf.net 10.10.20.5
# Test with ping
ping domain.wiuf.net
Workflow Pattern
When Adding New Internal Service:
- Deploy service with compose on target server
- Determine IP (usually 10.10.20.XXX where service runs)
- Choose domain (format:
servicename.wiuf.net) - Add to Pi-hole local DNS
- Verify resolution from client
- Document in this file
- Update infrastructure map if needed
Security Notes
- Only add .wiuf.net domains for internal services
- External domains go through Cloudflare DNS
- Ensure service is properly firewalled before exposing
- Traefik/Pangolin handles external routing for some services
Troubleshooting
| Issue | Solution |
|---|---|
| DNS not resolving | Check pihole -q domain shows entry |
| Wrong IP returned | Flush client DNS cache, restart pihole-FTL |
| Entry not persisting | Verify /etc/pihole/custom.list is writable |
| Domain unreachable | Check service is running, firewall open |
Access Required
Current: Manual SSH or web admin Future: Consider API access or automation via SSH key
To add Ani SSH access:
# On Pi-hole
cat >> /root/.ssh/authorized_keys << 'EOF'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINDOGx8/YnA/5ApTW7QSnjIBUoHUuVMeUrtoUaZWyPFt ani@consciousness
EOF
Pattern: Every internal service gets a .wiuf.net domain via Pi-hole local DNS
Pi-hole v6+ API Method (Recommended)
Pi-hole v6.0+ includes a comprehensive REST API at /api/. Self-documented at http://pi.hole/api/docs.
Authentication
Session-based (not static tokens):
# Step 1: Get session ID (SID)
curl -k -X POST "https://pi.hole/api/auth" \
--data '{"password":"your-password"}'
# Response contains: session.sid and session.csrf
Use SID in requests (4 methods):
- Query param:
?sid=vFA+EP4MQ5JJvJg+3Q2Jnw= - Header:
X-FTL-SID: vFA+EP4MQ5JJvJg+3Q2Jnw= - Cookie:
Cookie: sid=vFA+EP4MQ5JJvJg+3Q2Jnw= - Body:
{"sid":"vFA+EP4MQ5JJvJg+3Q2Jnw="}
API: Add Local DNS Record
A/AAAA Records:
# Add host record via API
SID="your-session-id"
curl -k -X PATCH "https://pi.hole/api/config?sid=$SID" \
-H "Content-Type: application/json" \
--data '{
"config": {
"dns": {
"hosts": ["10.10.20.120 qgis.wiuf.net", "10.10.20.120 deluge.wiuf.net"]
}
}
}'
CNAME Records:
curl -k -X PATCH "https://pi.hole/api/config?sid=$SID" \
-H "Content-Type: application/json" \
--data '{
"config": {
"dns": {
"cnameRecords": ["*.qgis.wiuf.net,qgis.wiuf.net"]
}
}
}'
Python Example (requests):
import requests
PIHOLE = "https://pi.hole"
PASS = "your_password"
# Authenticate
auth = requests.post(f"{PIHOLE}/api/auth", json={"password": PASS}, verify=False)
sid = auth.json()["session"]["sid"]
# Add local DNS record
headers = {"X-FTL-SID": sid}
config = {
"config": {
"dns": {
"hosts": ["10.10.20.XXX domain.wiuf.net"]
}
}
}
response = requests.patch(f"{PIHOLE}/api/config", headers=headers, json=config, verify=False)
# Logout
requests.delete(f"{PIHOLE}/api/auth", headers=headers, verify=False)
API: Management Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth |
Get session (authenticate) |
| DELETE | /api/auth |
End session (logout) |
| GET | /api/config/dns |
Get DNS configuration |
| PATCH | /api/config |
Update configuration |
| POST | /api/dns/blocking |
Enable/disable blocking |
| POST | /api/dns/cache |
Clear DNS cache |
SDK/Client Libraries
| Library | Install | Notes |
|---|---|---|
| pyhole6 | pip install pyhole6 |
Recommended, async, auto session |
| python-hole | pip install hole |
v5 & v6 unified |
| pi_hole_api | GitHub | Simple wrapper |
pyhole6 Example:
import asyncio
from pyhole6 import Pyhole6
async def add_dns():
async with Pyhole6("https://pi.hole", "password") as client:
# Access config.dns.hosts
config = await client.config.get()
hosts = config["dns"]["hosts"]
hosts.append("10.10.20.XXX domain.wiuf.net")
await client.config.patch({"dns": {"hosts": hosts}})
asyncio.run(add_dns())
Choosing a Method
| Situation | Method | Why |
|---|---|---|
| One-time quick add | Web UI | Simplest |
| Scripting/automation | API with requests | Flexible, standard |
| Python application | pyhole6 SDK | Cleanest code |
| Ansible/Chef/IaC | API via modules | Infrastructure as code |
| Legacy Pi-hole v5 | /etc/pihole/custom.list |
Only option |
API documentation auto-generated at: http://pi.hole/api/docs