You cannot connect because the server is not listening
You installed Fedora on a new laptop or a headless server. You walk away from the machine, only to realize you left a long download running or you need to check a log file from your phone. You try to connect from another device and get Connection refused. The SSH server is not running, or the firewall is blocking you. This is the standard state for a fresh Fedora Workstation install. Security by default means services stay off until you explicitly enable them. Fedora ships the SSH client so you can connect to other machines, but it does not ship the server package by default. This keeps the attack surface small. You need to install the server, start the daemon, and open the firewall.
What is actually happening
Fedora separates the SSH client from the SSH server. The client package openssh-clients is installed by default. The server package openssh-server contains the sshd daemon, the host key generation scripts, and the configuration files. When you install the server package, you get the binary, but systemd does not start it automatically. You must enable the unit to create the boot symlink and start the service for the current session.
The firewall firewalld runs in the public zone by default. This zone denies all incoming connections except for a small set of trusted services like DHCP and DNS. SSH is not in that set. You must add the ssh service to the zone. SELinux also enforces that sshd only binds to authorized ports. If you change the port in the configuration, SELinux will block the bind unless you update the policy. The chain is strict: package installed, unit enabled, firewall allowed, SELinux permitting. Break any link and the connection fails.
Install and enable the daemon
Run the installation command to pull the server package from the repositories. The transaction also pulls in dependencies like pam modules and polkit rules required for authentication.
sudo dnf install openssh-server -y
# Install the server package. The client is already present on Workstation.
sudo systemctl enable --now sshd
# Enable creates a symlink in /etc/systemd/system so sshd starts on boot.
# The --now flag also starts the service immediately in the current session.
The enable command creates a symlink in /etc/systemd/system/multi-user.target.wants/ pointing to the unit file in /usr/lib/systemd/system/. This tells systemd to start sshd when the system reaches the multi-user target. The --now flag combines enable and start in one call. If you only run enable, the service will not start until the next reboot. If you only run start, the service will stop working after a reboot. Use enable --now to cover both cases.
Check the unit state to confirm the daemon is active. The output shows the main PID, the memory usage, and the most recent log lines.
systemctl status sshd
# Check the unit state. Look for "Active: active (running)" in green text.
# The output also shows the main PID and recent journal entries for the service.
If the status shows inactive (dead), the service failed to start. Run journalctl -xeu sshd to see the error. The -x flag adds explanatory text from the man pages, and the -e flag jumps to the end of the journal. Most sysadmins type this muscle-memory style when a unit misbehaves.
Run systemctl status before you restart. Restarting a broken service just generates more errors. Read the log first.
Open the firewall
firewalld blocks incoming SSH traffic until you add the service to the zone. The firewall maintains two configurations: the runtime configuration that applies immediately, and the permanent configuration that survives reboots. You must update the permanent configuration and then reload to merge the changes into the runtime.
sudo firewall-cmd --permanent --add-service=ssh
# Add the ssh service to the permanent zone configuration.
# This edits the XML zone file on disk so the rule survives reboots.
sudo firewall-cmd --reload
# Reload merges the permanent configuration into the running firewall.
# Without this, the runtime config stays stale and the rule does not apply.
The --permanent flag writes to the zone file in /etc/firewalld/zones/. The --reload command flushes the runtime rules and re-applies the permanent rules. If you skip the reload, the firewall continues to drop SSH packets until you reboot. You will add the rule, test the connection, see it fail, reboot, and wonder why it worked after the reboot. Always reload after a permanent change.
firewall-cmd --reload after every rule change. Runtime and permanent configs diverge otherwise.
Verify the connection
Test the connection from the local machine first. This isolates network issues from service issues. If ssh localhost works, the daemon is listening and the firewall allows loopback traffic. If it fails, the problem is local.
ssh localhost
# Test the connection locally. This bypasses external network issues.
# You will be prompted for the password. Authentication succeeds if the service is healthy.
If you see ssh: connect to host localhost port 22: Connection refused, the daemon is not listening on port 22. Check systemctl status sshd and ss -tlnp | grep sshd. If you see ssh: connect to host localhost port 22: Connection timed out, the firewall is dropping packets. Check sudo firewall-cmd --list-services.
Use ss to verify the socket is bound. The output shows the local address and port, the state, and the process name.
ss -tlnp | grep sshd
# Verify the socket is listening on port 22 and bound to the correct interface.
# The output should show 0.0.0.0:22 or *:22 in the LISTEN state.
If ss shows nothing, sshd is not running. If ss shows 127.0.0.1:22, the daemon is only listening on loopback. Check /etc/ssh/sshd_config for the ListenAddress directive.
Run ssh localhost before you walk away. A silent failure in the config file can leave you locked out.
Common pitfalls and SELinux
Changing the SSH port is a common request. You edit /etc/ssh/sshd_config and set Port 2222. You restart the service. The connection still fails. The daemon starts, but SELinux blocks the bind because port 2222 is not in the allowed list for ssh_port_t. The error is subtle. sshd might not log a clear error. You see nothing in the SSH log, but journalctl -t setroubleshoot shows a one-line summary of the denial.
setroubleshoot: SELinux is preventing sshd from name_bind access on the tcp_socket port 2222.
Read the setroubleshoot output before disabling SELinux. The message tells you exactly what is blocked and how to fix it. You must add the port to the SELinux policy using semanage.
sudo semanage port -a -t ssh_port_t -p tcp 2222
# Add the custom port to the SELinux policy for the ssh_port_t type.
# This allows sshd to bind to port 2222 without triggering an AVC denial.
sudo firewall-cmd --permanent --add-port=2222/tcp
# Allow the custom port in the firewall. Services use names, ports use numbers.
sudo firewall-cmd --reload
# Reload applies the new firewall rule to the running configuration.
The semanage port command updates the local SELinux policy store. The firewall-cmd command adds the port to the firewall. Both are required. If you only update SELinux, the firewall drops the packets. If you only update the firewall, SELinux blocks the bind.
Edit /etc/ssh/sshd_config for all customizations. Never edit files in /usr/lib/ssh/. The files in /etc/ are user-modified and persist across updates. The files in /usr/lib/ ship with the package and get overwritten on upgrade. Fedora follows the convention that /etc/ holds local configuration and /usr/lib/ holds vendor defaults.
Check the configuration syntax before restarting. A typo in sshd_config can prevent the daemon from starting.
sudo sshd -t
# Validate the sshd_config syntax. This command checks for errors without starting the daemon.
# It returns no output if the config is valid. Errors are printed to stderr.
Trust the package manager. Manual file edits drift, snapshots stay.
When to use this vs alternatives
Use the default port 22 when you want zero configuration friction and standard tooling compatibility. Use a custom port when you are exposed to the public internet and want to reduce automated noise in your logs. Use key-based authentication when you value security and convenience over typing passwords. Use password authentication only when you cannot distribute keys to the client machines. Use firewalld zones when you need to apply different rules to different network interfaces. Use systemctl enable --now when you want the service to start immediately and persist across reboots.