This section describes the different aspects of my internal network.
Once I started building up my homelab with different services, I wanted to take it to the next level and find a secure way to access services remotely. I saw that both Twingate and Tailscale were popular with others, but Twingate wasn't free so I chose Tailscale. This page details what I learned during the implementation of Tailscale in my homelab.
Setting up Tailscale was one of the easier projects I had done in awhile. I began with downloading the Tailscale application on my main Windows client machine as well as my iPhone. Other than authenticating and providing permissions, there wasn't anything else to set up. I verified it worked by turning off Wi-Fi on my iPhone and trying to access the network share hosted from my windows client machine. It connected without issue!
Next, I set up Tailscale on my Macbook. I was worried that this would stop my Wiki from working due to the different IP address, but after testing I was happy to see that it wasn't affected. The nice thing about Tailscale is that it can be used in docker containers. However, in the case of my Wiki, that was not the right solution since it shared an IP with the host.
I didn't run into issues until I got to my Alpine Linux VM. Following the Tailscale documentation for Linux, I installed it by curling the install.sh script.
First enter
apk add curlas root.
The install itself was simple, but when I ran tailscale up, the terminal acted as if it was doing something, but nothing happened. After doing some research and trying different commands to setup (essentially the same commands that were in the install script), I went back to my windows pc to check my Tailscale admin dashboard. I could no longer connect to the webpage and then I realized that this was most likely caused by the AWS outage at the time. Since I couldn't access the login page in a browser, it made sense that it wouldn't work in a terminal.
Once the Tailscale site came back up, I tried tailscale up again with no success. That is when I realized that the second part of my original issue was that since I was using a linux distro with no GUI, interactive logins wouldn't work. I then went to the Tailscale admin settings and generated an Authorization Key. Back in my VM, I ran tailscale up --authkey <insert-key> which authenticates without an interactive login. I received no errors and tailscale status showed a list of the machines on my tailnet, so I knew it was successful.
I have had a tough time grasping the concept of how Tailscale functions, so this is what I understand so far.
When two or more devices are linked to Tailscale, they form a private network known as a 'Tailnet' and each device gets an additional IP address in the 100.x.x.x range. This IP range is similar to 192.x.x.x or 10.x.x.x in that it is not reachable by the public internet. In the case of Tailscale, the devices on the tailnet can only access each with those IPs.
However, these devices are still able to reach the public internet using their normal IP addresses. The idea behind this VPN is that a device can be on a separate network but still be able to access a shared folder on a computer that is on a different network for example. So, it is like both computers are sitting next to each other on the same network.
Tailscale also has capabilities to allow/deny permissions. I have not yet experimented with this feature, but the way I understand it is that a device or user can be granted or denied access to a resource based on Access Control Lists (ACLs).
Tailscale has an interesting feature known as an Exit Node. By default, Tailscale uses split tunneling to direct traffic. Essentially, it connects devices together and allows resources to be shared on the Tailnet, but all other traffic is routed to the internet through the ISP connected to the device.
The disadvantage of this method is that the traffic routed to the internet is not encrypted as it only uses the Wi-Fi/Cellular network that you are on. That is where exit nodes come in. When an Exit Node is activated on the Tailnet, connected devices send all traffic through the node then use the router connected to the node. To demonstrate, when the Exit Node is enabled, visit whatismyipaddress.com which will show the public IP as your home router instead of the ISP of your cellular network (if testing from phone).
Once I got the exit node set up on my Windows Server, I realized that ads were not being blocked even though my public IP was correctly showing my home router. It turns out that DNS still uses the local settings on my phone. Fixing this involved several steps:
The following steps are performed on the Tailscale admin console.
1. Under DNS > Nameservers, click Add Nameserver > Custom.
2. Enter the Tailscale IP address from the Pihole server.
3. Enable Use with exit node.
4. Under DNS > Search Domains, click Add search domain...
5. Enable Override DNS servers.
6. Enter the Windows Server domain.
The following steps are performed on the Pi-hole host.
1. Modify the pihole configuration.
sudo nano /etc/pihole/pihole.toml
2. Change the below settings:
# Change from this:
interface = "ens18" ### CHANGED, default = ""
# to this:
interface = "ens18,tailscale0" ### CHANGED, default = ""
# Change from this:
listeningMode = "LOCAL"
# to this:
listeningMode = "SINGLE"
3. Save file then restart pihole-FTL
sudo systemctl restart pihole-FTL
The following steps are performed on Windows Server.
1. In Control Panel > Network and Internet > Network Connections, edit the IPv4 settings of your network adapter to use the pihole server for DNS. Make sure the Tailscale IP is used.
The following steps are performed on a Tailscale client device.
1. Connect to the Exit Node.
2. Enable Allow Local Network Access.
3. In Preferences or DNS Settings (depending on the device), Enable Use Tailscale DNS Settings.
The other option is to use Pi-hole for DNS without enabling an Exit Node. With this setup, all DNS queries go through Pi-hole, and everything else gets routed through the normal Wi-Fi/Cellular network the device is using. This means the traffic would not be using the Tailscale encrypted tunnel, but the device would benefit from the ad-blocking of Pi-hole.
This will be my primary option. With Exit Node + Pi-hole, the disadvantage is that you are limited by bandwidth and upload speeds of your home network, so connections could potentially be slower. If I don't need an encrypted connection, then I really only need the ad-blocking ability.
| Option | Internal Resources | Web Traffic | Ad-blocking |
|---|---|---|---|
| Tailscale | Accessible | Unencrypted | False |
| Exit Node Only | Accessible | Encrypted | False |
| Exit Node + Pi-hole | Accessible | Encrypted | True |
| Pi-hole Only | Accessible | Unencrypted | True |
Another cool feature I found with Tailscale is the ability to advertise routes. When I was away from home, I attempted to access my Proxmox server using the internal IP but it would not resolve. That is when I learned about Tailscale Subnets.
By using the below command on my Windows Server VM (Exit Node), it allows the subnet to be accessible through the Tailnet. After executing, the subnet needs to be approved on the Tailscale admin interface.
tailscale set --advertise-routes 10.0.0.0/24
With RustDesk, I wanted to be able to access my devices when I was away from home. RustDesk does have a way to do this, but it includes exposing its ports to the public internet and I did not want to open myself to that vulnerability.
Instead, Tailscale allows remote access without exposing ports because the devices are on the same tailnet. Before I began, I tested RustDesk as it currently was, and it worked, but when I turned Wi-Fi off on my phone, RustDesk would no longer connect to a remote host. This was because the HBBR server was not mapped to the Tailscale IP address.
The solution was simply to modify the docker-compose.yml inside the VM by changing the HBBS service command to the following:
command: hbbs -r <tailscale-ip>:21117
This tells the HBBS server where to find the HBBR server.
After re-upping the containers, I changed the ID/relay settings on all client machines to use the Tailnet IP of the Alpine VM and confirmed I was able to connect to a remote host when not on the same network.
Before installing Tailscale on the VM or modifying the RustDesk containers, I attempted using RustDesk from a different network but it didn't work. This was because the Alpine VM had its own IP address, unlike my Wiki containers which shared the host IP. Therefore, Alpine needed its own instance of Tailscale to connect to the tailnet and obtain an IP.
Once I started building services exposed to the public, I needed to ensure higher availability. A static IP with my ISP was not an option because this would cost money, so I started exploring other options. That's when I learned about dynamic dns (DDNS).
Since IPv4 addresses have run out, ISPs only have a small amount that they can hand out. This means that they constantly cycle through addresses resulting in services becoming inaccessible since dns records need to be manually updated. That is where DDNS comes in. When the ISP changes the IP address of the router that hosts your services, the DDNS provider will automatically update DNS records to reflect the change. This keeps services running without the need for manual intervention.
There are many different ways to implement dynamic DNS. I first tried to use GoDaddy since it is my domain registrar but you are required to have at least 10 domains with them to be able to use DDNS services.
As an alternative, GoDaddy gives you the ability to generate an API key which can be added to a router or firewall to automatically update the GoDaddy DNS records when the IP address changes. However, when I looked at the dynamic DNS section of my router, it only allowed 3 pre-configured service providers and GoDaddy was not one of them.
One of the service providers offered by my router was changeip.com. I registered a domain name of tybax.dynamic-dns.net then went to GoDaddy and deleted my A records for wiki and emsdemo. I created CNAME records for my subdomains and pointed them to tybax.dynamic-dns.net with a TTL of 600 seconds. After that, all that was left was to enable dynamic DNS on my router and supply my credentials for changeip.com. Now, whenever a user navigates to one of my sites, they will be directed to my changeIP domain which will be pointing to my current WAN IP. So, even if my IP changes, the user will still always reach my services.