Files
Redflag/reference/pihole_dns_management.md
Ani (Daemon) bb380849c9 refactor: Add Pi-hole DNS management reference
- 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
2026-03-28 12:35:22 -04:00

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

  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:

# 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:

  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:

# 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.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