--- 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)** ```bash # 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** 1. Navigate to: http://10.10.20.5/admin (or pi.hole/admin) 2. Login with admin password 3. **Local DNS** **DNS Records** 4. **Domain:** `domain.wiuf.net` 5. **IP Address:** `10.10.20.XXX` 6. Click **Add** 7. 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:** ```bash # 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 ```bash # 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: 1. **Deploy service** with compose on target server 2. **Determine IP** (usually 10.10.20.XXX where service runs) 3. **Choose domain** (format: `servicename.wiuf.net`) 4. **Add to Pi-hole** local DNS 5. **Verify resolution** from client 6. **Document** in this file 7. **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:** ```bash # 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):** ```bash # 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:** ```bash # 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:** ```bash 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):** ```python 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:** ```python 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*