On this page
- Architecture
- SSH Socket Activation
- Firewall Rules
- Defense in Depth
- Tailscale Migration
- Local SSH Config
SSH & Tailscale Setup
Architecture
Laptop ──► Tailscale ──► 100.125.3.44:22 ──► sshd
│ │
│ iptables: accept from
│ 100.109.232.127 only
│
100.109.232.127 (laptop)
SSH Socket Activation
Ubuntu 24.04 manages SSH via systemd socket activation. The
ssh.socket unit creates the listening socket and passes it
to sshd on connection. This means
ListenAddress in sshd_config is
ignored — the socket unit controls the bind.
Override file
Path: /etc/systemd/system/ssh.socket.d/override.conf
[Socket]
ListenStream=
ListenStream=100.125.3.44:22
ListenStream=127.0.0.1:22
FreeBind=trueListenStream=(empty) clears the default0.0.0.0:22and[::]:22100.125.3.44:22is the Tailscale interface IP127.0.0.1:22allows localhost access (e.g., from the hosting console)FreeBind=trueallows the socket to start even if the Tailscale IP isn’t up yet
Apply
sudo mkdir -p /etc/systemd/system/ssh.socket.d
sudo cp override.conf /etc/systemd/system/ssh.socket.d/
sudo systemctl daemon-reload
sudo systemctl restart ssh.socketFirewall Rules
Two iptables rules control SSH access:
# Allow only laptop Tailscale IP
iptables -A INPUT -p tcp --dport 22 -s 100.109.232.127 -j ACCEPT
# Drop everything else on port 22
iptables -A INPUT -p tcp --dport 22 -j DROP
# Persist across reboots
iptables-save > /etc/iptables/rules.v4The iptables-persistent package loads
/etc/iptables/rules.v4 on boot.
Defense in Depth
Even though the socket unit already limits the listener to
Tailscale/localhost, the iptables rules add a second layer. If Tailscale
ever went down and the FreeBind caused sshd to bind
elsewhere, the firewall still blocks non-laptop IPs.
Tailscale Migration
Personal → Org
The VPS was on a personal tailnet. Migration steps:
- Remove the machine from the personal tailnet via admin console
- On the VPS:
systemctl stop tailscaled && rm -f /var/lib/tailscale/tailscaled.state - Start tailscaled and authenticate with org auth key:
systemctl start tailscaled tailscale up --auth-key=tskey-auth-<key>
Connection flow
Clients on the org tailnet connect directly (wireguard) to the VPS.
No public IP is involved. The Tailscale IP 100.125.3.44 is
stable but not guaranteed to never change — if it does, update the ssh
socket override.
Local SSH Config
~/.ssh/config on the laptop:
Host orcta_vps
HostName 100.125.3.44
User bernard
IdentityFile ~/.ssh/orcta_id
Without this change, connections still went to
158.220.100.30 (public IP), which now refuses SSH.