See my other guides for SSL certificates on Pi-hole v6:
Pi-hole v6 introduces changes to its web server:
- Embedded Web Server – Pi-hole no longer relies on
lighttpd. - TLS Configuration – Certificates must be in PEM format containing both the private key and certificate.
- Docker - The latest version for v6 contains breaking changes. This guide references the latest docker-compose-yml.
By default, Pi-hole v6 provides a self-signed SSL certificate, but you can automate certificate renewal with acme.sh, Cloudflare and Let's Encrypt.
This guide uses:
- acme.sh: An ACME shell script.
- Cloudflare DNS.
- Let’s Encrypt.
The default docker-compose.yml, with no changes, is referenced in this guide. The certificate files are issued and stored outside the container, in child directory etc-pihole where your docker-compose.yml is stored. Change your bind mounts as needed:
...
volumes:
# For persisting Pi-hole's databases and common configuration file
- './etc-pihole:/etc/pihole'
...
- Pi-hole v6 installed and running in Docker using Docker Compose on your system.
- A Cloudflare account that manages your domain’s DNS records.
- Control of a registered domain (e.g.,
mydomain.com).
These prerequisites ensure that you can successfully request and install an SSL certificate using Cloudflare DNS validation with acme.sh.
This guide uses Cloudflare DNS and Let’s Encrypt. These instructions can be adapted for any DNS provider and Certificate Authority (CA) that acme.sh supports, including ZeroSSL. Simply update the --dns and --server flags accordingly when issuing your certificate.
Install it:
curl https://get.acme.sh | sh -s [email protected]Reload .bashrc to register the acme.sh alias:
source .bashrc
Verify installation:
acme.sh --versionFor DNS-based domain verification, export your Cloudflare API token:
export CF_Token="ofz...xxC"
export CF_Email="[email protected]"This allows acme.sh to create the required DNS records automatically.
Run:
acme.sh --issue --dns dns_cf -d ns1.mydomain.com --server letsencryptThis generates:
- Private key:
ns1.mydomain.com.key - Full-chain certificate:
fullchain.cer(includesns1.mydomain.com.cer+ca.cer, in that order)
You do not need these other certificate files:
- Server certificate:
ns1.mydomain.com.cer(included infullchain.cer) - Intermediate CA cert:
ca.cer(included infullchain.cer)
Pi-hole requires a PEM file containing both the private key and server certificate.
Install the certificate:
The below command assumes the following with regards to file paths:
- Your pihole docker-compose file is located relative to your home directory in
~/docker/pihole - As specified in
docker-compose.yml, the pi-hole configuration directory is stored relative to your home directory in~/docker/pihole/etc-pihole. If you change your bind mounts indocker-compose.yml, be sure to change the paths here.
acme.sh --install-cert -d ns1.mydomain.com \
--reloadcmd "sudo rm -f ~/docker/pihole/etc-pihole/tls* && \
sudo cat fullchain.cer ns1.mydomain.com.key | sudo tee ~/docker/pihole/etc-pihole/tls.pem && \
docker restart pihole"This:
- Deletes old certificates on the host machine (
~/docker/pihole/etc-pihole/tls*). - Creates
tls.pemwith both full-chain certificate file and private key, in that order, on the host machine. - Restarts the pihole Docker container to apply the new certificate. The certificate file is mounted from the host into the container.
To avoid domain mismatch warnings (CERTIFICATE_DOMAIN_MISMATCH), set the correct hostname:
docker exec -it pihole pihole-FTL --config webserver.domain 'ns1.mydomain.com' && \
docker restart piholeFixes:
CERTIFICATE_DOMAIN_MISMATCH SSL/TLS certificate /etc/pihole/tls.pem does not match domain pi.hole!
- Your certificate renews automatically via
acme.sh's cron job. - You can manually renew with:
acme.sh --renew -d ns1.mydomain.com --force
- To check your certificate (on the host machine):
sudo openssl x509 -in ~/docker/pihole/etc-pihole/tls.pem -text -noout