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 --version
For 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 letsencrypt
This 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.pem
with 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 pihole
Fixes:
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