Build, install, and run the latest OpenSSH Server as a systemd service.
Running the latest OpenSSH Server is easy and security-wise. This can run side-by-side with the package-manager installed version of OpenSSH (this is recommended).
This is for a Debian-derived Linux System using systemd. This document will use version 9.0p1.
OpenSSH will be built on the system that will also run the ssh service.
Commands are presumed to run as user root.
Perform these steps once on the system.
mkdir -vp /opt/
Debian 12 may require
mkdir -vp /var/empty
List the dpkg requirements for OpenSSH server
apt show openssh-server
Most likely, the packages listed under Depends: will be needed to build OpenSSH.
My typical Debian system addtionally needed these
apt install \
libssl-dev \
gcc g++ gdb cpp \
make cmake \
libtool \
libc6 \
autoconf automake pkg-config \
build-essential \
gettext \
(I am not sure if all of these packages are needed, but it did the trick)
I suspect you also need these
apt install \
libzstd1 zlib1g \
libssh-4 libssh-dev libssl3 \
libc6-dev libc6 \
libcrypt-dev
These guesses have not been thoroughly tested.
Other helpful tools for debugging server issues are:
apt install netcat lsof wget diffutils
-
Download the latest archive.
This will example will use openssh-9.0p1.tar.gz.VER=9.0p1 wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-${VER}.tar.gz
wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/RELEASE_KEY.asc
gpg --import RELEASE_KEY.asc
wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-${VER}.tar.gz.asc
gpg --verbose --verify openssh-${VER}.tar.gz.asc
Preview the next subsection before running these commands.
VER=9.0p1
tar -xvf openssh-${VER}.tar.gz
cd openssh-${VER}
./configure --prefix=/opt/openssh-${VER}
make
make install
If ./configure is missing then the non-portable version was downloaded.
This section has not yet successfully tested 😔. Come back later.
Change string that all connecting SSH clients receive (this occurs before authentication).
This requires a change before running make from the previous Build section.
The statement that writes the "banner" string to the connected SSH channel is in file ./ssh_api.c.
if ((r = sshbuf_putf(banner, "SSH-2.0-%.100s\r\n", SSH_VERSION)) != 0)
Before running make, change the file ./ssh_api.c
sed -i -Ee 's|(sshbuf_putf\(banner, )("SSH-.*", SSH_VERSION)(\))|\1"please_go_away"\3|' -- ./ssh_api.c
This should change the statement to
if ((r = sshbuf_putf(banner, "please_go_away")) != 0)
rm -vf /opt/openssh-latest /etc/ssh-latest
ln -fvs /opt/openssh-${VER} /opt/openssh-latest
ln -fvs /opt/openssh-latest /etc/ssh-latest
Very Important! Keep the functioning built-in sshd service in-place or running at a different port.
- Change the system built-in SSH Service
/etc/ssh/sshd_config, add a non-typical port to thePortsdeclaration, for example2222. - Restart the system built-in SSH Service.
- Login to the SSH service listening using the non-typical port
2222.ssh -p 2222 user@server - Remove from the
Portsdeclaration the typical port22. - Restart the system built-in SSH Service.
OR
- Have the latest SSH service use a non-standard port(s), like
2222.
Now there is a reliable fallback SSH Service.
Copy the systemd template service files for the ssh service.
This path may vary among /lib/systemd/system, or /usr/lib/systemd/system, or something else.
Be sure not to use the generated systemd files typically found at /etc/systemd.
Find all *ssh* files
find / -xdev -name '*ssh*' 2>/dev/null | sort
Copy the systemd service files. In my case on Debian 11
cd /usr/lib/systemd/system/
cp -av ssh.service ssh-latest.service
cp -av [email protected] [email protected]
cp -av ssh.socket ssh-latest.socket
cp -av rescue-ssh.target rescue-ssh-latest.target
MMV: the path to systemd files tends to change often per distribution and and per release.
In the newly copied ssh-latest* systemd files, manually update references from ssh to ssh-latest.
In my case on Debian 11, the changes looked like:
$ cd /usr/lib/systemd/system
$ diff -y --suppress-common-lines [email protected] [email protected]
EnvironmentFile=-/opt/openssh-latest/default/ssh | EnvironmentFile=-/etc/default/ssh
ExecStart=/opt/openssh-latest/sbin/sshd -i $SSHD_OPTS | ExecStart=-/usr/sbin/sshd -i $SSHD_OPTS
RuntimeDirectory=sshd-latest | RuntimeDirectory=sshd
$ diff -y --suppress-common-lines ssh-latest.service ssh.service
ConditionPathExists=!/opt/openssh-latest/etc/sshd_not_to_be_r | ConditionPathExists=!/etc/ssh/sshd_not_to_be_run
EnvironmentFile=-/opt/openssh-latest/default/ssh | EnvironmentFile=-/etc/default/ssh
ExecStartPre=/opt/openssh-latest/sbin/sshd -t | ExecStartPre=/usr/sbin/sshd -t
ExecStart=/opt/openssh-latest/sbin/sshd -D $SSHD_OPTS | ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/opt/openssh-latest/sbin/sshd -t | ExecReload=/usr/sbin/sshd -t
Type=exec | Type=notify
RuntimeDirectory=sshd-latest | RuntimeDirectory=sshd
Alias=sshd-latest.service | Alias=sshd.service
$ diff -y --suppress-common-lines ssh-latest.socket ssh.socket
Before=ssh-latest.service | Before=ssh.service
Conflicts=ssh-latest.service | Conflicts=ssh.service
ConditionPathExists=!/opt/openssh-latest/etc/sshd_not_to_be_r | ConditionPathExists=!/etc/ssh/sshd_not_to_be_run
ListenStream=2222 | ListenStream=22
$ diff -y --suppress-common-lines rescue-ssh.target rescue-ssh-latest.target
Requires=network-online.target ssh.service | Requires=network-online.target ssh-latest.service
After=network-online.target ssh.service | After=network-online.target ssh-latest.service
Notice the change of Type from notify to exec.
Enable and reload the new services
systemctl enable ssh-latest.service
systemctl enable ssh-latest.socket
systemctl daemon-reload
mkdir -vp /opt/openssh-latest/default/
cp -av /etc/default/ssh /opt/openssh-latest/default/
The /opt/openssh-latest/default/ssh file sets the $SSHD_OPTS which is used by the service file.
Go to section Restart Service to start the new service.
Perform these steps on the system to update your latest installation. You must have already completed the Do this once section.
-
Download the latest archive.
This will example will use openssh-9.0p1.tar.gz.VER=9.0p1 wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-${VER}.tar.gz
wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-${VER}.tar.gz.asc
gpg --verbose --verify openssh-${VER}.tar.gz.asc
Preview the next subsection before running these commands.
VER=9.0p1
tar -xvf openssh-${VER}.tar.gz
cd openssh-${VER}
./configure --prefix=/opt/openssh-${VER}
make
make install
If ./configure is missing then the non-portable version was downloaded.
Set PREV to the appropriate value. In my case it was 8.6p1.
cd /opt/openssh-${VER}/etc/
# set aside default config files
mkdir -vp _original
mv -v ssh_host_* sshd_config ssh_config _original/
PREV=8.6p1
cp -av /opt/openssh-${PREV}/etc/ssh_host_* /opt/openssh-${PREV}/etc/{sshd_config,ssh_config} .
The files in /opt/openssh-latest/etc/ should be similar to:
moduli
_original/
ssh_config
sshd_config
ssh_host_dsa_key
ssh_host_dsa_key.pub
ssh_host_ecdsa_key
ssh_host_ecdsa_key.pub
ssh_host_ed25519_key
ssh_host_ed25519_key.pub
ssh_host_rsa_key
ssh_host_rsa_key.pub
sed -i "s/\/opt\/openssh-${PREV}\//\/opt\/openssh-${VER}\//g" -- sshd_config
Check for the version substrings
grep -Fe "${VER}" sshd_config
grep -Fe "${PREV}" sshd_config
File ssh_config should not need changes but you can check.
Optionally, the /opt/openssh-latest/etc/sshd_config can refer to the key files at /etc/ssh. Then there is no need to copy the /opt/openssh-latest/etc/ssh_host_* files.
In that case, the prior file listing of /opt/openssh-latest/etc/ should be similar to:
moduli
_original/
ssh_config
sshd_config
The file /opt/openssh-latest/etc/sshd_config should have lines
$ grep -Fe 'HostKey' /opt/openssh-${VER}/etc/sshd_config
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
Review the copied sshd_config with the original default sshd_config.
cd /opt/openssh-${VER}/etc/
diff -y -W $COLUMNS sshd_config _original/sshd_config | less -SR
systemctl stop ssh-latest.socket
systemctl stop ssh-latest
cd /opt
rm -v openssh-latest
ln -fvs openssh-${VER} openssh-latest
The directory /opt should look like
openssh-8.6p1/
openssh-9.0p1/
openssh-latest -> openssh-9.0p1
First, watch the service logs
journalctl -f -x -u ssh-latest
Then restart
systemctl restart ssh-latest.socket
systemctl restart ssh-latest
Check services status
systemctl status ssh-latest
For each port with a listening ssh service
echo | nc localhost 22
echo | nc localhost 2222
Check the service replies with the expected latest openssh version. For each port with a listening ssh service
echo | nc localhost 22
echo | nc localhost 2222
This should look like:
$ echo | nc localhost 2222
SSH-2.0-OpenSSH_9.0
Invalid SSH identification string.
Check there are two different sshd -D daemon processes
ps -ef --forest | grep "sshd -D"
Check the ports in-use
lsof -PVn -iTCP | grep sshd | grep LISTEN
I think the symlink at
/opt/openssh-latestwas not yet created.