Skip to content

Extend, Revoke, and Alternatives for Pi-hole Access

Apart from Revoke Access, everything on this page is optional.

Start at Give Claude Code Access to Pi-hole if you haven’t set up access yet.

Per vendor docs

Follow this section if you want Claude Code to apply fixes itself rather than suggest them to you.

Don’t follow this section if you want an agent that only reports.

Granting read access to one file, /etc/pihole/cli_pw, lets Claude Code run pihole allow, pihole deny, pihole enable, and pihole disable with no sudo involved at all. /etc/pihole/cli_pw holds the password Pi-hole generates for its own pihole commands to authenticate against the API.

It also gives it more than those four commands.

Those four commands are the intended use, but the grant hands over a credential, not a command list. Anything that credential can do, Claude Code can do by calling the API directly.

In practice that means it can also rebuild gravity, restart DNS, delete DHCP leases and network device records, and flush the query log, which destroys the history you granted read access to in the first place. It can remove lists as well as add them, and pihole disable turns off blocking for your whole network.

  1. Select where you run the commands from:

    And who you run the commands as:

    From the Pi
    sudo setfacl -m "u:${USER}:r" /etc/pihole/cli_pw
  2. Confirm it works by allowing a domain as that account, with no sudo:

    From the Pi
    pihole allow ads.example.com
  3. Uncomment the matching block in the Privileges section of your CLAUDE.md, so the agent knows it can run these commands directly.

The credential cannot change configuration or import a Teleporter backup. FTL enforces both, unconditionally, with no setting to turn them off:

The current CLI session is not allowed to modify Pi-hole config settings

That is the entire restriction. Everything else the API offers stays available, which is why the caution above lists more than the four commands. It’s still a stronger guarantee than an application password, where that same config restriction disappears the moment webserver.api.app_sudo is set to true.

Two practical limitations of FTL:

  • It assumes the default DNS port.

    • pihole allow locates the API by asking FTL which port DNS runs on, and that lookup reads pihole.toml, which the agent account deliberately cannot read. It falls back to the default of 53, so a stock install works. If you changed dns.port, these commands fail for the agent and warn that pihole.toml could not be read. Don’t solve that by granting access to pihole.toml, which holds your password hashes. Run those commands yourself with ! instead.
  • The grant survives restarts.

    • FTL rewrites cli_pw every time it starts, but truncates the existing file rather than replacing it, so the ACL rides through exactly as the query database grant does. If you delete the file outright, re-run the grant above.

To take this back, remove that one entry. Everything else on this page keeps working:

From the Pi
sudo setfacl -x "u:$USER" /etc/pihole/cli_pw
Tested

A few things have no read-only path: pihole debug, systemctl status pihole-FTL, journalctl.

If you want the agent to run those unattended, grant a password-free rule for a wrapper script that takes no arguments.

Skip this section unless you need it. Nothing depends on it in later sections.

Grant a password-free rule for a wrapper script that takes no arguments.
  1. Write a script that takes no input at all.

    From the Pi
    sudo tee /usr/local/sbin/pihole-agent-diag >/dev/null <<"EOF"
    #!/bin/bash
    set -euo pipefail
    echo "=== FTL service ==="
    systemctl status pihole-FTL --no-pager
    echo "=== last 100 FTL log lines ==="
    tail -n 100 /var/log/pihole/FTL.log
    EOF
  2. Make it root-owned and unwritable by anyone else.

    If the agent can edit this script, a password-free rule on it is a root shell:

    From the Pi
    sudo chown root:root /usr/local/sbin/pihole-agent-diag && \
    sudo chmod 755 /usr/local/sbin/pihole-agent-diag
  3. Allow exactly that script, with no arguments. The "" pins it to zero arguments:

    From the Pi
    echo "$USER ALL=(root) NOPASSWD: /usr/local/sbin/pihole-agent-diag \"\"" \
    | sudo tee /etc/sudoers.d/pihole-agent && \
    sudo chmod 440 /etc/sudoers.d/pihole-agent && \
    sudo visudo -c -f /etc/sudoers.d/pihole-agent
  4. Confirm the pin holds. Run these separately, since the second is meant to be refused.

    This should print the diagnostics:

    From the Pi
    sudo -n /usr/local/sbin/pihole-agent-diag

    This should be rejected, because the rule allows no arguments:

    From the Pi
    sudo -n /usr/local/sbin/pihole-agent-diag /etc/shadow

If you add this, uncomment the matching block in the Privileges section of your CLAUDE.md so the agent knows the wrapper exists. Otherwise it will keep asking you to run diagnostics by hand.

Remove the ACL entries. The -R matters, because rotated archives inherited the grant from the directory’s default ACL:

From the Pi
sudo setfacl -R -x "u:$USER" /var/log/pihole && \
sudo setfacl -x "d:u:$USER" /var/log/pihole && \
sudo setfacl -x "u:$USER" /etc/pihole/pihole-FTL.db && \
sudo setfacl -x "u:$USER" /etc/pihole/cli_pw 2>/dev/null || true

The cli_pw entry only exists if you granted it. The || true keeps the chain going when you didn’t.

Remove the sudoers rule if you added one:

From the Pi
sudo rm -f /etc/sudoers.d/pihole-agent

Confirm nothing is left. No user: line should name the account you revoked:

From the Pi
getfacl -pR /var/log/pihole /etc/pihole/pihole-FTL.db /etc/pihole/cli_pw | grep '^user:'
Tested

Pi-hole v6 exposes query data over an HTTP API authenticated with an application password. It needs no filesystem changes at all, which makes it the right choice if:

  • You administer Pi-hole but don’t have shell root on the Pi device itself, so you can’t set ACLs.
  • Pi-hole runs in Docker (such as on TrueNAS), where the logs and database live inside the container and the ACL recipe above doesn’t apply as written.
  1. Confirm which port the API is on.

    Port 80 is the default, but it’s marked optional, so FTL falls back silently, often to 8080, when something else holds it:

    From the Pi
    pihole-FTL --config webserver.port

    Use that port in the commands below. Every one of them targets 127.0.0.1, meaning Pi-hole’s own webserver, which only makes sense to run on the Pi itself - if you’re on a workstation, that’s exactly why each command below runs over ssh rather than directly against the Pi’s address.

  2. In the web interface, go to Settings > Web interface / API, switch to Expert mode, and generate an application password.

  3. Store it in a file only your user can read:

    From the Pi
    mkdir -p ~/.config/pihole && \
    install -m 600 /dev/null ~/.config/pihole/agent-app-password

    Paste the password in as a single line. Never put it in CLAUDE.md, a dotfiles repo, or anything else you commit.

  4. Install jq, which isn’t present by default:

    From the Pi
    sudo apt install jq
  5. Authenticate and query. Piping the password through stdin keeps it out of the process list.

    Replace YOUR-DEVICE-IP with the IP of a device on your network you want to check:

    From the Pi
    SID=$(jq -n --arg pw "$(cat ~/.config/pihole/agent-app-password)" '{password: $pw}' \
    | curl -s -X POST http://127.0.0.1/api/auth --data @- \
    | jq -r .session.sid) && \
    curl -s -H "X-FTL-SID: $SID" \
    'http://127.0.0.1/api/queries?client_ip=YOUR-DEVICE-IP&status=GRAVITY&length=50' | jq

/api/queries filters server-side on client_ip, client_name, domain, status, upstream, type, reply, dnssec, from, and until.

Unlike the database, the API takes status names such as GRAVITY rather than the numeric codes. Add disk=true to read the long-term database rather than the in-memory buffer.

On FTL 6.7 and later, sessions last 30 minutes. Each authenticated request extends the window.

Before the release of FTL 6.7, a logic bug (GHSA-w8cr-2cwg-92cg) kept the expiry check from running, so a session would periodically never expire. Update to 6.7 or later if you haven’t already.

Generate a new application password to revoke active sessions, including your web interface login.

Per vendor docs

Read-only is not the same as private.

Any of the methods on this page expose every DNS query from every device on your network, including which sites each device reached and when. Least privilege limits what an agent can change, not what it can see.

If some of that shouldn’t leave the Pi, Pi-hole can filter it out of API results before anything reads them. Both settings take arrays of regular expressions, not plain strings, so anchor them and escape the dots:

From the Pi
sudo pihole-FTL --config webserver.api.excludeClients '["^192\\.168\\.0\\.50$"]' && \
sudo pihole-FTL --config webserver.api.excludeDomains '["^example\\.com$"]'

Those filters reach only two API responses, /api/queries and /api/stats/top_clients. They do nothing for an agent reading pihole-FTL.db through the ACL grant, which sees the unfiltered database. If a device’s traffic genuinely shouldn’t be visible, the query log is the wrong place to fix that.