TL;DR — On Ubuntu 22.10 and later, sshd is started by systemd socket activation. The listening port belongs to ssh.socket, not to /etc/ssh/sshd_config. Editing Port in sshd_config is silently ignored. Fix it with a ssh.socket drop-in.
The Symptom
You uncomment Port yourNumber in /etc/ssh/sshd_config, restart the service, and nothing changes. What makes this especially confusing is that sshd itself insists the config was read correctly:
$ sudo sshd -T | grep -i "^port"
port yourNumber
$ sudo ss -tlnp | grep sshd
LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=174486,fd=3),("systemd",pid=1,fd=202))
LISTEN 0 4096 [::]:22 [::]:* users:(("sshd",pid=174486,fd=4),("systemd",pid=1,fd=203))
The config says yourNumber. The kernel says 22. The giveaway is in that last column: the listening file descriptor is held by systemd (pid=1), not by sshd.
Root Cause
Ubuntu even documents this in the shipped config file, though it is easy to scroll past:
# Port and ListenAddress options are not used when sshd is socket-activated, # which is now the default in Ubuntu.
Diagnosis in Three Commands
systemctl is-enabled ssh.socket sudo ss -tlnp | grep sshd sudo sshd -T | grep -i "^port"
| Observation | Diagnosis |
|---|---|
enabled + fd owned by systemd | Socket activation. The port lives in ssh.socket. |
sshd -T shows the new port, ss shows the old one | Same thing — config parsed, then ignored. |
sshd -T still shows port 22 | Your edit was never read. Check sshd_config.d/. |
Unit ssh.socket does not exist | Classic mode. sshd_config is authoritative. |
The Fix: A ssh.socket Drop-In
Keep socket activation and put the port where it actually belongs. Use systemctl edit rather than writing under /lib/systemd/system/, so your change survives package upgrades.
sudo systemctl edit ssh.socket
Add:
[Socket]
ListenStream=
ListenStream=0.0.0.0:yourNumber
ListenStream=[::]:yourNumber
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo ss -tlnp | grep ":yourNumber"
The empty ListenStream= line is mandatory. systemd list directives are additive: without the empty assignment to reset the list, you append yourNumber to the inherited 22 and keep listening on both. Omitting it produces no warning and no error — just a config that looks right and does nothing you wanted.
Alternative: Turn Socket Activation Off
If you would rather have sshd_config be the single source of truth — which you need anyway for ListenAddress or Match blocks — revert to the traditional daemon:
sudo systemctl disable --now ssh.socket sudo systemctl enable ssh.service sudo systemctl daemon-reload sudo systemctl restart ssh.service
If port 22 is still held afterwards, a leftover drop-in is re-enabling socket behaviour. Move it aside and reload:
ls -l /etc/systemd/system/ssh.service.d/ sudo mv /etc/systemd/system/ssh.service.d/00-socket.conf /root/ sudo systemctl daemon-reload && sudo systemctl restart ssh.service
Which Approach?
Drop-in on ssh.socket | Disable socket activation | |
|---|---|---|
| Where the port is configured | systemd unit | sshd_config |
ListenAddress / Match honoured | No | Yes |
| Memory footprint | On demand | Resident (a few MB) |
Survives apt upgrade | Yes | Yes |
| Chance of confusing you again in six months | Higher | Lower |
Don’t Lock Yourself Out
sudo ufw allow yourNumber/tcp
# from a SECOND terminal, with the original session still open:
ssh -p yourNumber user@host
# only once that succeeds:
sudo ufw delete allow 22/tcp
| Item | Note |
|---|---|
| Existing session | Keep the current SSH window open until the new port is verified. |
| Out-of-band access | Confirm you can reach a serial/VNC console before restarting. |
| Cloud firewall | Security groups on AWS/Azure/GCP are a separate allow-list from ufw. |
| Stale port 22 | If it lingers after disable --now, a reboot releases it for good. |
| Never edit | /lib/systemd/system/ssh.socket — overwritten on package update. |
| Rollback | sudo systemctl revert ssh.socket && sudo systemctl restart ssh.socket |
Takeaway
sshd -T tells you what sshd believes. ss -tlnp tells you what the kernel is doing. When the two disagree, look at who owns the file descriptor — that column is where the real answer is. Under socket activation, systemd owns the socket and sshd is merely handed the result.