Securely accessing my services with Tailscale + Dnsmasq
Table of Contents
For the longest time, I have accessed my self-hosted services publicly over the internet, putting all my trust in my reverse proxy and the authentication which these services implement (alongside server security). So far I’ve never had a breach, and hopefully it will stay that way!
For 90% of my time, I work from home and there is very little need to access my services whilst I’m out. It doesn’t make sense for me to expose my server anymore.
I’ve been playing on and off with Tailscale for the past year, evaluating if its the right solution for me to access my services when I am in the outside world. I know I could spin up a WireGuard tunnel and call it a day, but I love how I can install the Tailscale client, sign in and then that device will have access to other devices on my Tailnet. I don’t have to manage keys or anything like that for my devices.
Requirements
For my remote access solution to work, I came up with some requirements.
- My server should no longer be accessible to the public internet.
- The service URLs should stay the same, whether I am accessing via LAN, or on the go.
- As cheap as possible - I have enough subscriptions already!
Requirement #2 is the most important for me. I do not want to be switching service URLs depending on if I am at home, or outside! It turns out there is a name for this, and it’s called Split DNS. Funnily enough, Tailscale have a great article on it which you can read here.
Setting up Tailscale
I have a dedicated virtual machine running Rocky Linux 10, which I run Tailscale (+ DNS) on. The idea of this machine is to allow Tailscale to connect to services that do not have Tailscale running on them. This is called a subnet router.
tailscale up \
--authkey "TS-AUTH_KEY" \
--advertise-routes=192.168.0.0/24 \
--accept-dns=false
--advertise-routes- the subnet I want to advertise to Tailscale.--accept-dns- ensures that our VM uses local DNS.
After I approved the subnet routes from the Tailscale Dashboard, I need to configure a DNS server that I can run locally on the LAN. I wanted something simple and easily configurable. This is where I discovered dnsmasq.
Setting up Dnsmasq
We need this server as a way to look up internal IPs for our domain. You could do this with a public DNS provider, such as Cloudflare, but its considered bad practice as you’d essentially be returning an unrouteble address for anyone else. It’s definitely the simplest approach, but not one that sits right with me.
Introducing dnsmasq! The lightweight DNS server which I have running inside my Tailscale VM. It made sense to keep the two together.
Once you get dnsmasq installed, you can configure it with a config file at /etc/dnsmasq.conf
listen-address=127.0.0.1,{{ dns_server_ip }} # only bind to localhost and the ip of this server
address=/.coble.casa/{{ reverse_proxy_ip }} # point all *.coble.casa subdomains to reverse proxy
server=1.1.1.1
server=1.0.0.1
no-resolv # don't use /etc/resolv.conf, specifically because this service is providing the resolution found in that file and it would be a recursive loop
domain-needed # anything without dots does not get forwarded to DNS
log-queries
log-facility=/var/log/dnsmasq.log
The core part here is address=/.coble.casa/{{ reverse_proxy_ip }}. This is how we get queries such as photos.coble.casa to resolve to 192.168.0.150. This address entry in the config is a wildcard.
Other bits you may notice is that we have Cloudflare as our upstream DNS provider, and we have query logging enabled.
Restart the service, and now you have a lightweight DNS server running. Be sure to set the IP of this DNS server on your router, so that all of your devices can pick it up the next time their DHCP lease expires.
To check it’s working, I run a dig command.
dig +short photos.coble.casa
192.168.0.150
It returns the IP of my reverse proxy, which is exactly what we want to see! 🎉
This should work from any of your devices on the LAN, assuming that they are not using DNS-over-HTTPS (DoH). If this is the case, they’d need to be changed to use the system DNS instead.
Configuring Tailscale
There is one last bit we need to do to ensure that our Tailscale clients can use the DNS server which we just set up.
- Navigate to the Tailscale Dashboard and click the DNS tab.
- Scroll down to Nameservers and add a new global nameserver - I chose Cloudflare Public DNS.
- Click on the Override DNS servers toggle.
- Add another nameserver, this time clicking Custom.
- Set the IP address to the one of your DNS server.
- Enable the restrict to domain toggle (Split DNS), and add the domain (e.g. coble.casa).
Your Tailscale clients will now resolve to the local DNS server.
Conclusion
The nice thing about this setup is that nothing is exposed publicly anymore — only authenticated Tailscale devices can reach my services when I am on the go, which dramatically reduces my attack surface.
The only downside at the moment is that this setup does have a single point of failure, due to the VM taking care of Tailscale + DNS. In the future, I may introduce a dedicated device such as a Raspberry Pi to act as a fallback. But for the time being, it’s been very stable!