Snorre.io

Ngrok like tunnels with Tailscale, a VPS, and Traefik

Sometimes while developing you may want to test a callback or share your work over an https connection. You might have turned to ngrok a tool that allows you to create tunnels to your localhost. Ngrok is a good tool, but to get consistent subdomains you need to pay for a subscription and you only get ngrok domains. In this post we will see how I setup my own domain and tunnel to a dev server on my laptop using Tailscale and a reverse proxy.

Tailscale - A simpler Wireguard setup

Tailscale advertises itself as a “Zero config VPN”. VPN is short for virtual private network and Tailscale implements such a network by building ontop of the Wireguard protocol. In practice you install the Tailscale software on a device, start the daemon, and complete a login process via a URL. Behind the scenes Tailscale will set up keys, distribute them to any other devices via their admin server to join the device into your Tailscale network. This ensures that your new device is accessible from any existing devices in your Tailscale network. There is of course nothing preventing you from setting up Wireguard on your laptop and server and then handling key creation and IPs yourself.

What makes Tailscale useful is that it sets up an overlay network of sorts by meshing all devices and handling physical IP address changes. For N devices you dont have to manually set up pairwise connections between all of them, Tailscale handles this for you. If you run regular Wireguard and wanted a connection from your VPS to your laptop you’d have to update your config everytime your ISP modem changes your IP.

Another nice feature of Tailscale is their magic DNS feature that automatically assigns domains to each of your devices based on their hostnames. These DNS entries are then available on your Tailscale network interfaces and can be used when you want to access a device in place of their Tailscale IPs. Say your laptop hostname is Bobs-MBP, then Tailscale’s magic DNS will make the laptop available as bobs-mbp across your devices.

Each device in your Tailscale service can expose “services” which means any network exposed port running on that device. Essentially if you have an http server listening to port 3000 on your dev machine, the service will be exposed under bobs-mbp:3000 on your Tailscale network. If you join both your laptop and a virtual private server in the cloud to your Tailscale network, the VPS would then have access to your laptop’s services.

Exposing your services to the Internet

With the Tailscale explanation out of the way we can move onto the part where I go into the ngrok like setup. The chart below represents the simplified flow of an HTTPS request to your public domain all the way down to your laptop.

graph TB
    browser[Alice's Browser]
    dns[Cloudflare DNS]
    vps[Cloud VPS]
    traefik[Traefik LB]
    tailscaleVPS[Tailscale VPS]
    magicDNS[Tailscale Magic DNS]
    tailscaleMBP[Tailscale MBP]
    service[Web Service]

    browser -- What is the IP of app.example.com? --> dns
    dns -- It is X.X.X.X --> browser
    browser -- GET https://app.example.com --> vps
    vps -- GET https://app.example.com --> traefik
    traefik -- What is IP of bobs-mbp:3000 --> magicDNS
    magicDNS -- It is 100.X.X.X --> traefik
    traefik -- GET http://100.X.X.X:3000 --> tailscaleVPS
    tailscaleVPS -- tunnel GET http://100.X.X.X:3000 --> tailscaleMBP
    tailscaleMBP -- GET http://local-ip:3000 --> service
    service -- 200 OK --> tailscaleMBP
    tailscaleMBP -- tunnel 200 OK --> tailscaleVPS
    tailscaleVPS -- 200 OK --> traefik
    traefik -- 200 OK --> vps
    vps -- 200 OK --> browser

So essentially what happens is that Alice’s browser performs an HTTPS request against some domain you have setup, e.g. app.example.com. This domain resolves to the public IP address of a server you control, commonly some kind of VPS. As the request reaches your VPS it is routed to a load balancer you are running on the VPS, for example Traefik. The load balancer is then configured to proxy the request to the domain that was generated by Tailscale’s magic DNS. So in the example Traefik resolves bobs-mbp to the Tailscale network IP address of the other device and performs a request with that IP. The Tailscale daemon then handles tunneling that request over its udp tunnel to Bob’s MacBook Pro to the web service you’re running.

When running Traefik on your VPS you might create a rule such as this:

# http routing section
[http]
  [http.routers]
     # Define route to match requests for your domain to a service
     [http.routers.yourapp-dev]
      rule = "Host(`yourapp-dev.example.com`)"
      service = "yourapp-dev"
      # If you want https you need to first configure a certificate resolver which can be used as below
      [http.routers.yourapp-dev.tls]
        certResolver = "letsencrypt"

  # Define a service to handle forwarding the request to your Tailscale domain
  [http.services]
    # Define how to reach an existing service on our infrastructure
    [http.services.yourapp-dev.loadBalancer]
      [[http.services.yourapp-dev.loadBalancer.servers]]
        url = "http://bobs-mbp:3000"

In conclusion

While the setup might look complex from the diagram the process of setting everything up took about an hour of work. The Tailscale service is simple to install and configure, and you’ll get going in almost no time. Once both my VPS and laptop was enrolled in the Tailscale network and magic DNS had been enabled setting up the proxy was the only thing left to do.

Tailscale, being built on top of the Wireguard protocol, performs really well and more than good enough for a dev environment. With this approach you can expose your services to the internet in a safe manner on a domain you own. Shutting down the public access to your service can be done in many ways including:

  • disconnecting your laptop from Tailscale, thus shuttign down the tunnel
  • removing the proxy in your proxy server config, the tunnel is still there
  • using Tailscale ACL config to deny your VPS access to the service, tunnel is there but connections stopped by ACL

In sum I think the setup works well enough that I won’t need ngrok. Additionally Tailscale does more than ngrok and can be used as a traditional VPN when you are on unsecure networks.