Command Palette
Search for a command to run...
Comments
Join the discussionNo comments yet. Be the first to comment.
Search for a command to run...
No comments yet. Be the first to comment.
My setup notes — what I did, why, and how to recover when things break.
I have a Mac mini sitting at home on a residential connection with no static IP. I want to:
SSH into it from my laptop from anywhere, using plain old OpenSSH (no cloudflared, no Tailscale SSH magic).
Reach the rest of my home LAN through it — printers, NAS, router admin page, other devices.
Keep it secure enough to move sensitive data with no realistic man-in-the-middle exposure.
I went with Tailscale instead of a Cloudflare tunnel. It's a WireGuard-based mesh: every device gets a stable 100.x.y.z address that doesn't change even when my home IP does, NAT traversal handles the no-static-IP problem, and there's no port forwarding to set up.
Each device generates its own WireGuard keypair locally. The private key never leaves the device, so Tailscale (the company) literally cannot decrypt my traffic.
Traffic is end-to-end encrypted, point-to-point. Even when it falls back through Tailscale's relay (DERP) servers, those only ever see already-encrypted packets.
The one residual trust is that Tailscale's coordination server hands out honest public keys. That's the only theoretical MITM seam — and Tailnet Lock (Part 2 below) closes it entirely.
Reality check: device security and tight access rules matter more than the crypto. A compromised laptop or an over-broad ACL is a bigger real-world risk than WireGuard being broken.
This is the part to actually do first. Get SSH + LAN access working and stable before adding any hardening.
On macOS there are two builds. For an always-on machine that will act as a subnet router, install the standalone build (the tailscaled daemon + full CLI), not the App Store GUI version.
Standalone runs as a real system daemon, survives logout, and starts at boot.
It gives me the full tailscale CLI I need for --advertise-routes.
Download the standalone .pkg from https://tailscale.com/download/macos.
For the laptop, either build is fine — the App Store version is easiest since it's just a client.
Same account on both so they land in the same tailnet.
On the Mac mini:
sudo tailscale up
It prints a login URL — open it, authenticate, approve the device. Do the same on the laptop (menu-bar icon → log in).
Verify they see each other:
tailscale status
Admin console (login.tailscale.com) → DNS → enable MagicDNS. Lets me ssh user@mac-mini instead of memorizing the 100.x address.
This is the "just make it reachable, I'll use normal ssh" path — no Tailscale SSH.
That starts Apple's standard sshd. Tailscale isn't doing anything special — it's just the encrypted network path.
From the laptop, anywhere in the world:
ssh youruser@mac-mini # with MagicDNS
# or
ssh youruser@100.x.y.z # raw tailnet IP
Harden it with key-based auth:
ssh-copy-id youruser@mac-mini
Then optionally set PasswordAuthentication no in /etc/ssh/sshd_config for key-only access.
On the Mac mini:
ipconfig getifaddr en0 # the mini's LAN IP, e.g. 192.168.1.42
netstat -rn | grep default # confirms the gateway/router
If the mini is 192.168.1.42 with a typical /24, my subnet is 192.168.1.0/24. Adjust for your own network — some routers use 192.168.0.0/24, 10.0.0.0/24, etc.
The mini has to forward packets bound for other LAN devices:
sudo sysctl -w net.inet.ip.forwarding=1
sudo sysctl -w net.inet6.ip6.forwarding=1
macOS does not persist sysctl across reboots, so make it permanent with a LaunchDaemon.
Create /Library/LaunchDaemons/com.user.ipforwarding.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.ipforwarding</string>
<key>ProgramArguments</key>
<array>
<string>/usr/sbin/sysctl</string>
<string>-w</string>
<string>net.inet.ip.forwarding=1</string>
<string>net.inet6.ip6.forwarding=1</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
Then load it:
sudo chown root:wheel /Library/LaunchDaemons/com.user.ipforwarding.plist
sudo launchctl load /Library/LaunchDaemons/com.user.ipforwarding.plist
Forwarding now re-enables automatically after every reboot.
On the Mac mini (already up, so use set):
sudo tailscale set --advertise-routes=192.168.1.0/24
(Or sudo tailscale up --advertise-routes=192.168.1.0/24 to do it at login time. Both work.)
Subnet routes are double opt-in — advertising isn't enough, I must approve.
Admin console → Machines → click the Mac mini → Edit route settings → toggle on 192.168.1.0/24 → Save.
macOS clients use advertised subnets by default, so the laptop picks up the route automatically once approved — no --accept-routes flag needed.
Now from anywhere:
Mac mini directly: ssh youruser@mac-mini
Printer / NAS / router admin / other LAN boxes: just use their normal 192.168.1.x addresses, as if I were home.
System Settings → Energy / Battery → prevent sleep (or enable "wake for network access"). A sleeping mini drops off the tailnet and takes the LAN route with it.
[ ] tailscale status on both → each sees the other.
[ ] Mini shows it's advertising the route; admin console shows it approved (green).
[ ] From a genuinely remote network (phone hotspot is easiest): ssh mac-mini works and I can reach a 192.168.1.x LAN device.
Subnet collision gotcha: if the remote network I'm on also uses
192.168.1.0/24, routing gets ambiguous and LAN access misbehaves. Fix by changing my home router to a less common range like192.168.50.0/24or something in10.x.
Do this only after Part 1 is stable. It removes the need to trust Tailscale's key distribution — even a malicious/compromised Tailscale couldn't silently insert a rogue node to MITM me.
Any current Tailscale build is fine (Tailnet Lock has been GA since 2023).
I want redundancy so one failure doesn't strand me. With only a Mac mini + laptop, use both as signing nodes. (Note: an Android phone can't be a signing node, if I add one later.)
Admin console → Settings → Tailnet Lock → follow the wizard. Select signing nodes; it hands me a tailscale lock init ... command to paste on a trusted node.
Paste the command on the Mac mini. It prints disablement secrets. This is the critical part:
Disablement secrets are shown only once, at init. Lose them (and don't give one to Tailscale support) and the tailnet can't be recovered.
I only need one secret to disable the lock, though several are generated.
Store them in my password manager.
During setup, opt to generate the extra disablement secret that's handed to Tailscale support — that's my safety net against lock-out.
After init, existing devices must be signed by a trusted key. The console flags any "locked out" nodes; sign them from a signing node. From then on, every new device must be signed from the mini or laptop before it can connect — that's the tradeoff for not trusting Tailscale's key distribution.
tailscale lock disable <one-of-your-disablement-secrets>
If a signing key is ever compromised (different from lost):
tailscale lock recover-compromised-key # revokes the bad key, removes devices it signed
After a power outage, most of this heals itself if Part 1 was done as above:
tailscaled (standalone build) starts at boot and reconnects automatically.
A changed public IP doesn't matter — NAT traversal re-negotiates.
IP forwarding returns (LaunchDaemon RunAtLoad).
Subnet route + console approval persist; LAN access comes back.
SSH / Remote Login is a system service; it restarts at boot.
But two things will strand me if not handled:
By default a Mac can stay off after power returns. Turn on auto-restart:
sudo pmset -a autorestart 1
pmset -g | grep autorestart # verify
(Equivalent: System Settings → Energy → "Start up automatically after a power failure.")
If FileVault is on, a cold boot stops at the pre-boot unlock screen and macOS never finishes booting until someone physically types the password. Until then: no Tailscale, no SSH, no LAN route — fully locked out remotely.
Tradeoff:
| FileVault | Pro | Con |
|---|---|---|
| Off | Unattended recovery "just works" after a power cut | Disk not encrypted at rest (physical theft exposes data) |
| On | Stronger at-rest security | A cold boot likely needs physical access / KVM before remote access returns |
For an always-on server I want to reach remotely, I'm leaning FileVault off on this machine, relying on home physical security + key-only SSH + tight Tailscale ACLs instead. (Reconsider if the mini holds genuinely sensitive data at rest.)
Set a DHCP reservation for the mini on the router so it always gets the same LAN IP. Not strictly required for subnet routing, but it avoids confusion and survives a router reset cleanly.
Quick reference for when something breaks.
Expected: mini auto-boots (if pmset autorestart 1), tailscaled reconnects, route + SSH come back on their own within a few minutes.
If it doesn't come back: check FileVault — a cold boot may be stuck at the unlock screen. Plug in a monitor/keyboard at home and log in. Confirm pmset -g | grep autorestart shows 1.
Prevent next time: ensure auto-restart is on; decide consciously on FileVault.
On the mini: tailscale status → is it still advertising the route?
Admin console → Machines → mini → is the route still approved (green)?
On the mini: sysctl net.inet.ip.forwarding → should be 1. If 0, the LaunchDaemon didn't load: sudo launchctl load /Library/LaunchDaemons/com.user.ipforwarding.plist.
Am I on a remote network with the same subnet as home? That collision breaks LAN routing — switch networks or change my home subnet.
From the laptop: tailscale status → does it list the mini, and is the mini online?
If the mini is offline → it's a power/network/FileVault issue at home (see power section).
If online but SSH refused → Remote Login may have been turned off. Need physical/console access to re-enable System Settings → Sharing → Remote Login.
Key-only auth and lost my key? Re-enable password auth locally, or add a new public key from the console.
Re-check the subnet CIDR (Step 5) — it may have changed (e.g. back to 192.168.0.0/24).
If it changed, re-advertise: sudo tailscale set --advertise-routes=<new-subnet> and re-approve in the console.
Re-add the DHCP reservation for the mini.
Existing devices keep working — only adding new nodes breaks.
If I still have a disablement secret: tailscale lock disable <secret>.
If I gave a secret to Tailscale support: contact them to disable.
Worst case (no secrets anywhere): abandon the tailnet, create a fresh one, reinstall Tailscale on both machines, re-auth, re-advertise the route. ~15 minutes at this scale. (I always retain physical control of my own hardware — I'm never bricked out of the machine itself, only the old tailnet.)
tailscale lock recover-compromised-key
Revokes the bad key and automatically removes every device it signed. Then re-sign the legit devices.
System Settings → Energy → disable sleep / enable "wake for network access."
Confirm it's on power (not relying on display sleep settings only).
sudo tailscale down && sudo tailscale up # reconnect
tailscale netcheck # diagnose NAT / DERP / connectivity
As a last resort on the mini, uninstall and reinstall the standalone build, then re-auth — physical access to the machine means this is always available to me.
Standalone Tailscale on the mini, App Store version on the laptop, same account.
Enabled Remote Login (plain SSH) + key-based auth on the mini.
Turned on IP forwarding + LaunchDaemon to persist it.
Advertised 192.168.x.0/24 and approved it in the console.
Set pmset autorestart 1, made a conscious FileVault choice, kept the mini awake.
(Later / optional) Enabled Tailnet Lock with both devices as signing nodes and stored the disablement secrets.
End result: I SSH to mac-mini and reach my whole home LAN from anywhere, no static IP, no port forwarding, encrypted end-to-end — and a blackout doesn't lock me out.