Error Medic

SSH Connection Refused: Complete Troubleshooting Guide for Linux

Fix 'ssh: connect to host port 22: Connection refused' fast. Covers sshd daemon, firewalls, port config, permission denied, slow SSH, and full lockout recovery.

Last updated:
Last verified:
3,164 words
Key Takeaways
  • Root cause: sshd daemon is not running — fix with `sudo systemctl start sshd` and verify with `sudo systemctl status sshd`
  • Root cause: a firewall (ufw, iptables, nftables, or cloud security group) is blocking TCP port 22 — most common on fresh cloud instances
  • Root cause: SSH has been moved to a non-default port — identify with `sudo ss -tlnp | grep sshd` before assuming the daemon is down
  • Root cause: incorrect permissions on ~/.ssh or ~/.ssh/authorized_keys silently reject public key authentication even when sshd is reachable
  • Quick fix sequence: confirm sshd is running, verify the listening port, check firewall rules, then validate key permissions — in that order
SSH Fix Approaches Compared
MethodWhen to UseTimeRisk
Restart sshdDaemon crashed or not running; ps aux shows no sshd process< 1 minLow — non-destructive; active sessions survive a reload
Fix OS firewall (ufw/iptables/firewalld)nc -zv host 22 is refused or times out; sshd is running on the host2-5 minMedium — an incorrect rule can deepen the lockout; keep a console session open
Correct the SSH listening portSSH was moved off port 22; connection refused only on port 222-3 minLow — read-only check first, then update client config or ~/.ssh/config
Fix ~/.ssh permissionsGets past TCP connect but fails with Permission denied (publickey)< 2 minLow — chmod changes are easily reversible
Reinstall openssh-serversshd segfaults or crashes immediately; logs show library or binary errors5-10 minLow — package reinstall preserves existing sshd_config
Update cloud security groupSSH works from VPN or local but refused from external IPs2-5 minMedium — changes affect all instances using that security group

Understanding the SSH "Connection Refused" Error

When you run ssh user@hostname and see:

ssh: connect to host hostname port 22: Connection refused

the TCP three-way handshake reached the remote host, but the operating system sent back a TCP RST (reset) packet. This means:

  1. The host is reachable on the network.
  2. Nothing is listening on that port, or the kernel explicitly rejected the connection.

This is fundamentally different from a timeout (ssh: connect to host hostname port 22: Connection timed out), which means packets never received a response — typically caused by a firewall silently dropping them (using DROP rather than REJECT), a routing failure, or the host being offline. A refused connection is almost instant; a timeout takes 20–120 seconds.

Error Message Reference

Error Root Cause
Connection refused Host reachable; nothing listening on that port
Connection timed out Firewall DROPping packets, host offline, or no route
Permission denied (publickey) TCP connected, but authentication failed
Host key verification failed Server host key changed — reinstall or MITM risk
No route to host Layer 3 routing failure; check default gateway

Step 1: Verify the Host is Reachable

Before diagnosing SSH, confirm basic network connectivity:

ping -c 4 <hostname-or-ip>
traceroute <hostname-or-ip>

If ping fails but you know the host is running, ICMP may be blocked — that is acceptable. Proceed to Step 2. If ping succeeds but SSH refuses, the issue is at the SSH layer.

Also confirm you have the correct IP address; a DNS misconfiguration pointing to the wrong host is a surprisingly common culprit:

nslookup <hostname>
dig +short A <hostname>

Step 2: Verify sshd is Running on the Remote Host

Gain access to the remote host via a cloud serial console, web-based shell, or physical terminal, then check the daemon:

# systemd-based systems (Ubuntu 16+, Debian 9+, CentOS/RHEL 7+)
sudo systemctl status sshd
sudo systemctl status ssh        # Ubuntu/Debian use the 'ssh' unit name

# SysV init (older distros)
sudo service ssh status

# Universal process check
ps aux | grep [s]shd
pgrep -a sshd

Expected output when running:

bullet sshd.service - OpenBSD Secure Shell server
   Loaded: loaded (/lib/systemd/system/sshd.service; enabled)
   Active: active (running) since Mon 2024-01-15 09:23:11 UTC; 3h 42min ago

If inactive or failed, start and enable it:

sudo systemctl start sshd
sudo systemctl enable sshd       # Ensure start on reboot

If sshd fails to start, read the logs immediately:

sudo journalctl -xe -u sshd --no-pager
sudo tail -50 /var/log/auth.log          # Debian/Ubuntu
sudo tail -50 /var/log/secure            # RHEL/CentOS/Fedora

Step 3: Identify the SSH Listening Port

By default SSH listens on TCP port 22, but this is frequently changed. Check both the configuration and the live socket:

# What the config says
sudo grep -E '^Port' /etc/ssh/sshd_config

# What sshd is actually bound to (most reliable)
sudo ss -tlnp | grep sshd
sudo netstat -tlnp | grep sshd    # If ss is unavailable

Example output showing SSH has been moved to port 2222:

LISTEN 0  128  0.0.0.0:2222  0.0.0.0:*  users:(("sshd",pid=1337,fd=3))
LISTEN 0  128  [::]:2222     [::]:*     users:(("sshd",pid=1337,fd=5))

If SSH is on a non-standard port, specify it explicitly:

ssh -p 2222 user@hostname

Or make it permanent in ~/.ssh/config so you never have to remember:

Host myserver
    HostName hostname
    Port 2222
    User myusername
    IdentityFile ~/.ssh/id_ed25519

From the client side, test if the port is actually open before attempting SSH:

nc -zv <hostname> 22
nc -zv <hostname> 2222
# If SSH is listening, you will see the protocol banner:
echo '' | nc -w3 <hostname> 22
# Expected: SSH-2.0-OpenSSH_9.3p1 Ubuntu-1ubuntu3

Step 4: Diagnose and Fix Firewall Rules

A firewall blocking port 22 is the most common cause on cloud infrastructure. A REJECT rule produces an instant refusal; a DROP rule produces a timeout. Check every firewall layer: OS-level and cloud-level.

Ubuntu/Debian — ufw:

sudo ufw status verbose
# If no SSH rule appears:
sudo ufw allow 22/tcp
sudo ufw allow OpenSSH
sudo ufw reload

CentOS/RHEL 7+ — firewalld:

sudo firewall-cmd --list-all
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload

Direct iptables (any distro):

sudo iptables -L INPUT -n -v --line-numbers | grep -E '22|ssh'
# Insert ACCEPT rule at position 1 if missing:
sudo iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT
sudo iptables-save | sudo tee /etc/iptables/rules.v4

Cloud provider security groups — check this layer independently of the OS firewall:

  • AWS EC2: Console → EC2 → Security Groups → Inbound Rules → Add: Type=SSH, Source=My IP
  • GCP Compute Engine: VPC Network → Firewall → Create rule: tcp:22, apply to target tag or all instances
  • Azure: VM → Networking → NSG → Add Inbound Port Rule: Port=22, Protocol=TCP
  • DigitalOcean: Networking → Firewalls → Inbound Rules → SSH

Cloud security groups are enforced before OS-level firewalls. Both must allow port 22 for SSH to work.


Step 5: Fix SSH Permission Denied (publickey)

If you can reach the port (the SSH banner appears) but authentication fails, you will see:

user@hostname: Permission denied (publickey).
user@hostname: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

This is an authentication failure, not a connectivity failure. Work through these checks:

Ensure your key is loaded into ssh-agent:

ssh-add -l
ssh-add ~/.ssh/id_ed25519
ssh-add ~/.ssh/id_rsa

Verify your public key exists in authorized_keys on the server:

cat ~/.ssh/authorized_keys    # on the remote server
# Your id_ed25519.pub content must appear as a single line

Fix file permissions — SSH is strict and silent about this:

chmod go-w ~/
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_rsa.pub

When permissions are wrong, /var/log/auth.log on the server shows:

Authentication refused: bad ownership or modes for directory /home/user/.ssh

Enable maximum SSH verbosity to trace the exact failure:

ssh -vvv user@hostname 2>&1 | head -80

Look for lines like debug1: Offering public key followed by debug2: we did not send a packet, disable method — this confirms the key is being offered but silently rejected server-side.

Check that sshd_config permits key authentication:

sudo grep -E 'PubkeyAuthentication|AuthorizedKeysFile|PermitRootLogin' /etc/ssh/sshd_config

Ensure these are set (and not commented out):

PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys

Step 6: Fix Slow SSH Connections

A 10–30 second hang before the password prompt or shell prompt is typically caused by authentication negotiation timeouts, not connectivity issues.

1. Disable reverse DNS lookup (most common cause):

# In /etc/ssh/sshd_config on the server:
UseDNS no
sudo systemctl reload sshd

2. Disable GSSAPI authentication on the client:

# In ~/.ssh/config on the client:
Host *
    GSSAPIAuthentication no
    GSSAPIDelegateCredentials no

3. Force IPv4 to skip IPv6 resolution delay:

ssh -4 user@hostname
# Permanent fix in ~/.ssh/config:
AddressFamily inet

4. Check MaxStartups throttling on busy servers:

sudo grep MaxStartups /etc/ssh/sshd_config
# Default: 10:30:100 — raise for high-concurrency environments

Step 7: Diagnose sshd Segfaults

A segfault in sshd appears in system logs as:

kernel: sshd[4521]: segfault at 0 ip 00007f8a1b2c3d4e sp 00007fff... error 4 in libcrypto.so.3
sshd[4521]: fatal: mm_answer_sign: buffer error

Common causes: a partial system update creating a library mismatch, or hardware memory errors.

# Check for library mismatches
ldd $(which sshd) | grep -i crypto

# Reinstall openssh (Debian/Ubuntu)
sudo apt --fix-broken install && sudo dpkg --configure -a
sudo apt-get install --reinstall openssh-server

# Reinstall openssh (RHEL/CentOS)
sudo yum reinstall openssh-server

# Check for hardware memory errors
dmesg | grep -iE 'mce|hardware error|ECC|uncorrected'

Step 8: Emergency Recovery When Completely Locked Out

If you have no working SSH access and no backup access method:

  1. Cloud serial console: AWS Systems Manager Session Manager, GCP Cloud Shell serial console, Azure Boot Diagnostics serial console — all bypass SSH entirely.
  2. Volume rescue (AWS): Stop the instance, detach the root EBS volume, attach it to a rescue instance, mount it, edit /etc/ssh/sshd_config and firewall rules, then reattach.
  3. VPS rescue mode: Most providers (Hetzner, OVH, Linode/Akamai, Vultr) offer a rescue boot from the control panel that mounts your disk in a live environment.
  4. Physical servers: Boot from a live USB; mount the filesystem and fix /etc/ssh/sshd_config or firewall scripts.

Prevention: Before making any firewall change on a production server, open a second SSH session in a separate terminal window. If the new rule locks you out, the existing session stays open. Also consider configuring sshd to listen on a secondary backup port.


Validation Checklist

After applying any fix, confirm resolution with:

# 1. sshd is running
sudo systemctl is-active sshd

# 2. Port is bound
sudo ss -tlnp | grep sshd

# 3. Port is reachable from the client
nc -zv <hostname> 22 && echo 'Port open'

# 4. Full SSH login test with verbose output
ssh -v user@hostname

Frequently Asked Questions

bash
#!/usr/bin/env bash
# SSH Connection Refused — Comprehensive Diagnostic Script
# Usage: ./ssh-diagnose.sh [remote_host] [port]
# Run the remote-host sections ON THE SERVER via console access.
# Run the client sections from your LOCAL machine.

REMOTE_HOST="${1:-localhost}"
SSH_PORT="${2:-22}"

echo "=== SSH DIAGNOSTIC REPORT ==="
echo "Target: ${REMOTE_HOST}:${SSH_PORT}  |  Date: $(date -u)"
echo ""

# ── SERVER-SIDE CHECKS (run on the remote host via console) ──────────────────

echo "--- [1] sshd Process Status ---"
if pgrep -x sshd > /dev/null 2>&1; then
    echo "[OK]   sshd is running: $(pgrep -a sshd | head -1)"
else
    echo "[FAIL] sshd is NOT running"
    echo "       Fix: sudo systemctl start sshd"
fi

echo ""
echo "--- [2] SSH Listening Port ---"
if command -v ss &>/dev/null; then
    SS_OUT=$(sudo ss -tlnp | grep sshd)
    if [ -n "$SS_OUT" ]; then
        echo "[OK]   $SS_OUT"
    else
        echo "[WARN] sshd not found in ss output — may be stopped or on another port"
    fi
fi

CONFIG_PORT=$(sudo grep -E '^Port' /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}')
echo "       sshd_config Port: ${CONFIG_PORT:-22 (default)}"

echo ""
echo "--- [3] Firewall Status ---"
if command -v ufw &>/dev/null; then
    UFW_STATUS=$(sudo ufw status | head -1)
    echo "[ufw]  $UFW_STATUS"
    sudo ufw status | grep -iE '22|ssh|OpenSSH' || echo "[WARN] No SSH rule found in ufw rules"
elif command -v firewall-cmd &>/dev/null; then
    sudo firewall-cmd --list-services 2>/dev/null | grep -q ssh && \
        echo "[firewalld] SSH service allowed" || \
        echo "[WARN]      SSH service not in firewalld allowed list"
fi

echo ""
echo "--- [4] iptables: SSH Port Rules ---"
sudo iptables -L INPUT -n -v 2>/dev/null | grep -E ":22 |ssh" || \
    echo "       No specific iptables rule for port 22 found"

echo ""
echo "--- [5] ~/.ssh Directory Permissions ---"
SSH_DIR="$HOME/.ssh"
if [ -d "$SSH_DIR" ]; then
    SSH_PERM=$(stat -c '%a' "$SSH_DIR" 2>/dev/null)
    AUTH_PERM=$(stat -c '%a' "$SSH_DIR/authorized_keys" 2>/dev/null || echo "N/A")
    HOME_PERM=$(stat -c '%a' "$HOME" 2>/dev/null)
    echo "       ~/.ssh permissions:           ${SSH_PERM} (required: 700)"
    echo "       ~/.ssh/authorized_keys perms: ${AUTH_PERM} (required: 600)"
    echo "       ~/ home dir permissions:      ${HOME_PERM} (must not be world-writable)"
    [ "$SSH_PERM" != "700" ]  && echo "[WARN] Fix: chmod 700 ~/.ssh"
    [ "$AUTH_PERM" != "600" ] && [ "$AUTH_PERM" != "N/A" ] && echo "[WARN] Fix: chmod 600 ~/.ssh/authorized_keys"
else
    echo "[INFO] ~/.ssh directory does not exist"
fi

echo ""
echo "--- [6] Recent sshd Log Entries ---"
if [ -f /var/log/auth.log ]; then
    sudo tail -20 /var/log/auth.log | grep sshd
elif [ -f /var/log/secure ]; then
    sudo tail -20 /var/log/secure | grep sshd
else
    sudo journalctl -u sshd --no-pager -n 20 2>/dev/null
fi

# ── CLIENT-SIDE CHECKS (run from your local machine) ─────────────────────────

echo ""
echo "--- [7] Remote Port Reachability Test ---"
if command -v nc &>/dev/null; then
    BANNER=$(echo '' | nc -w3 "${REMOTE_HOST}" "${SSH_PORT}" 2>/dev/null | head -1)
    if [[ "$BANNER" == SSH* ]]; then
        echo "[OK]   Port ${SSH_PORT} open. Banner: ${BANNER}"
    else
        echo "[FAIL] No SSH banner on ${REMOTE_HOST}:${SSH_PORT}"
        echo "       Possible causes: sshd not running, wrong port, or firewall DROP rule"
    fi
else
    echo "[SKIP] nc not available. Install: sudo apt install netcat-openbsd"
    echo "       Alternative: nmap -p ${SSH_PORT} ${REMOTE_HOST}"
fi

echo ""
echo "--- [8] Client-Side Key Check ---"
echo "       Loaded keys in ssh-agent:"
ssh-add -l 2>/dev/null || echo "       [WARN] No keys loaded (run: ssh-add ~/.ssh/id_ed25519)"

echo ""
echo "=== DIAGNOSTIC COMPLETE ==="
echo "Review any [FAIL] or [WARN] lines above."
echo "For verbose SSH debug: ssh -vvv ${1:-user}@${REMOTE_HOST} -p ${SSH_PORT} 2>&1 | head -80"
E

Error Medic Editorial

The Error Medic Editorial team is composed of senior Linux engineers, SREs, and cloud architects with decades of combined experience managing production infrastructure on bare metal, virtual machines, and multi-cloud platforms. We specialize in translating cryptic system error messages into precise, actionable remediation steps backed by official documentation, real-world incident post-mortems, and validated command sequences.

Sources

Related Guides