All Things Backup: Pi-hole (Teleporter)

As the first in a series I’m calling “All Things Backup” I’m talking about the way I make sure I’ve got good backups of the critical information I need for my home lab. Is it overkill? Perhaps, perhaps not. If you’ve ever lost something feeding your personal happiness, then you know the value of having a backup.

In today’s topic I’m taking about my Pi-holes. If you aren’t familiar with Pi-hole, here’s the simple explanation: network-wide DNS-based advertisement and online tracking blocking? Still too technical? It basically makes most websites load faster with less interference. It makes other websites load slower , but they still load without interference.

Pi-hole is an open source project that’s maintained by a lovely community. I’ve been using their software for more than five years.

My history as a network engineer taught me two things: 1) How DNS works and 2) that you definitely should be running two DNS servers. Actually, the second came from working with various operating systems. Some will occasionally round-robin connections to DNS servers, but that’s beside the point. What’s important for me is that I can’t run with one Pi-hole, I need to run with two.

My Setup

This is why my home network has two Raspberry Pi 5 microcomputers with PoE HAT in a rackmount in my utility room.

The wonderful thing about the people at Pi-hole is that they know you probably want to back up your settings at some point – or possibly restore them if you’ve got to start over or move to new hardware. They’ve implemented a feature called “Teleporter” right there in the web interface.

This is an amazing little feature that creates a backup of what Pi-hole thinks is important on this device. There’ll be another post about things that are missing from the Teleporter backup.

Scripting Needs

I wanted to take advantage of some of the API options with the v6 of the Pi-hole solution to automate backups. There are probably a dozen different ways to do this, but I stuck with (what else?) PowerShell.

What I wrote was a function that exports “right now” backup when it’s executed. To access the API, I needed:

  1. the address/FQDN of the PI-hole
  2. the password used to access the admin feature
  3. a place to put the backup file

It’s not a very big lift. I started by making sure I could do this in a script in a pseudo-automated way and got that. Then I decided to wrap it into a function because that’ll be easier to put into a larger backup methodology.

I can go into the details, but I’ve commented my code thoroughly, so I think you can follow along.

The Function and How to Use It

<#
.Synopsis
    Backup Pi-hole using the Teleporter API
.DESCRIPTION
    Connect to the Pi-hole(s) using the Teleporter API and back them up to a specified location
.EXAMPLE
    Backup-PiHole -ComputerName 192.168.4.7 -PiPassword ( 'MyPiPassword' | ConvertTo-SecureString -AsPlainText ) -Path "\\NAS\Backups\pihole" -SkipCertificateCheck

    Directory: \\NAS\Backups\pihole

    Mode                 LastWriteTime         Length Name
    ----                 -------------         ------ ----
    -a---           9/10/2025  1:34 PM         115004 Teleporter_192.168.4.7.zip


    This example connects to the pihole at 192.168.4.7 and backs it up to the specified network share using the provided password. The certificate check
    is skipped because we are talking via IP address.
.EXAMPLE
    $Creds = ( Get-Content -Path .\auth.json | ConvertFrom-Json ); Backup-PiHole -PiHole "pihole01.home.kmsigma.com", "pihole02.home.kmsigma.com" -Credentials $Creds -Path "$env:Userprofile\Documents\pihole" -IncludeDate

    Directory: C:\Users\MyUser\Documents\pihole

    Mode                 LastWriteTime         Length Name
    ----                 -------------         ------ ----
    -a---           9/10/2025  1:39 PM         115002 Teleporter_pihole01.home.kmsigma.com_20250910.zip
    -a---           9/10/2025  1:39 PM          88406 Teleporter_pihole02.home.kmsigma.com_20250910.zip

    This example, reads the credentials from a JSON file and uses them to back up two Pi-hole servers to the specified path. The current date is
    included in the filename in "YYYYmmDD" format, so it can be easily sorted.
.OUTPUTS
   [System.IO.FileInfo]$PathToEachBackup
.NOTES
   General notes
.ROLE
   Utility
#>
function Backup-PiHole {
    [CmdletBinding(
        DefaultParameterSetName = 'PiPassword', 
        SupportsShouldProcess = $true, 
        HelpUri = 'https://docs.pi-hole.net/api/',
        ConfirmImpact = 'Medium'
    )]
    Param
    (
        # PiHole(s) to backup
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true, 
            Position = 0
        )]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        [Alias("ComputerName")] 
        [string[]]$PiHole,

        # Credentials in a [PSCustomObject] with "password" property
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'CredObject'
        )]
        [PSCustomObject]$Credentials,

        # Password in SecureString format
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'PiPassword'
        )]
        [SecureString]$PiPassword,

        # Backup Location (defaults to current folder)
        [Parameter(
            Position = 1
        )]
        [AllowNull()]
        [System.IO.DirectoryInfo]$Path = ( Get-Location | Get-Item ),

        # Should we skip the certificate check (default: $false)
        [switch]$SkipCertificateCheck,

        # Should we include the date in the filename (default: $false)
        [switch]$IncludeDate
    )

    Begin {
        
        if ( -not ( Test-Path -Path $Path ) ) {
            if ( $PSCmdlet.ShouldProcess("Path", "Create") ) {
                Write-Verbose "Creating path $Path"
            }
            New-Item -Path $Path -ItemType Directory | Out-Null
        }
    }
    Process {
        ForEach ( $p in $PiHole ) {
            if ( $PSCmdlet.ShouldProcess("$p", "Run Teleporter") ) {
                Write-Verbose "Backing up PiHole: $p"
                if ( $PSCmdlet.ParameterSetName -eq 'PiPassword' ) {
                    Write-Verbose -Message "    Using PiPassword for authentication"
                    $Creds = [PSCustomObject]@{
                        password = ( [Runtime.InteropServices.Marshal]::PtrToStringAuto( [Runtime.InteropServices.Marshal]::SecureStringToBSTR( $PiPassword ) ) )
                    }
                }
                else {
                    Write-Verbose -Message "    Using CredObject for authentication"
                    $Creds = $Credentials
                }

                Write-Verbose -Message "    Authenticating to $p"
                $AuthResp = Invoke-RestMethod -Uri "https://$( $p )/api/auth" -Body ( $Creds | ConvertTo-Json ) -Method Post -SkipCertificateCheck:$SkipCertificateCheck

                Write-Verbose -Message "    Received Session ID"
                $SessionID = $AuthResp.session.sid

                Write-Verbose -Message "    Storing Session ID in Headers"
                $Headers = @{
                    "X-FTL-SID" = $SessionID
                }

                $OutFile = Join-Path -Path $Path.FullName -ChildPath "Teleporter_$( $p )$( if ( $IncludeDate ) { "_$( Get-Date -Format "yyyyMMdd" )" } ).zip"

                Write-Verbose "Saving Teleporter for $( $p ) to $OutFile"
                if ( $PSCmdlet.ShouldProcess("$p", "Backup PiHole to $OutFile") ) {
                    Write-Verbose -Message "    Saving Teleporter file for $( $p ) as '$OutFile'"
                    Invoke-RestMethod -Uri "https://$( $p )/api/teleporter" -Headers $Headers -SkipCertificateCheck:$SkipCertificateCheck -OutFile $OutFile
                }
                Get-Item -Path $OutFile
            }
        
        }
    }
    End {
        # Nothing to see here
    }
}

That’s the function – only about 100 lines (with only about 35 of them being ‘functionally’ required).

To execute the function, I first dot-source the function file.

. .\Path\To\func_Backup-PiHole.ps1

Now that the function is loaded into memory, I can call it directly.

Backup-PiHole -ComputerName "192.168.4.7" -PiPassword ( 'MyPiPassword' | ConvertTo-SecureString -AsPlainText ) -SkipCertificateCheck

The above will connect to my Pi-hole instance at 192.168.4.7 using ‘MyPiPassword’ and export the teleporter file to my current directory.

It’s not my most elegant work. Looking at it, I didn’t take into account multiple Pi-hole deployments with distinct passwords. Regardless, this works for me. If you have other suggestions to improve this, I always welcome feedback.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.