In case you want to change the default SSH port 22 on your system and you are running into the following error:

Error: Bind to port failed: Permission denied


You will probably using a Linux distribution on which SELinux (Security-Enhanced Linux) is enabled.

Security-Enhanced Linux (SELinux) is a Linux kernel security module that provides a mechanism for supporting access control security policies, including mandatory access controls (MAC).

SELinux is a set of kernel modifications and user-space tools that have been added to various Linux distributions. Its architecture strives to separate enforcement of security decisions from the security policy, and streamlines the amount of software involved with security policy enforcement. The key concepts underlying SELinux can be traced to several earlier projects by the United States National Security Agency (NSA).

SELinux is available since 2005 as part of Red Hat Enterprise Linux (RHEL) version 4 and all future releases. 

Source: https://en.wikipedia.org/wiki/Security-Enhanced_Linux


Common Linux distributions that by default had enabled SELinux are Red Hat Enterprise Linux (RHEL) as used below for this post, CentOS, AlmaLinux, Rocky Linux, Fedora, Oracle Linux and Amazon Linux 2.

Available but not enabled by default are Debian, Ubuntu, openSUSE, SUSE Linux Enterprise Server, Gentoo and Arch Linux.

Ubuntu, openSUSE and Debian by default using SELinux’s counterpart AppAmor.


To see if SELinux is enabled on your system you can use the sestatus command.

# sestatus

or for verbose output
# sestatus -v


As mentioned for this post I will use Red Hat Enterprise Linux 9 (RHEL). Here you will already see a notification within the /etc/ssh/sshd_config file that if we want to change the SSH port, we first need to tell SELinux about this change. There is also shown the command we need to execute to add a custom port (adjusting the policy) for the sshd daemon and SELinux.


Otherwise we will run into the error below when trying to restart the sshd daemon.


By checking the logs using the journalctl -xeu sshd.service command we will get more details about the problem. In our case because SELinux is enabled here we first need to adjust the so called targeted policy to allow a different SSH port than 22.


We can first check the actual targeted policy for our SSH port by executing:

semanage port -l | grep ssh

When running a service on a custom port, you must change the configuration file for the service and also add an SELinux port definition. Without the port definition, the service will fail to start and log an error similar to “cannot bind to port”.

Source: https://www.redhat.com/sysadmin/semanage-keep-selinux-enforcing


Adjust the port definition to also allow tcp port 2222 will be used by the sshd daemon with:

# semanage port -a -t ssh_port_t -p tcp 2222

 -a, --add -> Add a record of the specified object type
 -t TYPE, --type TYPE -> SELinux type for the object


In case you want to remove the default SSH port by using the -d flag for this policy you will run into the following error:

# semanage port -d -t ssh_port_t -p tcp 22

ValueError: Port tcp/22 is defined in policy, cannot be deleted

Already default defined policies can’t be deleted, therefore you have to create your own policy.


Another option to bind a custom port for our sshd daemon is either to change the mode which SELinux is running or by completely disabling SELinux.

SELinux can run in one of two modes: enforcing or permissive.

Disabling or running SELinux in permissive mode is not recommended!
When SELinux is disabled or running in permissive mode, SELinux policy is not enforced.

Read also first the following article Benefits of running SELinux https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/8/html/using_selinux/getting-started-with-selinux_using-selinux#benefits-of-selinux_getting-started-with-selinux

# vi /etc/selinux/config

To change the mode from enforcing to permissive, open the vi /etc/selinux/config file and change the SELINUX value from enforcing to permissive.

Finally we need to reboot the system.

After the system restarts, confirm that the getenforce command returns Permissive.


To disable SELinux in RHEL 8 you can still use the deprecated method by also adjusting the /etc/selinux/config file. Here you just need to change the SELINUX value to disabled and reboot the system.

In RHEL 9 we need to configure the boot loader to disable SELinux. Therefore we can use the grubby tool

grubby is a command line tool for updating and displaying information about the configuration files for various architecture specific bootloaders. It is primarily designed to be used from scripts which install new kernels and need to find information about the current boot environment.

Source: https://www.linux.org/docs/man8/grubby.html


Configure your boot loader to add selinux=0 to the kernel command line:

# grubby --update-kernel ALL --args selinux=0

Finally reboot the system.

After the reboot, confirm that the getenforce command returns Disabled.


To get information about the current state of SELinux, we can use either getenforce or sestatus.





Links

What is SELinux (Security-Enhanced Linux)?
https://www.redhat.com/en/topics/linux/what-is-selinux

Four semanage commands to keep SELinux in enforcing mode
https://www.redhat.com/sysadmin/semanage-keep-selinux-enforcing

Changing SELinux states and modes
https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/8/html/using_selinux/changing-selinux-states-and-modes_using-selinux

Getting started with SELinux
https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/8/html/using_selinux/getting-started-with-selinux_using-selinux

Configuring SELinux
https://documentation.suse.com/sles/15-SP5/html/SLES-all/cha-selinux.html

Making persistent changes to the GRUB boot loader
https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/8/html/managing_monitoring_and_updating_the_kernel/assembly_making-persistent-changes-to-the-grub-boot-loader_managing-monitoring-and-updating-the-kernel