I’ve been using Claude Code heavily for development work, and there’s something magical about having one perfectly configured machine with all my tools, dotfiles, and project context exactly as I want them.
One nagging issue I’ve had is switching devices (from desktop to laptop or vice-versa) meant:
- Spending time syncing everything through Git (losing flow state)
- Inevitably different state (linux to mac)
Previously I’d just used basic SSH, maybe with some jump server setup, but never felt great about the experience.
My goals were simple - SSH access that was:
- Secure by default (no exposed ports)
- Works seamlessly at home and remotely
- Great DevEx that simplifies access regardless of location
I’d heard good things about Tailscale, a mesh VPN that’s free for personal use. It also has Tailscale SSH which handles SSH access natively.
Figured I’d write myself a quick guide for “6 months from now me” on how I got it working.
Setup
OPNSense router:
- Enable NAT-PMP in Services → UPnP
- Install Tailscale through ports
ssh admin@router
# opnsense-code ports
cd /usr/ports/security/tailscale
make install
service tailscaled enable
service tailscaled start
tailscale up --ssh
Next go to Interfaces -> Assignments
Note that there’ll is a new device now (tailscale0
)
Add it, name it, and enable it
ArchLinux desktop (zoe):
sudo pacman -S tailscale
sudo systemctl enable --now tailscaled
sudo tailscale up --ssh
MacBook (chloe):
Initially tried using the GUI, but it’s sandboxed so cannot run --ssh
, luckily there is a brew
variant.
brew install tailscale
brew services start tailscale
# Note you may need to do allow in settings for tailscale on startup
tailscale up --ssh
Within minutes, all devices showed up in my Tailscale network:
❯ tailscale status
100.64.0.2 zoe tsoporan@ linux
100.64.0.3 opnsense tsoporan@ freebsd
100.64.0.4 chloe tsoporan@ macOS
Testing
At this point we have all three devices setup with tailscale + ssh, you should be able to seamlessly do ssh <name>
and get into each one.
The Tailscale admin console also has a built-in SSH test feature. Click it, set username, and everything should work.
Since I use zoe
as primary for development, I now just connect and zellij attach main
to resume my Zellij session and get back to work exactly where I left off.
The Magic
What’s actually happening here is pretty cool. Tailscale uses multiple NAT traversal techniques to establish direct encrypted connections between devices without exposing any ports to the internet.
The core problem: When you’re behind a router, you have two obstacles:
- Stateful firewalls - allow outbound traffic, block inbound unless there was a matching outbound packet first
- NAT devices - rewrite your private IP to a public one, but create different mappings for different destinations
The “simultaneous transmission” trick: Both devices send packets to each other at the same time. Each device’s outbound packet “opens” its firewall for the return traffic, even if the initial packets get lost. It’s like both sides knocking on each other’s door simultaneously.
STUN discovery: Tailscale asks servers “what’s my public IP:port from your perspective?” This reveals what your traffic looks like on the internet after NAT translation.
Different NAT behaviors:
- Easy NATs (most home routers): Create one mapping per device that works for any destination
- Hard NATs (corporate firewalls): Create different mappings for every destination you talk to
Port mapping protocols: When available, Tailscale can ask your router via UPnP/NAT-PMP to forward a specific port, making connections much easier.
The birthday paradox: For stubborn NATs, Tailscale can use probability theory - open many ports on one side and randomly probe from the other. Math works out that you find a match much faster than brute force.
ICE algorithm: The elegant solution is “try everything at once and pick what works best.” Tailscale simultaneously attempts:
- Direct LAN connection (when you’re home)
- Multiple STUN-discovered endpoints
- Port-mapped connections
- Various IPv4/IPv6 combinations
When I’m at home: traffic routes directly through my LAN. When remote: encrypted tunnel through Tailscale’s network. Same commands, zero configuration.
The fallback is always DERP relays - if direct connection fails, traffic routes through Tailscale’s servers, but it’s still end-to-end encrypted.
Final Thoughts
I’m always looking for small productivity hacks that improve my workflow. Tailscale deserves all the praise it gets for taking something that’s typically annoying/complicated to set up right and making it just work.
Having one consistent development environment that follows me everywhere has been a game-changer. No more context switching, no more “let me just sync this,” no more productivity drops when working remotely.
Big fan.