A review of Tailscale, "a secure network that just works".

Tailscale is a service based on WireGuard that lets one’s devices form a peer-to-peer private network in a easy and seamless manner.

I have been using it for over a year now, so I can now do a quick review on how I use the service on a day-to-day basis.

Setup

Although it is possible to set up WireGuard manually to connect devices, it gets harder when peers are behind NAT. Tailscale takes charge of NAT traversal automagically. It’s also much simpler to setup, because once you’re connected to your Tailscale account, all your peers are added automatically and kept up-to-date. Currently, I connect to all my personal servers through Tailscale!

I still use WireGuard as a gateway, sometimes, when I am on untrusted networks. Tailscale has this feature, called exit nodes. However the performance is not as good as native WireGuard because it uses the Go userspace implementation. It also uses more CPU. I do use the exit node feature when I need a quick VPN on my phone for example. As usual, it works like magic.

Is it super easy to install. On macOS and iOS, the app is available on the store.

On linux, it’s a simple binary available for a veriety of distributions. Connecting to Tailscale and getting the peers devices is done with tailscale up.

Here’s how the CLI looks like:

root@yua ~# tailscale
USAGE
  tailscale [flags] <subcommand> [command flags]

For help on subcommands, add --help after: "tailscale status --help".

This CLI is still under active development. Commands and flags will
change in the future.

SUBCOMMANDS
  up         Connect to Tailscale, logging in if needed
  down       Disconnect from Tailscale
  logout     Disconnect from Tailscale and expire current node key
  netcheck   Print an analysis of local network conditions
  ip         Show current Tailscale IP address(es)
  status     Show state of tailscaled and its connections
  ping       Ping a host at the Tailscale layer, see how it routed
  version    Print Tailscale version
  web        Run a web server for controlling Tailscale
  file       Send or receive files
  bugreport  Print a shareable identifier to help diagnose issues

FLAGS
  --socket string
    	path to tailscaled's unix socket (default /var/run/tailscale/tailscaled.sock)

Let’s check my peers:

pi@raspberrypi ~> tailscale status
100.74.200.33   raspberrypi          user.name@ linux   -
100.118.141.47  chuu                 user.name@ linux   -
100.92.179.42   devenv               user.name@ linux   -
100.111.33.49   iphone               user.name@ iOS     active; relay "lhr", tx 13356 rx 5396
100.84.228.41   mina                 user.name@ linux   active; direct x.x.x.x:41641, tx 8220 rx 8140
100.85.62.76    momo                 user.name@ linux   active; direct [2001:bc8:xx:xx::1]:41641, tx 5480 rx 5396
100.91.147.84   nako                 user.name@ linux   active; direct [2001:bc8:xx:xx::1]:41641, tx 8220 rx 7956
100.119.51.44   sakura               user.name@ linux   active; direct x.x.x.x:41641, tx 258712 rx 70712
100.106.43.29   sana                 user.name@ linux   active; direct [2001:bc8:xx:xx:xx:xx:xx:9651]:41641, tx 3013004 rx 2969396
100.123.142.67  seulgi               user.name@ linux   active; direct [2001:bc8:xx:xx::1]:41641, tx 3951828 rx 3835332
100.114.241.37  yeji                 user.name@ macOS   active; direct [2a01:cb00:xx:xx:xx:xx:xx:2945]:41641, tx 814188 rx 57772
100.69.186.40   yua                  user.name@ linux   -
100.91.182.128  yuri                 user.name@ linux   active; direct x.x.x.x:41641, tx 2682584 rx 2654072

Above are my various peers, using the GCNAT IPv4 address range. Some of the peers are connected though IPv4, other IPv6.

My Raspberry Pi wasn’t able to connect to my iPhone directly for some reason, so in that case Tailscale opted to use a relay. This is completely transparent and ensures that all my devices can talk to each other, whatever happens.

Tailscale routes traffic over the shortest path possible. In most cases, this is a direct, peer-to-peer connection.

In cases where a direct connection cannot be established, devices will communicate by bouncing traffic off of one or more geographically distributed relay servers, called DERPs. The traffic that bounces through our relay servers is encrypted and no different security-wise than the other dozen hops your Internet packets already make when passing over the network from point A to B.

From the CLI, I can also see my peer’s endpoints, and the nearest DERP (relay).

pi@raspberrypi ~> tailscale netcheck

Report:
	* UDP: true
	* IPv4: yes, x.x.x.x:56966
	* IPv6: yes, [2a01:cb00:xx:xx:xx:xx:xx:2ab3]:41322
	* MappingVariesByDestIP: false
	* HairPinning: false
	* PortMapping:
	* Nearest DERP: London
	* DERP latency:
		- lhr: 65.1ms  (London)
		- fra: 65.1ms  (Frankfurt)
		- nyc: 77.7ms  (New York City)
		- dfw: 114.7ms (Dallas)
		- blr: 128.8ms (Bangalore)
		- sfo: 141.3ms (San Francisco)
		- sea: 156.7ms (Seattle)
		- tok: 231.2ms (Tokyo)
		- sin:         (Singapore)
		- syd:         (Sydney)

To check connectivity to peers, there is a ping command that shows the latency and the endpoint used to contact the peer.

root@yua ~# tailscale ping chuu
pong from chuu (100.118.141.47) via DERP(fra) in 54ms
pong from chuu (100.118.141.47) via DERP(fra) in 24ms
pong from chuu (100.118.141.47) via [2001:bc8:604:52c::1]:41641 in 11ms

Above we can see that there wasn’t an active connection webteen the two devices so the packets went trhough the relay until the peer-to-peer connection was established.

Web admin

The devices can be managed through a web interface:

Oops, looks like some of my peers are out-of-date

Oops, looks like some of my peers are out-of-date

The endpoints for my RPi (which is behind NAT!), the closest relays in case a p2p connection can’t be established, and discovered services on the Pi.

The endpoints for my RPi (which is behind NAT!), the closest relays in case a p2p connection can’t be established, and discovered services on the Pi.

The website is great and does what I need, but I have to say that I hate the fact that I have to use my Google account to connect. (this has changed recently, but I can’t convert my account to the other forms of authentication, I think.)

Pricing

Tailscale recently updated their pricing, which is still very generous, so I can’t complain. The Personal plan that I use is free and includes 20 devices. Moreover, they are very flexible about the limitations:

With both legacy and new pricing, we’ll continue to use soft limits. You’ll never be stopped from spinning up more devices or subnet routers, or trying out ACL rules. We encourage you to play around, find what works best for you, and update your payment settings after-the-fact.

If you’re using vastly more resources than your plan allows, we’ll get in touch about upgrading to something suited for your needs.

How refreshing!

Open-source & documentation

Everything is open source on github.com/tailscale which is awesome. Besides, the founders and engineers have been very open about their work on Twitter and on their blog. The blog is a sensational resource thanks to the detailed in-depths blog posts about the inner-workings of Tailscale, and consequently Wireguard, NAT, Go, and more.

I also have to mention the knowledge base which contains an endless amount of information, answering almost every interrogation one could have about the service.

My use cases

Tailscale enables many use cases. You can find many example in the monthly newsletter which gathers a few blog posts about Tailscale. Here are mine.

SSH to my servers

My servers don’t expose any open an OpenSSH server on the internet anymore. Thanks to tailscale, I can connect to them trough a peer-to-peer tunnel, and I don’t even need to open any port on my Firewall!

While Tailscale has a magic DNS feature, it prefer manage my own addressing:

➜  ~ dig +short mina.ts.infra.stanislas.cloud
100.84.228.41
➜  ~ dig +short mina.pub.infra.stanislas.cloud
335b3582-aea0-4691-8946-b0dd0431ec2c.pub.instances.scw.cloud.
51.15.236.143

I prefer do it that way because I use NextDNS on my laptop and phone with a specific configuration, and I don’t want Tailscale to mess with it.

Accessing my Raspberry Pi from anywhere

When away from home or abroad, I like to keep an access to my Raspberry Pi at home, just in case. To do so, in the past, I installed WireGuard on the Pi and enabled port forwarding on my router to access it. I’m more at ease having WireGuard exposed to the whole world than SSH, but it’s still no ideal. Thanks to Tailscale and its NAT traversal magic, I can access my Raspberry Pi from anywhere with zero-configuration.

Private websites from anywhere

There are some personal websites (hosted on some cloud providers) that I would like to not be publicly accessible, but still be able to access from anywhere.

A few examples:

  • Grafana
  • Prometheus
  • qBitorrent
  • Kibana
  • and so on.

And so on. Firewall rules are a pain since I my home IP changes every few days. Whith Tailscale, I have a much better solution at my disposal.

Caddy is my reverse proxy of choice. I just tell it to bind on the Tailscale IP of the machine it’s on, and I’m done.

root@yua ~# cat /etc/caddy/vhosts/service
service.yua.stanislas.cloud {
	bind 100.69.186.40
	reverse_proxy http://127.0.0.1:3000
}

Since I use DNS challenge with the Cloudflare API plugin, I don’t need Let’s Encrypt to access my server, so I can still get certificates for these websites. (Not that it really matters, since I’m accessing them trough an encrypted tunnel anyway).

Secure private network between providers

Right now, I use Tailscale between my servers for monitoring and logs. This prevents from exposing services like Prometheus or Logstash to the internet (and messing with UFW rules). It also comes in handy when I want to transfer a file between two servers.

Since performance is not excellent (remember that is uses the userspace implementation of WireGuard), I don’t think it would be suited for low-latency stuff like databases.

In a previous version of my personal infrastructure, I used WireGuard to connect my servers. I used an ansible role for this, which is was much more of a hassle!

Taildrop

Airdrop is one of the best features of the Apple ecosystem. Taildrop extends it to every other operating system.

That being said, even if I only use Apple devices, Taildrop seems faster than Airdrop for me. The advantage is that Airdrop requires devices to be next to each other, while Taildrop doesn’t since in goes trough the mesh network.

What could be improved

I’m a big fan of Tailscale and it covers all my needs, there are two things thatI think could be improved.

I can’t look at my syslog without filtering anymore because of how much tailscaled logs. Here is over the span of a minute:

May 29 16:05:10 sana tailscaled[71511]: netcheck: probePortMapServices: failed to look up gateway address
May 29 16:05:10 sana tailscaled[71511]: [RATELIMIT] format("netcheck: probePortMapServices: %v")
May 29 16:05:16 sana tailscaled[71511]: [RATELIMIT] format("netcheck: probePortMapServices: %v") (1 dropped)
May 29 16:05:16 sana tailscaled[71511]: netcheck: probePortMapServices: failed to look up gateway address
May 29 16:05:18 sana tailscaled[71511]: Accept: TCP{100.84.228.41:5044 > 100.106.43.29:38886} 58 tcp non-syn
May 29 16:05:20 sana tailscaled[71511]: netcheck: probePortMapServices: failed to look up gateway address
May 29 16:05:20 sana tailscaled[71511]: [RATELIMIT] format("netcheck: probePortMapServices: %v")
May 29 16:05:28 sana tailscaled[71511]: Accept: TCP{100.84.228.41:5044 > 100.106.43.29:38886} 58 tcp non-syn
May 29 16:05:29 sana tailscaled[71511]: [RATELIMIT] format("netcheck: probePortMapServices: %v") (1 dropped)
May 29 16:05:29 sana tailscaled[71511]: netcheck: probePortMapServices: failed to look up gateway address
May 29 16:05:32 sana tailscaled[71511]: netcheck: probePortMapServices: failed to look up gateway address
May 29 16:05:36 sana tailscaled[71511]: netcheck: probePortMapServices: failed to look up gateway address
May 29 16:05:38 sana tailscaled[71511]: Accept: TCP{100.84.228.41:5044 > 100.106.43.29:38886} 58 tcp non-syn
May 29 16:05:39 sana tailscaled[71511]: netcheck: probePortMapServices: failed to look up gateway address
May 29 16:05:39 sana tailscaled[71511]: [RATELIMIT] format("netcheck: probePortMapServices: %v")
May 29 16:05:46 sana tailscaled[71511]: [RATELIMIT] format("netcheck: probePortMapServices: %v") (1 dropped)
May 29 16:05:46 sana tailscaled[71511]: netcheck: probePortMapServices: failed to look up gateway address
May 29 16:05:48 sana tailscaled[71511]: Accept: TCP{100.84.228.41:5044 > 100.106.43.29:38886} 58 tcp non-syn
May 29 16:05:50 sana tailscaled[71511]: wgengine: idle peer d:5854132954582031 now active, reconfiguring wireguard
May 29 16:05:50 sana tailscaled[71511]: wgengine: Reconfig: configuring userspace wireguard config (with 4/12 peers)
May 29 16:05:50 sana tailscaled[71511]: magicsock: ParseEndpoint: key=[5mlQZ]: 5854132954582031b8c96247458b9af4ee9c14f27a72118890d36194e25d475a.disco.tailscale:12345
May 29 16:05:50 sana tailscaled[71511]: magicsock: DERP packet received from idle peer [5mlQZ]; created=true
May 29 16:05:50 sana tailscaled[71511]: magicsock: disco: node [5mlQZ] d:5854132954582031 now using [2a01:xxxx]:41641
May 29 16:05:50 sana tailscaled[71511]: netcheck: probePortMapServices: failed to look up gateway address
May 29 16:05:50 sana tailscaled[71511]: [RATELIMIT] format("netcheck: probePortMapServices: %v")
May 29 16:05:57 sana tailscaled[71511]: Accept: TCP{100.106.43.29:22 > 100.114.241.37:56269} 288 ok out
May 29 16:05:59 sana tailscaled[71511]: [RATELIMIT] format("netcheck: probePortMapServices: %v") (1 dropped)
May 29 16:05:59 sana tailscaled[71511]: netcheck: probePortMapServices: failed to look up gateway address
May 29 16:06:02 sana tailscaled[71511]: netcheck: probePortMapServices: failed to look up gateway address
May 29 16:06:06 sana tailscaled[71511]: Accept: TCP{100.106.43.29:22 > 100.114.241.37:56269} 152 ok out
May 29 16:06:07 sana tailscaled[71511]: netcheck: probePortMapServices: failed to look up gateway address
May 29 16:06:10 sana tailscaled[71511]: netcheck: probePortMapServices: failed to look up gateway address

Also, it constantly uses at least of few % of CPU, even when no traffic is going through the tailscale0 interface.

This is on Linux though, I don’t have any complaints on macOS.

Conclusion: ❤️

Tailscale is an amazing solution. It’s elegant, it just works. It feels like magic! It feels like the future, but now.