GitHub

On this page

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=true
  • ListenStream= (empty) clears the default 0.0.0.0:22 and [::]:22
  • 100.125.3.44:22 is the Tailscale interface IP
  • 127.0.0.1:22 allows localhost access (e.g., from the hosting console)
  • FreeBind=true allows 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.socket

Firewall 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.v4

The 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:

  1. Remove the machine from the personal tailnet via admin console
  2. On the VPS: systemctl stop tailscaled && rm -f /var/lib/tailscale/tailscaled.state
  3. 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.

Edit this page
Last modified: 2026-08-03