Setting up internal domains with Pi-hole
This is a continuation of Self-hosting at home with Ubuntu Server + Easypanel on Mac Mini (late 2014).
- I don't like IP addresses; I want pretty domains for my internal apps
- Installing Pi-hole with Easypanel on Ubuntu, when there is no template for it
- Setting up internal domain names with Pi-hole
- I want to use both Pi-hole and Mullvad VPN (all my Pi-hole settings stopped applying as soon as I turned on my VPN)
- I want https for my internal stuff too, but how
The plan
.internal
top-level domain name (Down the rabbit hole, I went)- Records approach, because I like order
- A record: Subdomains → host IPs
- CNAME record: Service subdomains → host subdomains
- If the host IP changes, update A record, which will apply to all CNAME records
- Naming, because conventions are important
- A records for hosts
- macmini.host.internal
- raspberrypi.host.internal (not actually a thing, just an example, but it feels real now so maybe it will be a thing)
- CNAME records for sites and services (they match my Easypanel project names)
- easypanel.home.internal
- pihole.home.internal
- jellyfin.home.internal
- miniflux.web.internal — for testing
- bearblog.web.internal (for that static website backup) — for testing
- A records for hosts
What I did
Prep for installing Pi-hole on Ubuntu
In PowerShell, on my Ubuntu server, address the port 53 conflict with systemd-resolved
:
$user@192.168.x.x
sudo sed -r -i.orig 's/#?DNSStubListener=yes/DNSStubListener=no/g' /etc/systemd/resolved.conf
sudo sh -c 'rm /etc/resolv.conf && ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf'
systemctl restart systemd-resolved
- Confirm that port 53 is free
sudo lsof -i :53
- Nothing should return
Install, configure, and run Pi-hole with Easypanel
In Firefox, on my computer:
- Go to http://192.168.x.x:3000/
- Go to Dashboard >
home
project- Select the ➕ to add an app
- Pick a service: App
- Service Name: pihole
- Select Docker Image
- Image: pihole/pihole:latest
- Select Save
- Expose some ports (for local network access)
- Go to
pihole
> Advanced - Select Add Port
- Protocol: TCP
- Published: 8081
- Target: 80
- Select Create
- Repeat two more times for port 53
- Protocol: TCP & UDP
- Published: 53
- Target: 53
- Go to
- Add Environment Variables
- Go to
pihole
> Environment - Add Environment Variables:
- Go to
TZ=America/Los_Angeles
FTLCONF_webserver_api_password=generatedPassword
FTLCONF_dns_listeningMode=ALL
FTLCONF_dns_upstreams=9.9.9.9;149.112.112.112
FTLCONF_misc_etc_dnsmasq_d=true
Note: FTLCONF_dns_listeningMode=ALL
is essential for running Pi-hole in a Docker container. It can't listen to other devices on the local area network without it.
- Go to
pihole
and select Deploy, then Start - In PowerShell, on my server, confirm Pi-hole is listening at port 53
sudo lsof -i :53
- See
docker-pr
listed
- Back in Firefox, access Pi-hole's web GUI
- Go to http://192.168.x.x:8081/admin/login
- Log in with generatedPassword
Set router to use Pi-hole as DNS server
For my Asus router, in Firefox:
- Go to http://192.168.x.x
- Go to Advanced Settings > WAN
- Go to DNS Server and select Assign
- DNS Server 1: 192.168.x.x
- Select Save
- Select Apply
- Go to Advanced Settings > LAN > DHCP Server
- Go to DNS and WINS Server Setting
- DNS Server 1: 192.168.x.x
- Select Apply
- Disconnect from Mullvad VPN
- In PowerShell (new window), on my device, confirm Pi-hole is working
nslookup ubuntu.com
- See
Address: 192.168.x.x
(The Asus guide's screenshots don't match my router's settings, so I just did both. It works, so it's fine, right?)
Update Pi-hole settings & add internal domains
In Firefox:
- Go to http://192.168.x.x:8081/admin/
- Expand Settings on the left
- Go to Privacy
- Uncheck Query Logging
- Go to Local DNS Records
- Under List of local DNS records
- Add
macmini.host.internal
/192.168.x.x
- Add
- Under List of local CNAME records
- Add
jellyfin.home.internal
/macmini.host.internal
- Add
pihole.home.internal
/macmini.host.internal
- Add
miniflux.web.internal
/macmini.host.internal
- Add
easypanel.home.internal
/macmini.host.internal
- Add
- Under List of local DNS records
Update Easypanel app domains to internal domains
- Go to http://192.168.x.x:3000
- Select
jellyfin
- Go to Domains > select Add domain
- Turn off HTTPS (since I don't have security certificates for this yet, but will look into this later)
- Host: jellyfin.home.internal
- Internal Port: 8096
- Select Create
- Make the new internal domain primary by starring it
- Go to http://jellyfin.home.internal/ (it works!)
- Update domain for
pihole
too- Host: pihole.home.internal
- Internal Port: 80 (essentially, pick the port that Easypanel says the image exposes)
- Update domain for
miniflux
too- Host: miniflux.web.internal
- Internal Port: 8080
- Go to Easypanel > Settings > General
- Custom Domain: easypanel.home.internal
- Select Save
Okay, now that everything works on the local network, I want everything to still work while I'm on Mullvad VPN.
Set Mullvad VPN to use Pi-hole as DNS server
- Connect on Mullvad VPN
- In PowerShell (new window), on my device, check DNS server IP
nslookup ubuntu.com
- See that
Address:
is not192.168.x.x
- Go to Mullvad VPN > Settings > VPN Settings
- Turn off all DNS content blockers
- Scroll down to Use custom DNS server
- Add a server: 192.168.x.x
- Disconnect from Mullvad VPN & reconnect
- In PowerShell, on my device, confirm Pi-hole is working
nslookup ubuntu.com
- See
Address: 192.168.x.x
- Go to http://jellyfin.home.internal/ (it works!)
Create security certificates for my internal domains
Because I don't like that Not Secure
label next to my address bar & that one warning page that asks if I really want to proceed. A lot of recommendations to buy an example.com
domain and use internal.example.com
subdomain for internal stuff. But I don't want to. (Why do I have to rent a domain for this? I revolt.)
I want to set up security certificates for:
*.home.internal
*.web.internal
*.playground.internal
And I'll use mkcert:
- Install mkcert on my Ubuntu server by following the installation steps for Linux
sudo apt update
sudo apt install libnss3-tools -y
curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64"
chmod +x mkcert-v*-linux-amd64
sudo cp mkcert-v*-linux-amd64 /usr/local/bin/mkcert
mkcert -install
- See:
Created a new local CA 💥
The local CA is now installed in the system trust store! ⚡️
- Create a directory for certificates
sudo mkdir -p /etc/ssl/mkcert
sudo chown $USER:$USER /etc/ssl/mkcert
cd /etc/ssl/mkcert
- Generate certificates
mkcert -key-file home-key.pem -cert-file home-cert.pem home.internal "*.home.internal"
mkcert -key-file web-key.pem -cert-file web-cert.pem web.internal "*.web.internal"
mkcert -key-file playground-key.pem -cert-file playground-cert.pem playground.internal "*.playground.internal"
- Confirm all the keys are there
ls
- See:
home-cert.pem home-key.pem playground-cert.pem playground-key.pem web-cert.pem web-key.pem
Configure security certificates in Easypanel + Traefik
Follow Easypanel's guide for Custom SSL Certificate Configuration:
- Move my certs & keys over to the directory that Easypanel wants them to be in
cd
sudo mkdir -p /etc/easypanel/traefik/certs
sudo chown $USER:$USER /etc/easypanel/traefik/certs
mv /etc/ssl/mkcert/*.pem /etc/easypanel/traefik/certs/
ls /etc/easypanel/traefik/certs/
- Create the custom.yaml file:
tls:
certificates:
- certFile: /data/certs/home.crt
keyFile: /data/certs/home.key
- certFile: /data/certs/web.crt
keyFile: /data/certs/web.key
- certFile: /data/certs/playground.crt
keyFile: /data/certs/playground.key
- Rename my files to match the desired naming convention:
mv /etc/easypanel/traefik/certs/home-cert.pem /etc/easypanel/traefik/certs/home.crt
mv /etc/easypanel/traefik/certs/home-key.pem /etc/easypanel/traefik/certs/home.key
mv /etc/easypanel/traefik/certs/web-cert.pem /etc/easypanel/traefik/certs/web.crt
mv /etc/easypanel/traefik/certs/web-key.pem /etc/easypanel/traefik/certs/web.key
mv /etc/easypanel/traefik/certs/playground-cert.pem /etc/easypanel/traefik/certs/playground.crt
mv /etc/easypanel/traefik/certs/playground-key.pem /etc/easypanel/traefik/certs/playground.key
ls /etc/easypanel/traefik/certs/
- Restart Traefik
- Go to http://192.168.x.x:3000/
- Go to Settings > General
- Select Traefik > Restart
- Update app domain settings to turn on HTTPS
- Go to
jellyfin
> Domains - Select Edit jellyfin.home.internal
- Toggle on HTTPS
- Select Save
- Repeat for other apps
- Go to
- Go to https://jellyfin.home.internal
- See
Warning: Potential Security Risk Ahead
- Select Advanced...
- Select View Certificate
- See my mkcert certificate listed
- Close window
- See
Install certificate on my computer
- Find the root certificate on my Ubuntu server
ls ~/.local/share/mkcert/
- See it here:
~/.local/share/mkcert/rootCA.pem
cat ~/.local/share/mkcert/rootCA.pem
- Copy the certificate's content (including
BEGIN
andEND
lines)
- Save it to my computer
- Open Notepad and paste content
- Save as
mkcert-macmini.crt
- Install the certificate on my computer
- Double-click the .crt file
- Select Install Certificate
- Select Local Machine
- Select Place all certificates in the following store
- Select Trusted Root Certification Authorities
- Select Next
- Select Finish
- Select OK and OK again
- In Firefox, go to https://jellyfin.home.internal
- Still seeing:
Warning: Potential Security Risk Ahead
(checked in Edge browser & didn't get the warning) - It turns out Firefox has a separate certificate store
- Still seeing:
- Add the certificate to Firefox
- In Firefox, go to about:preferences#privacy (this is a URL)
- Scroll down to Security
- Select View Certificates...
- Go to Authorities
- Select Import...
- Select
mkcert-macmini.crt
file - Tick Trust this CA to identify websites.
- Select OK and OK again
- See my mkcert name listed
- Go to https://jellyfin.home.internal in Firefox (it works!)
Automate renewals for security certificates
Certificates expire after 90 days, and I cannot be relied upon to manually renew every three months, so I automate it with a cron job. This only needs to be set up with Traefik on the server & doesn't affect the devices we use.
- Create a new shell script
sudo nano /usr/local/bin/renew-mkcert-certs.sh
- Write a script based on the steps I performed manually earlier
#!/bin/bash
# Paths
CERT_DIR="/etc/easypanel/traefik/certs"
echo "Generating new mkcert certificates..."
# Generate new certs
mkcert -key-file "$CERT_DIR/home-key.pem" -cert-file "$CERT_DIR/home-cert.pem" home.internal "*.home.internal"
mkcert -key-file "$CERT_DIR/web-key.pem" -cert-file "$CERT_DIR/web-cert.pem" web.internal "*.web.internal"
mkcert -key-file "$CERT_DIR/playground-key.pem" -cert-file "$CERT_DIR/playground-cert.pem" playground.internal "*.playground.internal"
echo "Renaming .pem to .crt/.key..."
# Rename files for Easypanel/Traefik
mv -f "$CERT_DIR/home-cert.pem" "$CERT_DIR/home.crt"
mv -f "$CERT_DIR/home-key.pem" "$CERT_DIR/home.key"
mv -f "$CERT_DIR/web-cert.pem" "$CERT_DIR/web.crt"
mv -f "$CERT_DIR/web-key.pem" "$CERT_DIR/web.key"
mv -f "$CERT_DIR/playground-cert.pem" "$CERT_DIR/playground.crt"
mv -f "$CERT_DIR/playground-key.pem" "$CERT_DIR/playground.key"
echo "Certificates renewed and placed in $CERT_DIR."
- Make the script executable
sudo chmod +x /usr/local/bin/renew-mkcert-certs.sh
- Test if the script works
/usr/local/bin/renew-mkcert-certs.sh
- See:
Generating new mkcert certificates...
[...]
Renaming .pem to .crt/.key...
Certificates renewed and placed in /etc/easypanel/traefik/certs.
- Confirm the new certificates are there
ls -lh /etc/easypanel/traefik/certs
- See files & new timestamp:
-rw-r--r-- 1 $user $user 1.5K Apr 11 21:24 home.crt
-rw------- 1 $user $user 1.7K Apr 11 21:24 home.key
-rw-r--r-- 1 $user $user 1.5K Apr 11 21:24 playground.crt
-rw------- 1 $user $user 1.7K Apr 11 21:24 playground.key
-rw-r--r-- 1 $user $user 1.5K Apr 11 21:24 web.crt
-rw------- 1 $user $user 1.7K Apr 11 21:24 web.key
- Restart Traefik in Easypanel & confirm things still work as expected
- Go to http://192.168.x.x:3000/
- Go to Settings > General
- Select Traefik > Restart
- Go to https://jellyfin.home.internal
- No security warnings, we are good
- How do I restart Traefik via command line?
- See commands in Easypanel's guide for Traefik dashboard & docker service update —
--force
can be used to perform a rolling restart without any changes to the service parameters docker service update --force traefik
- Test it out and see:
- See commands in Easypanel's guide for Traefik dashboard & docker service update —
traefik
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service traefik converged
- Go to https://jellyfin.home.internal
- No security warnings, we are good
- Add Traefik restart into shell script
sudo nano /usr/local/bin/renew-mkcert-certs.sh
- Add to the bottom:
docker service update --force traefik
- Set script to run automatically
crontab -e
- Since this is my first time using crontab, it wants to know my preferred editor — I select 1 for nano and press enter
- It opens a new file, and I add to the bottom:
@monthly /usr/local/bin/renew-mkcert-certs.sh
- Save and see:
crontab: installing new crontab
- One last time, confirm it all works
/usr/local/bin/renew-mkcert-certs.sh
ls -lh /etc/easypanel/traefik/certs
- See all the same confirmations as before & the new timestamps for the files
- It works!
Close exposed ports in Easypanel
Now that all the internal domains work, there's no need to access the apps at their IPs anymore. The exposed ports can be closed.
- Go to
jellyfin
> Advanced- Select Remove for 8096
- Select Stop (⏹), Deploy, then Start (▶)
- Visit https://jellyfin.home.internal (everything still works)
- Repeat for
miniflux
- I'm leaving
pihole
's ports open — 53 is essential for it to function, and for 8080, I want to still be able to access it via IP just in case (since this is the thing that makes the internal domains work)
References
I used ChatGPT to help me navigate the https + security certificates situation.
- Container/Easypanel things
- Pi-hole stuff
- DNS info
- Security certificates for internal domains, how
Notes
- Unbound?
- Ye olde turn-it-off-and-on-again saving the day (in this case, the router, after a firmware update, which I did after adjusting my router's settings)
- Everything was slow the next morning & I could barely participate in work meetings
- Did a ping test (
ping 192.168.x.x
), which showed high latency + packet loss - Thought I broke things
- But a quick turn-it-off-and-on-again fixed everything, like it never happened