Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Configure the OpenSSH server (sshd) for LFCS/RHCSA

By updated

Improve this page

The SSH client is not enough for credible certification preparation. The LFCS objectives explicitly ask you to configure the OpenSSH server and client. This guide covers the server side: changing the sshd configuration, validating it before restarting, and applying minimal hardening without locking yourself out of the server.

What you will learn

  • Locate and change the configuration of sshd
  • Turn on key authentication and reduce password use
  • Prevent direct root login over SSH
  • Check the syntax with sshd -t before a reload
  • Check the effective configuration with sshd -T
  • Diagnose SSH service errors with systemctl and journalctl

In which context?

You configure sshd in very frequent real situations:

  • hardening a new server before production;
  • moving from password access to public key access;
  • forbidding remote login for the root account;
  • opening a dedicated SSH port in a filtered environment;
  • fixing an "SSH refused" incident after a configuration change.

Prerequisites

  • local console access or KVM/IPMI in case of error;
  • a non-root sudo user;
  • an SSH key already in place on the client side.

sshd configuration baseline

The main file is /etc/ssh/sshd_config, but it is not where you should write. Its first useful directive is Include /etc/ssh/sshd_config.d/*.conf, and for most options the first value read wins. A file of that directory that already sets the option cancels what you add at the end of sshd_config. The directives of a hardening baseline, at their first appearance:

  • PasswordAuthentication no: refuses passwords, only accepts keys.
  • PubkeyAuthentication yes: turns on public key authentication.
  • PermitRootLogin no: forbids direct login of the root account.

Put them in a low-numbered drop-in, read before the others, for example /etc/ssh/sshd_config.d/10-hardening.conf. Without changing the port for now:

PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes

The trap is concrete: the AlmaLinux 10 image ships a 25-permitrootlogin.conf containing PermitRootLogin yes. Measured on 2026-09-29: this baseline added at the end of sshd_config gives permitrootlogin yes in sshd -T, the same baseline in 10-hardening.conf gives permitrootlogin no. Only sshd -T, in step 2, shows you the value really applied.

Changing the port is covered separately, further down, because it hides two traps that cut access to the server: on RHEL because of SELinux, on Ubuntu because of socket activation. It is not an "optional" directive you add lightly.

Validation before applying

Step 1: check the syntax

Terminal window
sudo sshd -t

If the command returns nothing, the syntax is valid. Mind what this validation does not say: sshd -t checks the file's grammar, not the service's ability to start. A Port refused by SELinux passes sshd -t without error, then makes the restart fail. Correct syntax is not proof of a service that starts.

Step 2: check the effective configuration

Terminal window
sudo sshd -T | grep -E '^(port|passwordauthentication|permitrootlogin|pubkeyauthentication) '
port 22
permitrootlogin no
pubkeyauthentication yes
passwordauthentication no

sshd -T shows the configuration as sshd understands it. Remember this trap, proven further down: on Ubuntu with socket activation, sshd -T can show port 2222 while the server really listens on 22. What sshd believes and what the system does are two different things.

Step 3: reload the service

Terminal window
sudo systemctl reload sshd
sudo systemctl status sshd --no-pager

Sessions already open survive a reload as well as a restart: both units, ssh.service on Debian and sshd.service on AlmaLinux, carry KillMode=process, and systemd only stops the main daemon. The difference lies elsewhere. On Debian, ExecReload first runs sshd -t again and refuses a broken configuration, the daemon going on with the old one; a restart stops the daemon, then fails at startup, and the server no longer accepts any new connection. On AlmaLinux, reload only sends a HUP signal, without that check: the manual sshd -t remains your only safety net. On Debian and Ubuntu, the service is historically called ssh; recent versions accept the sshd alias. Under Debian 13 "Trixie" (in French), note two changes that confuse audits: OpenSSH moves to 10.0p2, and authentication failures are now logged by a process named sshd-session, not sshd.

Change the SSH port: the two traps that cut access

Moving sshd to a port other than 22 is a common hardening reflex, and the RHCSA assesses its tricky part with the objective "Manage SELinux port labels". But Port 2222 in sshd_config is not enough, and depending on the distribution, applying it naively makes the server unreachable. The two cases below were reproduced in VMs, SELinux in enforcing on one side, socket activation on the other.

On RHEL, Rocky and AlmaLinux: SELinux guards the port

SELinux only allows sshd to listen on ports labelled ssh_port_t, and by default this list only contains 22. Adding Port 2222 and restarting produces exactly this, measured on an AlmaLinux 10 in enforcing:

Terminal window
# sshd -t sees NO problem
sudo sshd -t # (silent)
# but the restart fails
sudo systemctl restart sshd
Job for sshd.service failed because the control process exited with error code.

The log gives the real reason, which sshd -t had kept quiet:

Terminal window
sudo journalctl -u sshd -n 20 --no-pager | grep -i bind
error: Bind to port 2222 on 0.0.0.0 failed: Permission denied.
fatal: Cannot bind any address.

The SSH server is dead, and nothing listens any more. Permission denied on a bind, while the command runs as root, is the signature of an SELinux refusal, not of a Unix permission problem.

The fix is to declare the port to SELinux before touching sshd:

  1. Add the port to the ssh_port_t type. On a minimal install, semanage is missing: it comes from the policycoreutils-python-utils package.

    Terminal window
    sudo dnf install -y policycoreutils-python-utils
    sudo semanage port -a -t ssh_port_t -p tcp 2222
  2. Check that SELinux now knows the port.

    Terminal window
    sudo semanage port -l | grep '^ssh_port_t'
    ssh_port_t tcp 2222, 22
  3. Only now, change the port and restart.

    Terminal window
    echo 'Port 2222' | sudo tee -a /etc/ssh/sshd_config
    sudo sshd -t && sudo systemctl restart sshd
    ss -tlnp | grep :2222
  4. Open the port in the firewall, otherwise it listens but stays unreachable from outside.

    Terminal window
    sudo firewall-cmd --permanent --add-port=2222/tcp
    sudo firewall-cmd --reload

The reflex that avoids all this: add the port, do not replace it

As long as the new port is not proven, keep 22 open. sshd accepts as many Port directives as you want and listens on all of them.

Terminal window
printf 'Port 22\nPort 2222\n' | sudo tee -a /etc/ssh/sshd_config

Measured on AlmaLinux 10.2, both ports answer after a single restart:

LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1223,fd=9))
LISTEN 0 128 0.0.0.0:2222 0.0.0.0:* users:(("sshd",pid=1223,fd=7))

You open a second session on 2222 to check that it goes through, and only then do you remove Port 22. Without this safety net, a semanage or firewall mistake leaves you outside, and you have to go through the console.

On Ubuntu 24.04: the port in sshd_config is ignored

Here the trap is the opposite: nothing crashes, but the change has no effect. Since Ubuntu 22.10, sshd is started by socket activation: a systemd ssh.socket unit opens the port and hands the connection to sshd. The Port of sshd_config is then never read.

The proof, measured on an Ubuntu 24.04 after adding Port 2222 and restarting:

Terminal window
sudo sshd -T | grep '^port' # what sshd believes
ss -tlnp | grep -E ':22 |:2222 ' # what the system does
port 2222
LISTEN 0 4096 0.0.0.0:22 ... users:(("sshd",...),("systemd",pid=1,...))

sshd announces port 2222, but it is 22 that listens, opened by systemd (pid=1). An administrator who trusts sshd -T, closes 22 in the firewall and opens 2222 cuts their own access without any command reporting an error.

The real port is therefore changed on the socket, not in sshd_config:

  1. Override ssh.socket with systemctl edit.

    Terminal window
    sudo systemctl edit ssh.socket
    [Socket]
    ListenStream=
    ListenStream=2222

    The first, empty line clears the inherited value (otherwise the service would listen on both ports), the second sets the new port.

  2. Reload and restart the socket, then check.

    Terminal window
    sudo systemctl daemon-reload
    sudo systemctl restart ssh.socket
    ss -tlnp | grep :2222

Test a configuration without touching the running service

You can validate an sshd configuration in a separate file, without ever restarting the system's service. It is the safest way to practise before the exam or before applying it on a real server.

Terminal window
mkdir -p /tmp/sshd-lab && cd /tmp/sshd-lab
ssh-keygen -t ed25519 -N '' -f ssh_host_ed25519_key
printf '%s\n' \
'ListenAddress 127.0.0.1' \
'HostKey /tmp/sshd-lab/ssh_host_ed25519_key' \
'PidFile /tmp/sshd-lab/sshd.pid' \
'PasswordAuthentication no' \
'PermitRootLogin no' \
'PubkeyAuthentication yes' \
'AuthorizedKeysFile .ssh/authorized_keys' \
'UsePAM no' > sshd_config.test
sshd -t -f /tmp/sshd-lab/sshd_config.test
sshd -T -f /tmp/sshd-lab/sshd_config.test | grep -E '^(passwordauthentication|permitrootlogin|pubkeyauthentication|usepam) '
usepam no
permitrootlogin no
pubkeyauthentication yes
passwordauthentication no

Quick troubleshooting

sshd failures are recognised by their signature: a bind refused as root, an announced port that does not listen, a directive that does not apply. The table links each signature to its cause and to the fix, in the order you meet them. Keep the rescue console within reach: it is what serves when the table comes too late.

SymptomLikely causeFix
sshd -t OK but restart fails, Bind to port ... Permission deniedSELinux forbids the port (RHEL/Rocky/Alma)semanage port -a -t ssh_port_t -p tcp <port> before restarting
Port change has no effect, sshd -T gives the right port but ss shows 22socket activation (Ubuntu 24.04)change the port in ssh.socket, not in sshd_config
Permission denied (publickey) although the key is rightlocked account (usermod -L) on RHELunlock it, or use usermod --expiredate; the behaviour differs from Debian
Connection refusedservice stopped or port closedsystemctl status sshd, check the firewall and the port really open with ss -tlnp
sshd: bad configuration optiontypo in sshd_configfix it then run sshd -t again
sshd -T still prints permitrootlogin yes after adding noa drop-in read before already sets the option (25-permitrootlogin.conf on AlmaLinux 10)write the rule in sshd_config.d/10-*.conf
Disconnected after a change, no access at allblocking directive or port cuttake over through the console; as a last resort, mount the disk and create /.autorelabel

Key points

  • sshd is configured in /etc/ssh/sshd_config.d/, in a low-numbered file: the first value read wins. The hardening baseline is: public key, root forbidden, password turned off.
  • sshd -t validates the syntax, not the startup. A port refused by SELinux passes it without a hitch, then the service refuses to start.
  • Changing the port is not trivial. On RHEL, SELinux only allows 22: you need semanage port -a before restarting. On Ubuntu 24.04, ssh.socket decides the port, not sshd_config.
  • sshd -T says what sshd believes, not what the system does: under socket activation, the two differ. Check the real port with ss -tlnp.
  • Always open the port in the firewall (firewall-cmd on RHEL): SELinux and the service can be right, the firewall still blocks.
  • Keep a session open and a rescue console. Open sessions survive a restart, but a failed restart refuses every new connection; without a console, you must mount the disk from the host.

Knowledge check

Check that the essentials of this guide stuck. The questions cover only what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn