You changed the SSH port and connections fail
You changed the SSH port to 2222 to avoid automated bot scans. You updated /etc/ssh/sshd_config. You opened the port in firewalld. You restarted the service. The service reports active. You try to connect from another machine and get Connection refused or Connection timed out. You check the logs and see avc: denied. SELinux is blocking sshd from binding to the new port. The firewall lets the packet in, but SELinux stops the process from listening.
This is a common trap. The firewall and SELinux are separate layers. firewalld filters network packets based on IP addresses and ports. SELinux filters process actions based on security labels. Both must allow the connection. If SELinux denies the bind, sshd never creates the listening socket. The connection fails before the firewall even matters.
What SELinux is actually doing
SELinux uses Mandatory Access Control. It labels every object on the system: processes, files, directories, and network ports. Each process runs in a confined domain with specific permissions. The sshd process runs in the sshd_t domain. That domain is only permitted to bind to network ports labeled with the type ssh_port_t.
By default, Fedora labels TCP port 22 with ssh_port_t. Any other port has a different label or no label at all. When you configure sshd to listen on 2222, the daemon attempts to create a socket on that port. The kernel checks the SELinux policy. It sees sshd trying to bind to a port that is not labeled ssh_port_t. The policy denies the action. The kernel logs an AVC denial. sshd fails to bind and may exit or fall back to the default port.
Think of it like a building with access cards. sshd has a card that opens the door to Room 22. Room 2222 is locked. You need to update the access control system to give sshd permission for Room 2222. You do this by labeling port 2222 with the ssh_port_t type.
SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux. Disabling SELinux removes the protection layer entirely. Fixing the policy keeps the protection and allows your specific use case.
Fix the port label
Use semanage to add the port label. This tool updates the SELinux policy store permanently. It writes to a binary database, not a text file. This ensures the change survives reboots and package updates.
Check the current port labels first. This prevents conflicts and shows you what is already defined.
sudo semanage port -l | grep ssh_port_t
# WHY: Lists all ports currently labeled ssh_port_t.
# Look for tcp 22. If your new port is already listed, you don't need to add it.
# The output shows the port number, protocol, and type.
Add the new port to the ssh_port_t type. Replace 2222 with your actual port number.
sudo semanage port -a -t ssh_port_t -p tcp 2222
# WHY: -a adds a new entry to the policy store.
# -t sets the security type to ssh_port_t.
# -p tcp specifies the protocol. SSH uses TCP.
# 2222 is the port number. This command updates the database immediately.
Restart sshd to apply the change. The daemon reads its configuration and binds sockets at startup. SELinux checks the label during the bind call.
sudo systemctl restart sshd
# WHY: Restarts the SSH daemon.
# sshd attempts to bind to the configured ports on startup.
# With the new label, SELinux allows the bind on port 2222.
# If the restart fails, check the journal for errors.
Restart the service after every policy change. The policy applies to new socket binds, not existing connections.
Verify the fix
Confirm the port label was added correctly.
sudo semanage port -l | grep ssh_port_t
# WHY: Verifies the new port appears in the list.
# You should see tcp 22 and tcp 2222 both labeled ssh_port_t.
# If 2222 is missing, the add command failed or was not run.
Check the sshd status and recent logs.
sudo systemctl status sshd
# WHY: Shows the service state and recent log lines.
# Look for "Listening on 0.0.0.0 port 2222".
# If the service is failed, the journal will show the error.
# Run journalctl -u sshd for full logs if needed.
Check the actual listening sockets. This proves sshd bound successfully.
ss -tlnp | grep sshd
# WHY: Lists TCP listening sockets with process names.
# You should see a line with sshd listening on port 2222.
# If the port is not listed, sshd did not bind.
# SELinux likely blocked it, or the config is wrong.
Run ss -tlnp | grep sshd to see the actual listening sockets. If the port isn't there, sshd didn't bind, and SELinux likely blocked it.
Common pitfalls and error messages
If you run the add command and see ValueError: Port tcp 2222 already defined, the port is already in the database with a different type. This happens if you added the port before or if another package claimed it. Use the modify flag instead.
sudo semanage port -m -t ssh_port_t -p tcp 2222
# WHY: -m modifies an existing entry in the policy store.
# It changes the type to ssh_port_t for the existing port.
# This overwrites the old label without throwing an error.
# Use this when the port exists but has the wrong type.
Ensure you specify the correct protocol. SSH uses TCP. If you add the port with UDP, sshd will still be denied because it binds TCP. The label must match the protocol.
sudo semanage port -a -t ssh_port_t -p udp 2222
# WHY: This is wrong for SSH. It labels the port for UDP traffic.
# sshd binds TCP sockets. SELinux will deny the bind.
# Always use -p tcp for SSH ports.
Check the firewall configuration. SELinux allows the bind, but firewalld might still drop incoming packets. Open the port in firewalld if you haven't already.
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload
# WHY: Opens the port in firewalld permanently.
# --reload applies the persistent config to the runtime.
# Without reload, the change only takes effect after reboot.
# Both SELinux and firewalld must allow the connection.
Reload the firewall after every rule change. The runtime config and the persistent config diverge otherwise.
When to use this vs alternatives
Use semanage port -a when you need to add a new port to an existing SELinux type.
Use semanage port -m when the port is already defined but has the wrong type.
Use setsebool -P sshd_forward_ports on when you are using SSH port forwarding and need to allow dynamic port allocation.
Use firewall-cmd --add-port=2222/tcp when you need to open the port in the firewall. SELinux and firewalld are separate layers. Both must allow the traffic.
Use sestatus when you need to verify SELinux is enforcing and not disabled.
Stay away from setenforce 0 for permanent fixes. That disables SELinux globally and leaves the system unprotected.
Use semanage for port changes. Direct edits to policy files get overwritten by package updates and break the system.