I have a Mac on the university campus. The IT people set it up and showed me how to remotely connect to it. The only problem is that I need to use the university’s VPN every time I want to use this machine, either through ssh or the Screen Sharing app on macOS. I understand the reason behind this decision, but logging in to my VPN requires me to enter my full username and password every single time (and maybe even bothers me with the Duo 2FA). Plus, the IT people didn’t know how to expose a specific port on this machine to the internet (with or without VPN), and online tutorials were discussing advanced system admin stuff. I didn’t want to spend so much time just to learn how to expose a port behind the university VPN. I decided to find an easier solution. The answer: tailscale.

tailscale allows me to expose a specific port of my machine to my tailnet (i.e., only I get access) or to the entire internet. These are accomplished via tailscale serve ... and tailscale funnel ... commands. tailscale has a wonderful documentation and explains these in great detail.

I set up tailscale on my remote Mac like this:

alias tailscale="/Applications/Tailscale.app/Contents/MacOS/Tailscale"

Let’s say my server is running at localhost:8000 . I expose it to the entire internet like so:

tailscale funnel --bg --set-path /api 8080

Notice how there’s no need to specify the port number anymore. Some programs still need to know the port number. Use something like 0 or 0000 and you’ll be fine.

--bg means this funnel will start automatically on system reboot. --set-path /api means I get to access this endpoint at <my-tailscale-url>/api. To get the list of exposed ports, you can do tailscale funnel status or tailscale serve status. To the best of my knowledge on February 5, 2024, you can’t just remove/edit one item on the list—you need to reset the whole thing using tailscale funnel reset and start again.

But I still needed to use VPN to use ssh or the Screen Sharing app. So I added a new connection to the Screen Sharing app with the remote Mac’s tailscale IP (given on the tailscale console or the app), and I used port 5900 —the VNC port. That was it! Screen Sharing didn’t require VPN anymore.

All was well until the Mac restarted to run an update. For security reasons, Apple forces you to log-in in-person before you can use the Screen Sharing app again. So I had to go all the way to the office just to enter my username and password. And then some day the app stopped working completely because it thought my credentials were not right. I found a way to re-enable Screen Sharing on the remote Mac just by having ssh access:

sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -configure -access -off -restart -agent -privs -all -allowAccessFor -allUsers

This did the trick for a couple days, but the app stopped working again. I had enough and thought maybe ssh would be less annoying. But ssh still required using VPN, so I decided to fix the stupid VPN problem once and for all. Interestingly, tailscale had me covered again. Almost.

Turns out, tailscale supports ssh out of the box; you just do tailscale up --ssh. But this command doesn’t work on the version of the tailscale app I had installed through App Store because it was sandboxed. tailscale offers two more ways to install the app on Mac: a standalone macOS application (GUI) and an open-source CLI version (no GUI).

I tried the first one and replaced the App Store version. But the app still didn’t support ssh. Maybe it’s because of Apple’s limitations because GUI tailscale apps use Network or System Extensions offered by Apple. I was left with no other choice than the no-GUI CLI app.

Untitled

I installed the CLI app from Github after uninstalling the GUI versions. I also had to remove the alias I gave to tailscale before. Installing the CLI app required installing go on the remote (brew install go) and compiling. After that, the binaries were put in /users/behnam/go/bin, just like the tutorial said. I added them to my PATH in Fish by doing:

set -U fish_user_paths /users/behnam/go/bin $fish_user_paths

Then I ran: