Page cover

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.

Setting up your first Linux Machine in Tailscale

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.

Adding the Linux Server

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-network

Now 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 -d

Check 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 :53

If 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=no

Adding 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.

PiHole Local DNS

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

Adding custom DNS server

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