HardStacked
Share:

OpenSSH · systemd

"Failed to restart sshd.service: Unit sshd.service not found" on Ubuntu 24.04 and Debian 13

Published · HardStacked · 11 min

You edited /etc/ssh/sshd_config, probably PasswordAuthentication yes, and you want the change applied. Tutorials written for Red Hat, Fedora or Arch say sudo systemctl restart sshd. You type it and get Failed to restart sshd.service: Unit sshd.service not found. Meanwhile SSH works. You may be typing this inside an SSH session right now.

Someone asked it on askubuntu in 2024 (question 1523856, 2024-08-19, 16,309 views, no accepted answer), in one sentence:

"i tried to restart the daemon using sudo systemctl restart sshd and i keep getting this error"

The only answer (score 8, not accepted) says restarting is no longer needed at all, because systemd would start a fresh sshd for each connection. Section 07 shows why we don't rely on that. And nobody says why the name sshd exists on some Debian and Ubuntu machines and not on yours, or why it worked before an upgrade.

01. The two-line verdict

On Debian and Ubuntu the service is called ssh.service. sshd.service is not a service, it is a nickname: a symlink on disk that systemctl enable ssh creates and systemctl disable ssh removes. The service can run fine without its nickname. And on Ubuntu, according to the package's own install script, SSH starts through a socket: a fresh install never enables ssh.service, and the upgrade to the 24.04 package disables it. The nickname vanishes while SSH keeps answering.

The error message says "the unit doesn't exist". The truth is "the unit exists under another name, and the link for this one isn't there". That is the lying light of this article.

Bench numbers below were measured on 2026-10-17 on a Raspberry Pi 5, Debian 13 trixie, systemd 257 (257.13-1~deb13u1). The Ubuntu parts come from reading the Ubuntu 24.04 package, not from running it. We say which is which as we go.

02. The bench: a throwaway unit with a nickname

To see the mechanism without touching your real sshd, the bench uses a throwaway user unit, demo-ssh.service, standing in for ssh.service, with Alias=demo-sshd.service standing in for sshd.service. It runs with systemctl --user, no sudo. The user and the system instances share the install code; our article on "Cannot alias" read it in src/shared/install.c, and we did not run this specific bench as root. Paths were filtered at capture time ($HOME becomes ~), nothing was edited afterwards. Setup:

These commands are provided as is, without warranty. Test on a non critical machine or a snapshot first.

bench setup, user instance
D=~/.config/systemd/user; mkdir -p "$D" cat > "$D/demo-ssh.service" <<'U' [Unit] Description=demo stand-in for ssh.service [Service] ExecStart=/bin/sleep infinity [Install] WantedBy=default.target Alias=demo-sshd.service U r(){ echo "\$ $*" | sed "s#$HOME#~#g"; "$@" 2>&1 | sed "s#$HOME#~#g"; echo "[exit ${PIPESTATUS[0]}]"; } systemctl --user daemon-reload

Case A: the unit is started, never enabled.

case A: started, never enabled
$ systemctl --user start demo-ssh.service
[exit 0]
$ systemctl --user is-active demo-ssh.service
active [exit 0]
$ systemctl --user is-enabled demo-sshd.service
not-found [exit 4]
$ systemctl --user restart demo-sshd.service
Failed to restart demo-sshd.service: Unit demo-sshd.service not found. [exit 5]

The service is active. Its nickname is not-found, and restarting through the nickname gives the exact shape of the error from the title. Case B: enable.

case B: enable
$ systemctl --user enable demo-ssh.service
Created symlink '~/.config/systemd/user/demo-sshd.service' → '~/.config/systemd/user/demo-ssh.service'. Created symlink '~/.config/systemd/user/default.target.wants/demo-ssh.service' → '~/.config/systemd/user/demo-ssh.service'. [exit 0]
$ env LC_ALL=C ls -go ~/.config/systemd/user/demo-sshd.service
lrwxrwxrwx 1 50 Oct 17 13:04 ~/.config/systemd/user/demo-sshd.service -> ~/.config/systemd/user/demo-ssh.service [exit 0]
$ systemctl --user is-enabled demo-sshd.service
alias [exit 0]
$ systemctl --user restart demo-sshd.service
[exit 0]
$ systemctl --user is-active demo-sshd.service
active [exit 0]

enable creates two links, the first being the nickname, and the nickname works at once. The ls -go line is the nickname itself: a symlink, nothing more.

Case C is the one that matters. Same unit, now disabled while it is still running:

case C: disable while it runs
$ systemctl --user disable demo-ssh.service
Removed '~/.config/systemd/user/default.target.wants/demo-ssh.service'. Removed '~/.config/systemd/user/demo-sshd.service'. [exit 0]
$ systemctl --user is-active demo-ssh.service
active [exit 0]
$ systemctl --user is-enabled demo-sshd.service
not-found [exit 4]
$ systemctl --user restart demo-sshd.service
Failed to restart demo-sshd.service: Unit demo-sshd.service not found. [exit 5]

disable removed the nickname link. The service is still active. Its nickname is not-found, exit 4, and restart through it fails with exit 5. Nothing is broken. The nickname was only ever a link that enable placed and disable takes away.

03. Why the nickname lives in [Install]

Alias=sshd.service sits in the [Install] section of ssh.service. Everything in [Install] acts only when you run enable (or disable to undo it). The running service never reads it. We extracted the real units from the Ubuntu 24.04 package (openssh-server 1:9.6p1-3ubuntu13, arm64, read on 2026-10-17, nothing installed) and from Debian 13 (1:10.0p1-7+deb13u4). Both files have exactly the same [Install] section:

ssh.service, same on Ubuntu 24.04 and Debian 13
[Install] WantedBy=multi-user.target Alias=sshd.service

So the alias is identical on both. The difference is whether anything ever runs enable on ssh.service. Same mechanism as the Cannot alias article: the alias is a link placed at enable time, not a property of the running unit.

04. Ubuntu: a fresh install never enables ssh.service

Ubuntu starts SSH by socket. The official announcement, "SSHd now uses socket-based activation (Ubuntu 22.10 and later)" on discourse.ubuntu.com, says the socket is the default since openssh-server 1:9.0p1-1ubuntu1. The package's ssh.socket has this [Install]:

ssh.socket, Ubuntu 24.04
[Install] WantedBy=sockets.target RequiredBy=ssh.service

Now the maintainer script of the 24.04 package. These are the real lines, two separate blocks. First the socket (lines 183 to 192):

postinst, Ubuntu 24.04, lines 183 to 192
# was-enabled defaults to true, so new installations run enable. if deb-systemd-helper --quiet was-enabled 'ssh.socket'; then # Enables the unit on first installation, creates new # symlinks on upgrades if the unit file has changed. deb-systemd-helper enable 'ssh.socket' >/dev/null || true else # Update the statefile to add new symlinks (if any), which need to be # cleaned up on purge. Also remove old symlinks. deb-systemd-helper update-state 'ssh.socket' >/dev/null || true fi

Then the service (lines 197 to 205):

postinst, Ubuntu 24.04, lines 197 to 205
if deb-systemd-helper debian-installed 'ssh.service'; then # The following line should be removed in trixie or trixie+1 deb-systemd-helper unmask 'ssh.service' >/dev/null || true if deb-systemd-helper --quiet was-enabled 'ssh.service'; then # Create new symlinks, if any. deb-systemd-helper enable 'ssh.service' >/dev/null || true fi fi

What the script says: ssh.socket is enabled on a new installation, because was-enabled defaults to true. ssh.service is only enabled if deb-systemd-helper debian-installed 'ssh.service', meaning the service must already be known to dpkg. On a fresh machine it isn't. So ssh.service never gets enabled, the sshd.service link is never created, and restart sshd returns not found. That second step, what debian-installed means on a new machine, is our reading of the code. We did not run this on a real Ubuntu. Take it as "what the package script says, read on 2026-10-17", not as something we measured.

05. Ubuntu: the upgrade removes the nickname from a machine where it worked

This is the "it worked yesterday" case. In the same script (line 133, then 143 to 159; lines 134 to 142 only clean up old drop-in files and are cut here), when upgrading from a version older than 1:9.6p1-3ubuntu3:

postinst, Ubuntu 24.04, line 133 then 143 to 159
if dpkg --compare-versions "$2" lt-nl 1:9.6p1-3ubuntu3~; then if [ -d /run/systemd/system ]; then if dpkg --compare-versions "$2" ge 1:9.3p1-1ubuntu3~ && ! systemctl --quiet is-enabled ssh.socket; then # If we are upgrading from a version that was # already migrated, but ssh.socket is disabled, # then leave it that way. : else # Make sure ssh.service is disabled. systemctl unmask ssh.service systemctl disable --now ssh.service > /dev/null 2>&1 # sshd-socket-generator is invoked on daemon-reload. systemctl daemon-reload systemctl enable ssh.socket fi fi fi

Read it as written: unless the machine was already migrated with the socket deliberately disabled (the first branch, which leaves things alone), the script runs systemctl disable --now ssh.service, then systemctl enable ssh.socket. From case C of the bench, you know what disable does to the nickname: it removes the link. So sshd.service was a valid name on your machine before the upgrade, and after it, it is not. SSH still answers because the socket is listening. Again: read in the package, not run on a real Ubuntu upgrade.

06. Debian 13: the nickname exists as long as ssh.service is enabled

Debian is the normal case. Real output from a Pi 5 running Debian 13, read-only, on 2026-10-17, masked:

a Pi 5 on Debian 13, read-only
OS: Debian GNU/Linux 13 (trixie) openssh-server 1:10.0p1-7+deb13u4
$ systemctl is-enabled ssh.service
enabled
$ systemctl is-enabled ssh.socket
disabled
$ systemctl is-enabled sshd.service
alias
$ ls -go /etc/systemd/system/sshd.service
lrwxrwxrwx 1 35 Jul 26 20:40 /etc/systemd/system/sshd.service -> /usr/lib/systemd/system/ssh.service

ssh.service is enabled, ssh.socket is disabled, and is-enabled sshd.service answers alias. The link in /etc/systemd/system points to ssh.service. This is a different machine from the bench, same mechanism as case B. The day someone disables ssh.service or moves it to the socket, this machine would give the same error as the title. That part is the bench's case C, not something we did on this Pi.

07. Diagnose in three commands, then fix

On your machine, system instance, so no --user.

  1. systemctl is-enabled sshd.service. alias means the nickname is in place. not-found means it isn't. Both words appear in the outputs above.
  2. systemctl is-enabled ssh.service ssh.socket. This tells you who starts SSH: the service, the socket, or both.
  3. ls -l /etc/systemd/system/sshd.service. The link itself, present on the real Debian above (we used ls -go, which drops the owner and group columns).

Do you even need to restart? The only answer on askubuntu says no: systemd would start a fresh sshd for each connection, so the next login picks up the new config. The socket Ubuntu ships says otherwise. It contains Accept=no, and systemd's manual defines that as "only one service unit is spawned for all connections". So from the first connection on, one long-running sshd, the same ssh.service, holds the socket. Whether it picks up your edit for the next login is not something we measured, so we don't rely on it.

The fix is to use the real name: sudo systemctl reload ssh. In both the Ubuntu 24.04 and Debian 13 packages, ssh.service defines reload as two commands, /usr/sbin/sshd -t then /bin/kill -HUP $MAINPID. man sshd says -t is "Test mode. Only check the validity of the configuration file and sanity of the keys", and that sshd "rereads its configuration file when it receives a hangup signal". systemd runs those commands in order and, per its manual, if one fails "other lines are not executed". So a broken sshd_config stops the reload before the signal is sent. sudo systemctl restart ssh also works, but reload checks first. What -t cannot catch is a valid setting that shuts you out, like password login turned off before your key is installed. If you are connected through SSH, keep that session open and test a second login before closing it; see the lockout article.

If you really want the sshd name back on Ubuntu, Ubuntu's announcement gives the way back to the old behavior: systemctl disable --now ssh.socket, remove /etc/systemd/system/ssh.service.d/00-socket.conf and /etc/systemd/system/ssh.socket.d/addresses.conf, systemctl daemon-reload, then systemctl enable --now ssh.service. That last enable is what would recreate the sshd.service link (case B of the bench). We did not run that sequence, so take it as Ubuntu's documented path, not as something we tested.

08. The reverse trap: scripts written for another distribution

Scripts, Ansible roles and tutorials written for Red Hat, Fedora or Arch use sshd because there the unit really is sshd.service. Copy that onto Ubuntu 24.04 and it fails on this exact message. Cheat sheet: RHEL sshd, Debian and Ubuntu ssh. If you maintain a role for both families, use the name per family instead of assuming one.

09. The neighbor: changing the port on Ubuntu

On Ubuntu in socket mode, Port and ListenAddress from sshd_config are read by a generator, sshd-socket-generator, shipped in the package. Strings extracted from the binary:

strings from sshd-socket-generator, Ubuntu 24.04
# Automatically generated by sshd-socket-generator ListenStream= ListenStream=%s

It writes ssh.socket.d/addresses.conf. Ubuntu's announcement says the same for 24.04: "the port and address settings are pulled dynamically from sshd.conf via a systemd generator". So changing the port likely means systemctl daemon-reload and a restart of ssh.socket, not only a reload of the service. That last step is inferred from the package and the announcement, not measured. The port story has its own article: changing the SSH port.

10. What was and wasn't tested

Measured: Debian 13 trixie, systemd 257, the bench plus one real machine. Read without running: the Ubuntu 24.04 package 1:9.6p1-3ubuntu13. Not measured: a real Ubuntu, Ubuntu 22.10 or 23.x, Red Hat.

The same shape runs through the rest of this series, where the system declares one thing and does another: a firewall the systemd unit reports as enabled while it is switched off, a WireGuard service that reports active with no interface at all, and a timedatectl that shows NTP service: active while the clock never got the time.

Share: