Set up local domains with Tailscale and PiHole
You want to access local service through a VPN but don't want to setup the VPN yourself and also want domains
When I started setting up selfhosted services, I was not confident enough to point my services directly at my home ip address and open the 443 port on my router. My ISP was also a dynamic IP address so it would need regular updating to my dns servers to make it work. That's why I decided to go with a VPN service. One of the best things about tailscale is that it creates a local network between your connected devices so you could access each other without. It is done without even needing to rent a VPS in the cloud. They hosted it for you!
As usual I was pretty vigilant about tailscale reading my data so I went through most of their privacy policies to make sure I know what I was getting into. You can read their Security policies here.
Setting up tailscale
To begin with, you create account with tailscale account . Then you add your first machine. Since most of my services were running docker containers, I chose the Linux Server option.

Leave most option as default:
Do not use exit node
Do not check ephemeral
Do not reuse the authentication key
Leave the authkey expiration on default (90 days)
Not reusing the authentication key is important, if you checks this option, any devices that got hands on your secret key would be able to connect to your network seamlessly. If left uncheck the authentication key becomes invalidated after the first device connects.

Click on generate install script and copy the script and keep it somwhere safe. It will be used later.
Setting up docker tailscale client
Now that we have generated install script, it is now time to register a tailscale client. I prefer using the tailscale client on a linux container. That way it does not have access to all your networks but only the ones you specify.
Next, generate a network where the tailscale client would be on. Type ip a and see how your docker networks are and try generating a similar one:
docker network create \
--subnet=172.21.0.0/16 \
tailscale-networkNow you set up the tailscale client using a docker-compose.yml file:
version: "3.7"
services:
tailscale:
image: tailscale/tailscale:latest
container_name: tailscale
environment:
- TS_AUTHKEY=tskey-auth-[paste-your-tskey-here]
- TS_EXTRA_ARGS=--advertise-routes=[advertise your subnet here e.g: 172.21.0.0/16]
- TS_STATE_DIR=/var/lib/tailscale
- TS_USERSPACE=false
volumes:
- ./[your tailscale directory]:/var/lib/tailscale
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
- sys_module
networks:
tailscale-tunnel:
ipv4_address: [give-a-fixed-ip-adress-for-container e.g:172.21.0.2]
restart: unless-stopped
networks:
tailscale-network:
external: true
ipam:
config:
- subnet: [your-tailscale-subnet-here e.g:172.21.0.0/16]Save and run your docker-compose.yml file
docker compose up -dCheck your taiscale logs to make sure everything is working
Setting up Pihole for local dns
Now that your tailscale is working, its now time to set up PiHole so that it resolves local domain queries. This way you do not need to type ip addresses in the url bar and can simply type domain names. Keep in mind .com, .org domains cannot be local. I've found that it works best using .local for local domain names.
First set up PiHole as a docker container:
services:
pihole:
container_name: pihole
image: pihole/pihole:latest
# For DHCP it is recommended to remove these ports and instead add: network_mode: "host"
ports:
- "53:53/tcp"
- "53:53/udp"
# "67:67/udp" # Only required if you are using Pi-hole as your DHCP server
- "82:82/tcp" #web port
environment:
TZ: '[Your timezone]'
WEB_PORT: 82 #internal docker webport
WEBPASSWORD: 'your-strong-and-secure-password'
volumes:
- './etc-pihole:/etc/pihole'
- './etc-dnsmasq.d:/etc/dnsmasq.d'
networks:
tailscale-network:
ipv4_address: [pi-hole-fixed-ip]
restart: unless-stopped
networks:
tailscale-network:
external: true
ipam:
config:
- subnet: [tailscale-subnet]Additional Note: Sometimes, they might say that your port 53 is already in used. You can check to see what's using it with:
sudo lsof -i :53If it's your systemd-resolve, you can stop it from using the port 53 without stopping the service itself. Go to /etc/systemd/resolved.conf and add the line:
DNSStubListener=noAdding DNS records
Now go to your pihole server from your local adress with http://[your-local-ip]:82/admin. Login with your password. Next, go to Local DNS -> DNS records. You add your local domain names with your container ip address.
Note: You cannot add port number here and you have to add the tailscale subnet ip address meaning whatever ip address you specified at your tailscale-network.

After you have set it up go back to your tailscale settings. Click on Add nameserver and then Custom

In the custom field, point it to your PiHole subnet ip (e.g: 172.21.0.2) and click on restrict domains to .local

This way only your .local DNS queries when your devices are connected to tailscale will go through pihole. If you want all your DNS queries to go through PiHole untoggle Restrict to Domain.
Now you can access your domain with by typing the local domain name:port_number (e.g: pihole.local:82) when connected to tailscale!
E.g: pihole.local:82
Last updated
