To keep SSH reachable only through WireGuard, bring up the wg0 interface with wg-quick, give each client an address with AllowedIPs, and restrict port 22 to that network: either with ListenAddress in sshd_config or with a ufw rule that only accepts TCP 22 arriving on wg0. From outside, the server exposes only WireGuard's UDP port.

How WireGuard changes the exposure of an SSH server

OpenSSH listens on TCP port 22 by default and, unless told otherwise, on every interface, public one included. With WireGuard in front, the only port open to the Internet is the tunnel's UDP port, and WireGuard stays silent to packets not signed by a known key, so a scan sees nothing.

The setup in this article uses 10.8.0.0/24: the server is 10.8.0.1 and the phone or laptop is 10.8.0.2. SSH only accepts connections on 10.8.0.1 or only from the wg0 interface. There are two ways to get there, and they stack well: bind sshd to the tunnel address with ListenAddress, or filter by interface with ufw.

Configuring WireGuard with wg-quick and AllowedIPs for SSH

wg-quick reads /etc/wireguard/wg0.conf, creates the interface, assigns the address and adds routes for every range listed in AllowedIPs. Start by generating keys with tight permissions:

umask 077
wg genkey | tee server.key | wg pubkey > server.pub

Server configuration:

# /etc/wireguard/wg0.conf (server)
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <server private key>

[Peer]
# phone
PublicKey = <phone public key>
AllowedIPs = 10.8.0.2/32

Client configuration, split tunnel: only traffic to 10.8.0.1 enters the VPN and everything else uses the normal connection.

[Interface]
Address = 10.8.0.2/32
PrivateKey = <phone private key>

[Peer]
PublicKey = <server public key>
Endpoint = server.example.com:51820
AllowedIPs = 10.8.0.1/32
PersistentKeepalive = 25

On the server, AllowedIPs acts as an access list: packets from that peer are accepted only with source 10.8.0.2. On the client it decides which destinations are routed through the tunnel; 0.0.0.0/0 would send everything. To start it and make it persistent:

sudo wg-quick up wg0
sudo systemctl enable --now wg-quick@wg0
sudo wg show

Protocol and key exchange details are in the official WireGuard quick start.

Restricting SSH to wg0 with ListenAddress in sshd_config

The ListenAddress directive makes sshd open its socket only on the given address; port 22 disappears from the public IP, for both IPv4 and IPv6.

# /etc/ssh/sshd_config
ListenAddress 10.8.0.1
PasswordAuthentication no

sudo sshd -t
sudo systemctl restart ssh   # sshd on other distributions

The usual catch is boot order: if sshd starts before wg0, the address does not exist yet and sshd fails with "Cannot assign requested address". Fix it with a systemd override (sudo systemctl edit ssh) adding [email protected] and [email protected] under [Unit]. On distributions that start sshd through ssh.socket, the listening address comes from the socket unit, not sshd_config. Every option is documented in the sshd_config manual page.

Restricting SSH to wg0 with ufw: exact per-interface rules

With ufw, sshd keeps listening everywhere, but the firewall only lets TCP 22 through when it arrives on wg0. It does not depend on boot order and it also covers Mosh, which uses UDP ports 60000-61000.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 51820/udp
sudo ufw allow in on wg0 to any port 22 proto tcp
sudo ufw allow in on wg0 to any port 60000:61000 proto udp
sudo ufw delete allow OpenSSH
sudo ufw enable
sudo ufw status verbose

Apply these rules from a session already running over the tunnel, and open a second connection to 10.8.0.1 before closing the first. The sturdiest setup is per-interface ufw plus ListenAddress: if one layer breaks, the other still keeps the port closed.

SSH over WireGuard on iOS and Android versus desktop

On a phone, the official WireGuard app brings the tunnel up as a system VPN profile, and the SSH client connects to 10.8.0.1 like any other host. The app can generate the key pair on the device or import a .conf from a QR code made with qrencode -t ansiutf8 < client.conf.

What is comparediOS / AndroidDesktop
Tunnel clientOfficial WireGuard app, system VPN profilewg-quick or the official app
Importing the configQR code or .conf fileCopy the .conf into /etc/wireguard
Switching wifi to mobile dataMosh keeps the SSH session aliveRare; plain SSH is usually enough
Esc, Ctrl, Tab and arrowsNeed the terminal's own keyboardPhysical keyboard
SSH private keySystem secure storage~/.ssh at 700, keys at 600

In termi1, the mobile SSH terminal, the Ed25519 key is generated on the phone and never leaves the device; Mosh keeps the session when you move from wifi to mobile data, and tmux holds whatever you left running. If the 10.8.0.1 connection is tagged as production, Production Guard holds back commands that delete or stop things before they reach the prompt. If you would rather not manage WireGuard keys by hand, a mesh such as a private network without open ports or Tailscale fills the same role.

4 mistakes when locking SSH inside WireGuard

  1. Closing public port 22 with no fallback. If the tunnel drops, only the provider console is left; check it works first.
  2. Overlapping AllowedIPs between peers. Two peers with the same range send traffic to whichever was declared last.
  3. Forgetting Mosh's UDP ports. Mosh starts over SSH, but without 60000-61000 open on wg0 the session never connects.
  4. Not validating sshd_config. Run sshd -t before restarting; a syntax error leaves the service down.

Frequently asked questions

Is WireGuard enough without changing the SSH configuration?

WireGuard alone does not protect port 22: sshd still listens on the public IP and anyone can try to connect. You also need ListenAddress set to the tunnel address or a ufw rule that only accepts TCP 22 arriving on wg0.

What should the client's AllowedIPs contain for SSH only?

The client's AllowedIPs should hold only the server's tunnel address, for example 10.8.0.1/32. That way wg-quick routes just that traffic through the VPN and normal browsing uses the regular connection. Setting 0.0.0.0/0 would push all traffic through the tunnel.

Does Mosh work over WireGuard?

Mosh works over WireGuard as long as the server accepts UDP 60000-61000 on wg0. Mosh first opens an SSH connection to launch mosh-server, then switches to UDP. It does not do port forwarding or X11 forwarding, so those still require classic SSH.

What happens if WireGuard goes down and SSH only listens on wg0?

If WireGuard goes down while SSH is limited to wg0, there is no SSH access from the Internet until the tunnel returns. Keep the provider's web console available, enable wg-quick@wg0 at boot, and order sshd after the tunnel with systemd.