Pi-hole Maintenance and Advanced Configuration
After the Pi is set up and running, use this page as a reference for confirming sudo requires a password, running manual updates, configuring backups of your Pi configuration, using a USB drive to store logs, or setting how long query history is kept.
All of these sections are optional.
Want Claude Code to help diagnose issues using these logs? See Give Claude Code Access to Pi-hole Logs and Diagnostics.
Optional: Require a Password for sudo Commands
Section titled “Optional: Require a Password for sudo Commands”The non-root user that Raspberry Pi Imager created might be able to run sudo commands without a password.
Raspberry Pi OS grants that through a file in /etc/sudoers.d/ named after the account, such as 010_pi-admin-nopasswd.
Remove the file so that sudo commands require authentication.
Raspberry Pi OS 6.2 (April 2026) stopped creating that file on new installs, so if you flashed your SD card with a current copy of Raspberry Pi Imager, you almost certainly don’t have it. This matters more if you’re rebuilding on an older image or setting up an existing device, but the command below handles both cases either way.
-
Remove the configuration file if it exists:
From the Pi sudo sh -c 'rm -fv /etc/sudoers.d/*-nopasswd'The glob runs inside
sh -cso that root expands it./etc/sudoers.d/is readable only by root, so your own shell can’t match the filename beforesudostarts.A removed file is named in the output. No output means there was nothing to remove.
-
Confirm no passwordless rule survives anywhere else in the directory:
From the Pi sudo grep -rl NOPASSWD /etc/sudoers.d/This prints nothing when passwordless
sudois off. Any file it names still grants it, and you should read that file before removing it. -
Log out of the SSH session:
Terminal window exit -
Log back in:
Terminal window
Update Pi-hole
Section titled “Update Pi-hole”Update the Pi-hole software and refresh the Gravity database with updated lists.
Run these whenever Pi-hole releases a new version, or after adding or changing blocklists.
sudo pihole -up && \sudo pihole -gBack Up Your Configuration
Section titled “Back Up Your Configuration”Pi-hole’s Teleporter exports your full configuration: blocklists, allowlists, custom domains, regex rules, DNS settings, and DHCP config.
- In the Pi-hole web interface, go to Settings > Teleporter.
- Select Export to download a backup file.
To restore after a rebuild or SD card failure, use the Import option on the same page and select the backup file.
Re-export after making significant changes to lists or settings. Store the backup file somewhere other than the Pi, such as on your local computer, a NAS, or cloud storage.
Treat the file as sensitive. It includes your full configuration, so store it like any config backup that might hold credentials: never in a shared or public folder.
Advanced Pi-hole Configuration with pihole.toml
Section titled “Advanced Pi-hole Configuration with pihole.toml”If you need to configure Pi-hole’s DNS behavior outside the web interface, edit /etc/pihole/pihole.toml directly.
After editing, restart the FTL service to apply changes:
sudo systemctl restart pihole-FTLpihole-FTL is the core Pi-hole system service.
It needs to be restarted when you make core configuration changes.
Example: Change Upstream DNS
Section titled “Example: Change Upstream DNS”Change the addresses in the [dns] section of pihole.toml:
...[dns] upstreams = ["127.0.0.1#5335", "1.1.1.1"] # unbound on localhost, Cloudflare backup...Or for Cloudflare directly:
...[dns] upstreams = ["1.1.1.1", "1.0.0.1"]...Reference: Full pihole.toml
Section titled “Reference: Full pihole.toml”This is the full pihole.toml from my setup, with comments stripped and the password hash redacted.
Values marked ### CHANGED were modified from the Pi-hole default.
pihole.toml (v6.7, comments stripped)
# Pi-hole configuration file (v6.7)# Reference config - comments stripped, default values preserved for reference.# Values marked ### CHANGED were modified from the Pi-hole default.# To apply: copy to /etc/pihole/pihole.toml, then sudo systemctl restart pihole-FTL
[dns] upstreams = [ "127.0.0.1#5335", "1.1.1.1" ] ### CHANGED, default = [] CNAMEdeepInspect = true blockESNI = true EDNS0ECS = false ### CHANGED, default = true ignoreLocalhost = false showDNSSEC = true analyzeOnlyAandAAAA = false piholePTR = "PI.HOLE" replyWhenBusy = "ALLOW" blockTTL = 2 hosts = [] domainNeeded = false expandHosts = false bogusPriv = true dnssec = false interface = "eth0" ### CHANGED, default = "" hostRecord = "" listeningMode = "ALL" ### CHANGED, default = "LOCAL" queryLogging = true cnameRecords = [] port = 53 localise = true revServers = []
[dns.domain] name = "lan" local = true
[dns.cache] size = 10000 optimizer = 3600 upstreamBlockedTTL = 86400 rrtype = "ANY"
[dns.blocking] active = true mode = "NULL" edns = "TEXT"
[dns.specialDomains] mozillaCanary = true iCloudPrivateRelay = true designatedResolver = true
[dns.reply.host] force4 = false IPv4 = "" force6 = false IPv6 = ""
[dns.reply.blocking] force4 = false IPv4 = "" force6 = false IPv6 = ""
[dns.rateLimit] count = 1000 interval = 60
[dhcp] active = false start = "" end = "" router = "" netmask = "" leaseTime = "" ipv6 = false rapidCommit = false multiDNS = false logging = false ignoreUnknownClients = false hosts = []
[ntp] [ntp.ipv4] active = true address = ""
[ntp.ipv6] active = true address = ""
[ntp.sync] active = false ### CHANGED, default = true server = "pool.ntp.org" interval = 3600 count = 8
[ntp.sync.rtc] set = false device = "" utc = true
[resolver] resolveIPv4 = true resolveIPv6 = true macNames = true networkNames = true refreshNames = "IPV4_ONLY"
[database] DBimport = true maxDBdays = 91 DBinterval = 60 useWAL = true forceDisk = false
[database.network] parseARPcache = true expire = 91
[webserver] domain = "pi.hole" acl = "" port = "80o,443os,[::]:80o,[::]:443os" threads = 50 headers = [ "X-DNS-Prefetch-Control: off", "Content-Security-Policy: default-src 'none'; connect-src 'self'; font-src 'self'; frame-ancestors 'none'; img-src 'self'; manifest-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; form-action 'self'", "X-Frame-Options: DENY", "X-XSS-Protection: 0", "X-Content-Type-Options: nosniff", "Referrer-Policy: strict-origin-when-cross-origin" ] serve_all = false advancedOpts = []
[webserver.session] timeout = 1800 restore = true
[webserver.tls] cert = "/etc/pihole/tls.pem" validity = 47
[webserver.paths] webroot = "/var/www/html" webhome = "/admin/" prefix = ""
[webserver.interface] boxed = true theme = "default-auto"
[webserver.api] max_sessions = 16 prettyJSON = false pwhash = "<redacted>" ### CHANGED from default (use `sudo pihole setpassword` to set) totp_secret = "" app_pwhash = "" app_sudo = false cli_pw = true excludeClients = [] excludeDomains = [] maxHistory = 86400 maxClients = 10 client_history_global_max = true allow_destructive = true
[webserver.api.temp] limit = 60.000000 unit = "C"
[files] database = "/etc/pihole/pihole-FTL.db" tmp_db = "/etc/pihole/pihole-tmp.db" gravity = "/etc/pihole/gravity.db" gravity_tmp = "/tmp" macvendor = "/etc/pihole/macvendor.db" pcap = ""
[files.log] ftl = "/var/log/pihole/FTL.log" dnsmasq = "/var/log/pihole/pihole.log" webserver = "/var/log/pihole/webserver.log"
[misc] privacylevel = 0 delay_startup = 0 nice = -10 addr2line = true etc_dnsmasq_d = false dnsmasq_lines = [] extraLogging = false readOnly = false normalizeCPU = true hide_dnsmasq_warn = false hide_connection_error = false
[misc.check] load = true shmem = 90 disk = 90
[debug] database = false networking = false locks = false queries = false flags = false shmem = false gc = false arp = false regex = false api = false tls = false overtime = false status = false caps = false dnssec = false vectors = false resolver = false edns0 = false clients = false aliasclients = false events = false helper = false config = false inotify = false webserver = false extra = false reserved = false ntp = false netlink = false timing = false performance = false all = falseOffload Logs to a USB Drive
Section titled “Offload Logs to a USB Drive”The Pi-hole FTL database (pihole-FTL.db) grows continuously and can wear out an SD card over months.
Move the database and log file to a USB drive to protect the SD card and extend its life.
What You Need
Section titled “What You Need”- A USB flash drive
- The Pi running with Pi-hole installed
Mount the USB Drive
Section titled “Mount the USB Drive”-
Plug in the USB drive and find its device name:
From the Pi lsblkLook for a
/dev/that wasn’t there before, such as/dev/sdawith a partition at/dev/sda1. -
Get the partition’s UUID.
This example is intentionally set to fail if you don’t adjust
YOUR-USB-PARTITIONto the device you found in the previous step:From the Pi sudo blkid /dev/YOUR-USB-PARTITIONCopy the UUID value from the output.
-
Create a mount point:
From the Pi sudo mkdir -p /mnt/pihole-logs -
Add an entry to
/etc/fstabto auto-mount on boot. Open the file and add this line at the bottom, replacingYOUR-UUIDwith the UUID you copied:/etc/fstab UUID=YOUR-UUID /mnt/pihole-logs ext4 defaults,noatime 0 2Note that this example uses
ext4. If your USB drive is formatted as exFAT or NTFS instead of ext4, change the filesystem type accordingly.If you need to format the drive first
This deletes everything on the drive.
-
Confirm which device is the USB drive, not an internal disk:
From the Pi lsblk -o NAME,SIZE,LABEL,MOUNTPOINTMatch the size and label to your USB drive. Don’t guess: an internal disk can share the same
/dev/sdXnaming scheme, andmkfswon’t ask for confirmation before it formats.It deletes everything on the drive.
-
Format the partition.
This example is intentionally set to fail if you don’t adjust
YOUR-USB-PARTITION:From the Pi sudo mkfs.ext4 /dev/YOUR-USB-PARTITION
-
-
Mount everything in fstab:
From the Pi sudo mount -aIf this returns an error, double-check the UUID and filesystem type in
/etc/fstab. -
Set ownership so Pi-hole’s FTL process can write to the drive:
From the Pi sudo chown pihole:pihole /mnt/pihole-logs
Point Pi-hole to the USB Drive
Section titled “Point Pi-hole to the USB Drive”-
Back up the current
pihole.toml:From the Pi sudo cp /etc/pihole/pihole.toml /etc/pihole/pihole.toml.bak -
Move the existing database to the USB drive (so you don’t lose query history):
From the Pi sudo systemctl stop pihole-FTL && \sudo mv /etc/pihole/pihole-FTL.db /mnt/pihole-logs/ && \sudo mv /var/log/pihole/pihole.log /mnt/pihole-logs/ 2>/dev/null || true -
Edit
/etc/pihole/pihole.tomlto update the file paths. Find the[files]section and set:pihole.toml ...[files]database = "/mnt/pihole-logs/pihole-FTL.db"[files.log]dnsmasq = "/mnt/pihole-logs/pihole.log"...logis a subsection ([files.log]), not a single value. Onlydnsmasqneeds to change here;ftlandwebservercan stay on the SD card. If there is no[files]section, add it at the end of the file. -
Start FTL and verify it comes up cleanly:
From the Pi sudo systemctl start pihole-FTL && \sudo systemctl status pihole-FTL -
Confirm the database is on the USB drive and growing:
From the Pi ls -lh /mnt/pihole-logs/ -
Reboot the Pi:
From the Pi sudo reboot -
Verify that the USB drive mounts and Pi-hole starts correctly:
From the Pi lsblk && \systemctl status pihole-FTLThe FTL service won’t start if it can’t find the database file. DNS queries from all network devices will fail until the mount is fixed. See FTL database grows too large and FTL fails to start if FTL won’t come back up.
Optional: Set Pi-hole Database Retention
Section titled “Optional: Set Pi-hole Database Retention”By default, Pi-hole keeps 91 days of query history.
If you want to change how many days of history Pi-hole keeps:
-
Set the number of days to keep the database in
pihole.toml:pihole.toml ...[database]maxDBdays = 180... -
Restart FTL after changing this:
From the Pi sudo systemctl restart pihole-FTL