Skip to content

Instantly share code, notes, and snippets.

@robbert1978
Created May 2, 2026 11:31
Show Gist options
  • Select an option

  • Save robbert1978/bbd0c1f8ac7283e32bd0bab17ba9de10 to your computer and use it in GitHub Desktop.

Select an option

Save robbert1978/bbd0c1f8ac7283e32bd0bab17ba9de10 to your computer and use it in GitHub Desktop.
Setting ssh over vsock as a systemd (<256) service

SSH over vsock — systemd socket activation (Linux side)

Per-connection socket-activated sshd listening on AF_VSOCK port 22. No long-running daemon, no TCP/IP, no networking stack involved on the guest side.

systemd 256+ ships systemd-ssh-generator and creates these units automatically. This doc is for older systemd (240–255, e.g. Ubuntu 24.04) where you install them by hand.

Prerequisites

  • systemd as PID 1 (ps -p 1 -o comm= returns systemd).
    • On WSL2: needs [boot] systemd = true in /etc/wsl.conf and a wsl --shutdown from Windows after editing.
  • openssh-server installed.

If systemd --version is 256 or newer, skip to "Smoke test" — systemctl status sshd-vsock.socket should already be active.

Unit files

/etc/systemd/system/sshd-vsock.socket:

[Unit]
Description=OpenSSH Server vsock Socket

[Socket]
ListenStream=vsock::22
Accept=yes

[Install]
WantedBy=sockets.target

/etc/systemd/system/sshd-vsock@.service:

[Unit]
Description=OpenSSH per-connection server (vsock %I)

[Service]
ExecStart=-/usr/sbin/sshd -i
StandardInput=socket
StandardOutput=socket
StandardError=journal
RuntimeDirectory=sshd
RuntimeDirectoryPreserve=yes

Notes:

  • ListenStream=vsock::22vsock:CID:port with empty CID = bind any CID (VMADDR_CID_ANY). systemd 256+ also accepts vsock://any:22; 255 and earlier reject the URL form.
  • Accept=yes makes systemd accept() on the vsock listener and hand the connected fd to a fresh sshd-vsock@<n>.service instance per connection.
  • sshd -i is OpenSSH's inetd mode — reads/writes the SSH protocol on stdin/stdout instead of binding a port itself.
  • Socket and template service must share the prefix (sshd-vsocksshd-vsock@.service).
  • RuntimeDirectory=sshd recreates /run/sshd (sshd's privsep chroot dir) on every boot. /run is tmpfs, so without this the service dies with fatal: Missing privilege separation directory after every reboot.

Enable and start

sudo systemctl daemon-reload
sudo systemctl enable --now sshd-vsock.socket
sudo systemctl status sshd-vsock.socket --no-pager
ss -l --vsock | grep ':22\b'

Expected: sshd-vsock.socket is active (listening) and ss shows a v_str LISTEN *:22 row.

Smoke test

CID 1 is loopback (the same VM). This proves the activation path end-to-end without leaving the guest:

socat - VSOCK-CONNECT:1:22 < /dev/null

Expected:

SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.16

If you see the banner, systemd accepted on vsock, instantiated the template service, and sshd -i answered.

Required: kill the 10-second login DNS hang

OpenSSH's getpeername() formatter has no case for AF_VSOCK and falls back to the literal hostname UNKNOWN. Several downstream consumers (audit logging, lastlog, PAM session) then try to resolve UNKNOWN — the lookup falls through /etc/hosts, mdns4_minimal, and lands on DNS, which times out twice (5s + 5s = 10s) on every interactive login.

Fix it once at the NSS layer:

echo '127.0.0.1 UNKNOWN' | sudo tee -a /etc/hosts

Now files (the first NSS source) resolves UNKNOWN immediately and the chain never reaches DNS.

UseDNS no in sshd_config does not fix this — the lookup is coming from glibc/audit, not from sshd's reverse-DNS path.

After the UNKNOWN fix, a round-trip time ssh user@host true lands around 200–300 ms; an interactive ssh user@host prompt appears in roughly the same time.

Optional: suppress MOTD output

touch ~/.hushlogin skips the login banner. Not a performance fix on this setup — Ubuntu's MOTD scripts (50-motd-news, 91-release-upgrade, 91-contract-ua-esm-status) do make synchronous network calls, but they're cached/short-circuited fast enough that they don't noticeably extend the login.

Troubleshooting

  • systemctl status sshd-vsock.socket shows bad-setting: systemd version is too old for the address syntax — use vsock::22 (no scheme), not vsock://any:22.
  • socat - VSOCK-CONNECT:1:22 hangs: Accept=yes missing, or the template name doesn't match the socket prefix.
  • socat - VSOCK-CONNECT:1:22 returns immediately with no banner: journalctl -u 'sshd-vsock@*' will show the reason. Most common is fatal: Missing privilege separation directory: /run/sshd — fixed by the RuntimeDirectory=sshd line in the service unit above.
  • Banner is fine but every login takes ~10 s: the UNKNOWN /etc/hosts entry is missing — see the section above.
  • ss -l --vsock shows nothing on :22: socket unit failed to start. journalctl -u sshd-vsock.socket will say why (most often: kernel built without CONFIG_VSOCKETS, or no vsock transport loaded — modprobe vsock_loopback and modprobe vmw_vsock_virtio_transport cover most cases).

Coexistence with TCP sshd

This setup is independent of the normal sshd@.service listening on TCP :22. You can keep both, or set ListenAddress 127.0.0.1 (or remove the TCP listener entirely) in sshd_config so the only way in is vsock.

Different port

Replace 22 with port N:

  • ListenStream=vsock::N in sshd-vsock.socket.
  • On the Windows side, register service GUID <N-hex-padded-8>-FACB-11E6-BD58-64006A7986D3 (e.g. port 2222 → 000008AE-FACB-11E6-BD58-64006A7986D3) and connect with winsocat stdio hvsock:<VM-GUID>:vsock-N.

TL;DR — paste-ready

sudo tee /etc/systemd/system/sshd-vsock.socket >/dev/null <<'EOF'
[Unit]
Description=OpenSSH Server vsock Socket

[Socket]
ListenStream=vsock::22
Accept=yes

[Install]
WantedBy=sockets.target
EOF

sudo tee /etc/systemd/system/sshd-vsock@.service >/dev/null <<'EOF'
[Unit]
Description=OpenSSH per-connection server (vsock %I)

[Service]
ExecStart=-/usr/sbin/sshd -i
StandardInput=socket
StandardOutput=socket
StandardError=journal
RuntimeDirectory=sshd
RuntimeDirectoryPreserve=yes
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now sshd-vsock.socket

echo '127.0.0.1 UNKNOWN' | sudo tee -a /etc/hosts

socat - VSOCK-CONNECT:1:22 < /dev/null     # expect SSH banner

Connect to vsock (Windows side)

  • Using hvc:
λ hvc list

STATUS     NAME
running    Mint
saved      Win11
off        Asmi
off        Endeavour
off        Win10
off        Wn11_2
λ hvc nc -t vsock Mint 22
SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.15
λ hvc ssh robbert@Mint

Last login: Sat May  2 18:02:53 2026 from UNKNOWN
🕑 [18:28:08] robbert::mint : 📁 ~
λ »
  • Using winsocat:
winsocat stdio hvsock:86E0C1E9-782C-47E0-B7C2-99F8167F5178:vsock-22
SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.16
  • Config:
Host mint.mshome.net
  HostName mint.mshome.net
  User robbert
  Proxycommand C:/WINDOWS/system32/hvc.exe nc -t vsock --ssh --host-prefix hyper-v/ "hyper-v/mint" 22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment