Linux Tutorials

Change SSH Port on Rocky Linux 10 with SELinux

SELinux on Rocky Linux 10 stopped sshd from listening on port 2222 and on 8080, and let it bind port 33000 with no label at all. That third result is not a policy hole. Ports inside the kernel’s ephemeral range skip the SELinux bind check entirely. So the same steps work on one number and fail on another, and the port you choose matters as much as the commands you type.

Original content from computingforgeeks.com - post 26479

This guide shows how to change SSH port on Rocky Linux 10 with SELinux enforcing and firewalld running. You label the port, listen on both ports, open the firewall and test before you close port 22, so you stay connected the whole time. We ran it on Rocky Linux 10.2 with OpenSSH 9.9p1, selinux-policy 42.1.18 and firewalld 2.4.3 in October 2026, including the failures, and finished with a tested rollback.

Prerequisites

A port change touches three systems at once (sshd, SELinux and firewalld), and getting any one of them wrong cuts off SSH. You want a working fallback before you start, because the failure mode is a server you can no longer reach. Have these in place first.

  • Rocky Linux 10 with SELinux enforcing (getenforce)
  • firewalld running (sudo firewall-cmd --state)
  • sudo access and an SSH session you keep open throughout
  • console access as the last resort

AlmaLinux 10 and RHEL 10 build the same openssh-server and selinux-policy packages, so the commands carry over, but our lab ran Rocky only. SELinux port labels are also a standard RHCSA exam task; if you are working toward it, our list of RHCSA study books for RHEL 10 covers this and the rest of the SELinux objectives. Moving the port is not hardening on its own. It cuts log noise from scanners, while key-only sshd configuration is what actually keeps people out.

Pick a port outside the ephemeral range

Choose a port between 1024 and 32767 that nothing else uses and that SELinux has not already assigned to another service. The upper limit comes from the kernel’s ephemeral port range, which the system hands out as source ports for outgoing connections:

sysctl net.ipv4.ip_local_port_range

On our server that range was 32768 to 60999:

net.ipv4.ip_local_port_range = 32768	60999

We first tried 33000, which sits inside that range, without any SELinux label. sshd bound it on IPv4 and IPv6 and logged no denial, because the kernel skips SELinux’s name_bind check for ports in the ephemeral range. That looks convenient and is a trap. The port is also in the pool the kernel gives to outgoing connections, so a busy server can hand 33000 to a client socket and leave sshd unable to bind it on the next restart. Below 32768 SELinux does check, so the label step matters, and the rest of this guide uses 2222. Set it once for the commands that follow:

export SSH_PORT="2222"

Then confirm nothing listens there and SELinux has no other type on it. Both commands should print nothing, and any output means you pick a different number before going further.

sudo ss -tlnp | grep -w "${SSH_PORT}"
sudo semanage port -l | grep -w "${SSH_PORT}"

Ports with an existing label are the second trap. Port 8080 belongs to http_cache_port_t, so sshd was denied there too. If your chosen port shows a type in the second command, pick another one rather than taking it away from the service that owns it. That grep has a blind spot, though: it only matches ports listed on their own, so a number inside a labelled range (10005 sits in http_cache_port_t‘s 10001-10010) prints nothing and looks free. sudo seinfo --portcon=2222 from the setools-console package (not installed by default, so run sudo dnf install -y setools-console first) resolves ranges properly, and on our server it showed 2222 only inside the generic unreserved_port_t range, which is what you want.

Label the port for SELinux

sshd runs in the sshd_t domain, which may bind ports labelled ssh_port_t (plus the X11 and VNC forwarding port types) but not generic unreserved ports, and a new install labels just port 22 as ssh_port_t. When you skip this step, the failure is quiet. We added Port 2222 and Port 8080 next to Port 22 and restarted sshd. The restart reported success and sshd kept running, but only port 22 was listening, and the journal held the reason.

sshd[7018]: error: Bind to port 8080 on 0.0.0.0 failed: Permission denied.
sshd[7018]: error: Bind to port 8080 on :: failed: Permission denied.
sshd[7018]: error: Bind to port 2222 on 0.0.0.0 failed: Permission denied.
sshd[7018]: error: Bind to port 2222 on :: failed: Permission denied.
sshd[7018]: Server listening on 0.0.0.0 port 22.
sshd[7018]: Server listening on :: port 22.

That silent half-success is why you check listeners, not the exit code of systemctl. Had port 22 not been in the list, sshd would have had nothing to listen on. The matching denial is in the audit log, and tcontext=...unreserved_port_t tells you the port had no SSH label.

sudo ausearch -m avc -ts today -c sshd --input-logs

We added --input-logs because ausearch reads standard input instead of the log when it is not attached to a terminal, and without the flag our scripted run printed <no matches> even though the denials were on disk. You’ll see one record per failed bind, and the one for 2222 looked like this.

type=AVC msg=audit(1792294305.494:800): avc:  denied  { name_bind } for  pid=7018 comm="sshd" src=2222 scontext=system_u:system_r:sshd_t:s0-s0:c0.c1023 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0

Add the label. semanage ships in policycoreutils-python-utils, which our Rocky 10 image already had; install it with sudo dnf install -y policycoreutils-python-utils if the command is missing.

sudo semanage port -a -t ssh_port_t -p tcp "${SSH_PORT}"
sudo semanage port -l | grep -w ssh_port_t

Both ports now carry the SSH label, and the change survives reboots because it is stored in the local policy.

ssh_port_t                     tcp      2222, 22

Older guides say -a fails on a port that already has a type and that you must switch to -m. With this policycoreutils, semanage port -a -t ssh_port_t -p tcp 8080 printed Port tcp/8080 already defined, modifying instead and quietly relabelled the web cache port, so -a no longer protects you from taking a port away from another service. That is one more reason to check the port first.

Listen on both ports first

Add the new port without removing 22, so a mistake later leaves the old door open. Use a drop-in file rather than editing sshd_config, because Rocky’s main file includes /etc/ssh/sshd_config.d/*.conf and updates never touch your own file there.

sudo vim /etc/ssh/sshd_config.d/20-port.conf

List both ports, one Port line each, because sshd listens on every Port it finds. This is the file we ran for the transition.

Port 22
Port 2222

Check the syntax and restart sshd. Your open session is unaffected by the restart, since each logged-in session runs in its own process.

sudo sshd -t
sudo systemctl restart sshd
sudo ss -tlnp | grep sshd

You should see four listeners, two ports on IPv4 and IPv6, all owned by one sshd process. Fewer lines mean a bind failed, so go back to the journal before touching the firewall.

LISTEN 0      128          0.0.0.0:2222      0.0.0.0:*    users:(("sshd",pid=7482,fd=7))
LISTEN 0      128          0.0.0.0:22        0.0.0.0:*    users:(("sshd",pid=7482,fd=9))
LISTEN 0      128             [::]:2222         [::]:*    users:(("sshd",pid=7482,fd=8))
LISTEN 0      128             [::]:22           [::]:*    users:(("sshd",pid=7482,fd=10)) 

The same PID on all four lines confirms one sshd owns both ports. Port 22 stays open on purpose until a login on 2222 has worked end to end.

Open the new port in firewalld

sshd is listening, but firewalld still only allows the ssh service, which means 22/tcp. From the workstation, a connection to 2222 failed exactly as if nothing were listening.

ssh: connect to host 10.0.1.50 port 2222: Connection refused

firewalld rejects the packet rather than dropping it, so you get an error instead of a timeout, but the wording depends on the path. Our workstation reaches the lab through a NAT gateway and saw “Connection refused”. A Linux host on the same network as the server got the rejection as an ICMP “administratively prohibited” message and printed ssh: connect to host 10.0.1.50 port 2222: No route to host, which is the more common sight and, unlike “refused”, points straight at a firewall. Add the port to the permanent configuration and reload, which applies it without dropping existing connections.

sudo firewall-cmd --permanent --add-port="${SSH_PORT}/tcp"
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports

The list should print 2222/tcp. Now open a second terminal on your workstation and log in on the new port. Do not close the original session until this works.

export HOST_IP="10.0.1.50"
ssh -p 2222 "josphat@${HOST_IP}" hostname

Ours returned web01 with exit code 0. If it still fails, the three checks are, in order, ss (is sshd listening?), semanage port -l (is the port labelled?) and firewall-cmd --list-ports (is it open?). Our firewalld guide for Rocky Linux 10 shows how to limit the port to an admin network with a rich rule instead of opening it to everyone.

Close port 22

Only after a successful login on 2222, remove port 22 from sshd and from the firewall. Edit the same drop-in so it holds the new port alone.

sudo vim /etc/ssh/sshd_config.d/20-port.conf

The final file is a single line, and you’d only add more Port lines back for another transition.

Port 2222

A reload is enough for a port change. On our server sshd re-read the file and dropped the port 22 listeners while keeping the same process ID, and the session we ran it from stayed connected. Then take the ssh service out of firewalld.

sudo sshd -t
sudo systemctl reload sshd
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --reload

The finished state shows the label, sshd on 2222 only and the firewall allowing nothing but that port for SSH:

sshd on port 2222 with SELinux label and firewalld rule on Rocky Linux 10

A login attempt on port 22 from the workstation now gets Connection refused, while 2222 keeps working. The ssh_port_t label on 22 stays regardless, because port 22 is defined in the base policy and semanage can’t delete it, so a rollback never has to recreate it.

Point SSH clients at the new port

Typing -p 2222 every time gets old, and scripts break when someone forgets it. Put the port in ~/.ssh/config on each workstation instead.

vim ~/.ssh/config

Add a host block like the one we tested, with your own address and user.

Host web01
    HostName 10.0.1.50
    Port 2222
    User josphat

With the alias in place, our ssh and scp calls to web01 used port 2222 without any flag. Without it, you have to remember the flags differ: ssh takes a lowercase -p, but scp and sftp take an uppercase -P. That mismatch is the most common slip after a port change, and these two worked against our server.

scp -P 2222 file.txt "josphat@${HOST_IP}:/tmp/"
sftp -P 2222 "josphat@${HOST_IP}"

Ansible inventories, backup jobs and monitoring checks that hardcode port 22 need the same update. If you run fail2ban, its sshd jail bans on the port it was told about, so set port = 2222 in the jail; our Fail2ban guide for Rocky Linux 10 shows where.

Revert SSH to port 22

Rolling back is the same three systems in the reverse order, and again the firewall goes first so you never have a port that sshd uses but nobody can reach. We ran this sequence from a session on port 2222, and it stayed connected throughout:

  1. Allow the ssh service in firewalld again.
  2. Delete the 20-port.conf drop-in and reload sshd.
  3. Remove the 2222 firewall rule.
  4. Delete the SELinux label for 2222.

These are the commands, in that order, and you can paste them as one block because each step leaves SSH reachable. The first line sets the port again, because a rollback often happens days later in a new shell where SSH_PORT is no longer defined, and an empty variable makes the firewall and semanage lines fail.

export SSH_PORT="2222"
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload
sudo rm /etc/ssh/sshd_config.d/20-port.conf
sudo sshd -t
sudo systemctl reload sshd
sudo firewall-cmd --permanent --remove-port="${SSH_PORT}/tcp"
sudo firewall-cmd --reload
sudo semanage port -d -t ssh_port_t -p tcp "${SSH_PORT}"

Afterwards sudo ss -tlnp | grep sshd showed sshd back on port 22 only, semanage port -l listed ssh_port_t tcp 22, and the firewall’s services line read cockpit dhcpv6-client ssh again. If any SELinux denial turns up along the way, our SELinux troubleshooting guide explains how to read it.

Keep reading

UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Security UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP Php PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Php PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Upgrade Kali Linux to the Latest Version Kali Linux Upgrade Kali Linux to the Latest Version Best Cybersecurity and Penetration Testing Books for 2026 Books Best Cybersecurity and Penetration Testing Books for 2026 How To Configure  OpenVPN Server on pfSense / OPNsense pfSense How To Configure OpenVPN Server on pfSense / OPNsense

Leave a Comment

Press ESC to close