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.
- systemd as PID 1 (
ps -p 1 -o comm=returnssystemd).- On WSL2: needs
[boot] systemd = truein/etc/wsl.confand awsl --shutdownfrom Windows after editing.
- On WSL2: needs
openssh-serverinstalled.
If systemd --version is 256 or newer, skip to "Smoke test" —
systemctl status sshd-vsock.socket should already be active.
/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=yesNotes:
ListenStream=vsock::22—vsock:CID:portwith empty CID = bind any CID (VMADDR_CID_ANY). systemd 256+ also acceptsvsock://any:22; 255 and earlier reject the URL form.Accept=yesmakes systemdaccept()on the vsock listener and hand the connected fd to a freshsshd-vsock@<n>.serviceinstance per connection.sshd -iis 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-vsock↔sshd-vsock@.service). RuntimeDirectory=sshdrecreates/run/sshd(sshd's privsep chroot dir) on every boot./runis tmpfs, so without this the service dies withfatal: Missing privilege separation directoryafter every reboot.
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.
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/nullExpected:
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.
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/hostsNow 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.
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.
systemctl status sshd-vsock.socketshowsbad-setting: systemd version is too old for the address syntax — usevsock::22(no scheme), notvsock://any:22.socat - VSOCK-CONNECT:1:22hangs:Accept=yesmissing, or the template name doesn't match the socket prefix.socat - VSOCK-CONNECT:1:22returns immediately with no banner:journalctl -u 'sshd-vsock@*'will show the reason. Most common isfatal: Missing privilege separation directory: /run/sshd— fixed by theRuntimeDirectory=sshdline 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 --vsockshows nothing on:22: socket unit failed to start.journalctl -u sshd-vsock.socketwill say why (most often: kernel built withoutCONFIG_VSOCKETS, or no vsock transport loaded —modprobe vsock_loopbackandmodprobe vmw_vsock_virtio_transportcover most cases).
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.
Replace 22 with port N:
ListenStream=vsock::Ninsshd-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 withwinsocat stdio hvsock:<VM-GUID>:vsock-N.
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- 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