Tailscale
Introduction
Tailscale is a VPN service that uses WireGuard to create a fast, secure, and simple peer-to-peer network. Tailscale is a zero config VPN that works on any platform, service, or runtime. It encrypts all connections using WireGuard and integrates with 100+ tools for easy deployment and management.
Tailscale is a modern VPN built on top of Wireguard. It works like an overlay network between the computers of your networks - using NAT traversal.
Everything in Tailscale is Open Source, except the GUI clients for proprietary OS (Windows and macOS/iOS), and the control server.
The control server works as an exchange point of Wireguard public keys for the nodes in the Tailscale network. It assigns the IP addresses of the clients, creates the boundaries between each user, enables sharing machines between users, and exposes the advertised routes of your nodes.
A Tailscale network (tailnet) is private network which Tailscale assigns to a user in terms of private users or an organisation.
You can run Tailscale as an SSH Server and as an Exit Node - which means you can remove any SSH ports and have the Tailscale handle all the authentication and use itself as a Gateway for any node on your Tailscale network. Amazing.
Self Hosting
You can self host a Tailscale server with Headscale + Headscale Admin.
Headscale is an open source, self-hosted implementation of the Tailscale control server. Headscale's goal is to provide self-hosters and hobbyists with an open-source server they can use for their projects and labs. It implements a narrow scope, a single Tailnet, suitable for a personal use, or a small open-source organisation.
Headplane is a GUI that can deeply integrate with the Headscale server and bears more than a passing resemblance to the actual Tailscale Admin :-)
Headscale Admin is a web frontend for the headscale Tailscale-compatible coordination server.
YouTube - Jim's Garage - Self Host Tailscale with Headscale
Docker
You can self host a Tailscale server with Headscale + Headscale Admin + Traefik docker containers.
Follow the instructions on the respective web sites and the YouTube (outdated though!) video to get started, including grabbing copies of the docker compose and headscale config/config.yaml files, then edit the first 3 options as follows ...
server_url: https://headscale.mydomain.com listen_addr: 0.0.0.0:8080 metrics_listen_addr: 0.0.0.0:9090
Running headscale in a container
This is the directory layout for a Docker Compose method ...
/root/docker/stacks/headscale |-- config | `-- config.yaml |-- data | |-- db.sqlite | `-- noise_private.key |-- docker-compose.yaml `-- run `-- headscale.sock 3 directories, 5 files
... and this is the docker compose YAML file which has both Headscale and Headscale Admin containers ...
services: headscale: container_name: headscale volumes: - ./config:/etc/headscale/ - ./data:/var/lib/headscale/ - ./run:/var/run/headscale/ ports: - 8080:8080 - 9090:9090 image: headscale/headscale:0.23.0-alpha12 environment: - TZ=Europe/London command: serve restart: unless-stopped networks: traefik: ipv4_address: 172.19.0.28 labels: - "traefik.enable=true" - "traefik.docker.network=traefik" - "traefik.http.routers.headscale.rule=Host(`headscale.mydomain.com`) && PathPrefix(`/`)" - "traefik.http.routers.headscale.entrypoints=websecure" - "traefik.http.routers.headscale.service=headscale" - "traefik.http.services.headscale.loadbalancer.server.port=8080" - "traefik.http.services.headscale.loadbalancer.server.scheme=http" headscale-admin: image: goodieshq/headscale-admin:latest container_name: headscale-admin restart: unless-stopped environment: - TZ=Europe/London volumes: - ./config:/etc/headscale/ - ./data:/var/lib/headscale/ - ./run:/var/run/headscale/ ports: - 9999:80 networks: traefik: ipv4_address: 172.19.0.29 labels: - "traefik.enable=true" - "traefik.docker.network=traefik" - "traefik.http.routers.headscale-admin.rule=Host(`headscale.mydomain.com`) && PathPrefix(`/admin`)" - "traefik.http.routers.headscale-admin.entrypoints=websecure" - "traefik.http.routers.headscale-admin.service=headscale-admin" - "traefik.http.routers.headscale-admin.middlewares=auth" - "traefik.http.services.headscale-admin.loadbalancer.server.port=80" - "traefik.http.services.headscale-admin.loadbalancer.server.scheme=http" - "traefik.http.middlewares.auth.basicauth.users=funkyusername:xxxxxxxxxxxxxxxxhashedxxxxpasswordxxxxxxxxxxxxxxx" networks: traefik: external: true
Start up the containers, and you should see these lines in the docker logs ...
headscale | 2024-05-31T10:53:13+01:00 INF Opening database database=sqlite3 path=/var/lib/headscale/db.sqlite headscale | 2024-05-31T10:53:13+01:00 INF Setting up a DERPMap update worker frequency=86400000 headscale | 2024-05-31T10:53:13+01:00 INF listening and serving HTTP on: 0.0.0.0:8080 headscale-admin | {"level":"info","ts":1717149193.3994539,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"} headscale-admin | {"level":"info","ts":1717149193.4059176,"msg":"serving initial configuration"}
Test the headscale server with curl and the docker image ...
curl 0.0.0.0:9090/metrics docker exec headscale headscale --help docker exec headscale headscale version docker exec headscale headscale nodes list docker exec headscale headscale apikeys list
Navigate to the Headscale Admin web address you provided in the config.yml file with /admin at the end - https://headscale.mydomain.com/admin) and then you will see an error about Authentication and missing API key.
Generate your server's API key. You only get one chance to see this ...
docker exec headscale headscale apikeys create
... copy it, then go the Headscale Admin Settings page and paste it there with your 'server_url' from the config file.
Then press the SAVE button.
Yay - you have successfuly set up your own self-hosted Tailscale server! :-)
Now you can add your first client device.
Client Devices
Windows
https://tailscale.com/download/windows
Prevent Auto-Connect on Restart/Login
Linux
Software Installation
Native
Install the software ...
curl -fsSL https://tailscale.com/install.sh | sh
Go to your Admin server and grab your pre-auth key.
Join the tailnet ...
tailscale up --login-server=https://headscale.mydomain.uk --authkey=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx --accept-dns=false
WEB
There is a built-in web interface with tailscale, which you can access by running this command ...
sudo tailscale set --webclient
... and then pointing your web browser at the address it tells you to - e.g. http://100.64.0.1:5252
To stop the web interface, run this command ...
sudo tailscale set --webclient=false
GUI
https://github.com/DeedleFake/trayscale
Docker
https://hub.docker.com/r/tailscale/tailscale
https://tailscale.com/kb/1282/docker
Create your pre-auth key on your Headscale server using Headscale Admin.
Pull the image ...
docker pull tailscale/tailscale:latest
Start the docker passing the login server and pre-auth key environment variables ...
docker run -d --name=tailscale -v /var/lib:/var/lib -v /dev/net/tun:/dev/net/tun --network=host --cap-add=NET_ADMIN --cap-add=NET_RAW --env TS_STATE_DIR=/var/lib/tailscale --env TS_AUTHKEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx --env TS_EXTRA_ARGS=--login-server=https://headscale.domain.uk --advertise-exit-node --env TS_HOSTNAME=mynodehostname tailscale/tailscale
Test ...
docker exec tailscale tailscale --socket /tmp/tailscaled.sock status
Ping a node ...
docker exec tailscale tailscale --socket /tmp/tailscaled.sock ping nameofnode
Job, done.
Here is the Docker Compose for all that ...
services: tailscale: container_name: tailscale volumes: - /var/lib:/var/lib - /dev/net/tun:/dev/net/tun network_mode: host cap_add: - NET_ADMIN - NET_RAW environment: - TS_STATE_DIR=/var/lib/tailscale - TS_USERSPACE=false - TS_AUTHKEY=f85ff211b94078a1xxxxxxxxxxxxxxx079bae00769e51 - TS_EXTRA_ARGS=--login-server=https://headscale.mydomain.uk --advertise-exit-node - TS_HOSTNAME=mynodehostname image: tailscale/tailscale restart: unless-stopped
Cloud Init
https://www.youtube.com/watch?v=e-X5FJwrkaA
Users
Generate a User on the Headscale server first, then register a Node linked to that User.
docker exec headscale headscale users create <USER_NAME>
Nodes
Registration
Method 1: Register a machine using normal login
On a client machine, run the tailscale login command, which will output a machine key (mkey) ...
sudo tailscale up --login-server <YOUR_HEADSCALE_URL>
On the server, run the headscale nodes command to register that client machine node using that mkey to a User ...
docker exec headscale headscale nodes register --key <mkey:YOUR_MACHINE_KEY> --user <USER_NAME>
Method 2: Register a machine using a pre authenticated key
Generate a key linked to a User ...
docker exec headscale headscale preauthkeys create --reusable --expiration 24h --user <USER_NAME>
This will return a pre-authenticated key that can be used to connect a node to headscale during the tailscale login command on the client ...
sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
Testing
On the server, run the command to list the connected nodes ...
docker exec headscale headscale nodes list ID | Hostname | Name | MachineKey | NodeKey | User | IP addresses | Ephemeral | Last seen | Expiration | Connected | Expired 1 | paully-laptop | paully-laptop | [v6BuV] | [E5A+n] | paully | 100.64.0.1, fd7a:115c:a1e0::1 | false | 2024-05-31 12:56:23 | 0001-01-01 00:00:00 | online | no
Exit Node
Want to hide your real IP address or "be in a different country"?
Server
Spin up your virtual private server in France and when you join that node to your tailnet, just advertise it as an 'exit node' ...
sudo tailscale up --login-server=https://headscale.mydomain.uk --authkey=a8590d89ca494b27cxxxxxxxxxxx5e3e923cc2ad974ee1be --advertise-exit-node --hostname=exit-node-fr-1 --accept-dns=false
Then toggle the option in your client to use the exit node in France 'exit-node-fr-1' and check your IP address at https://ipinfo.io
Client
List exit nodes on your tailnet ...
sudo tailscale exit-node list IP HOSTNAME COUNTRY CITY STATUS 100.64.0.2 nodename.username.tailscale.tailnet.name - - -
Then set your traffic to go through that exit node ...
sudo tailscale set --exit-node=100.64.0.2
https://tailscale.com/kb/1103/exit-nodes
Subnet Routers
https://tailscale.com/kb/1019/subnets
Pi-Hole with Tailscale
https://shotor.com/blog/run-your-own-mesh-vpn-and-dns-with-tailscale-and-pihole/
HowTos
What's my tailnet name?
tailscale status --json | jq -j '.MagicDNSSuffix'
How do I list my tailnets?
sudo tailscale switch --list
How do I login to multiple tailnets?
sudo tailscale login --login-server https://controlplane.tailscale.com --auth-key string --nickname work sudo tailscale login --login-server https://tailscale.mydomain.com --auth-key string --nickname home
How do I switch tailnets?
sudo tailscale switch --list sudo tailscale switch work sudo tailscale switch home
How do I route specific network traffic via Tailscale but not all traffic?
This has been a long time coming but I have finally figured this out - how to use an exit node but not route all your network traffic through it!
In this example, we will be routing traffic to a server through Tailscale but delete the network's default device of the Tailscale tunnel ...
sudo -i tailscale up tailscale status tailscale set --exit-node=100.64.0.2 --exit-node-allow-lan-access=true ip route add 3.10.68.19/32 dev tailscale0 table 52 scope global ip route del default dev tailscale0 table 52 ip route get 3.10.68.19 ping 3.10.68.19 ip route get 1.1.1.1 ping 1.1.1.1 ip route get 192.168.0.1 ping 192.168.0.1
Links
Tailout - a command-line tool for quickly creating a cloud-based exit node in your tailnet.
Public BTCPay Server with Umbrel & Tailscale
Authentication
https://github.com/gmiles32/headscale-authelia
Remote Access
- RustDesk > Settings > Security > Direct IP Access + IP Whitelisting (e.g. tailscale IP range = 100.64.0.x)
- RustDesk > Settings > Security > Password > Use Permanent Password