Troubleshooting

SSH "Connection refused": causes and fixes

Connection refused means the machine answered but nothing is listening on that port. The four causes — sshd not running, wrong port, firewall reject, wrong host — how to tell it apart from timeouts and auth failures, and the fixes.

CC Chen Chen· Founder·June 11, 2026·5 min read

What "Connection refused" means

"Connection refused" is a network-level rejection: your packets reached the machine (or a firewall in front of it), and something actively answered "nothing is listening here" — a TCP reset, which your client reports as ECONNREFUSED. That makes it different from Permission denied (auth failed after connecting) and from a timeout (no answer at all — usually wrong IP, host down, or a firewall silently dropping). Refused is the good news error: it proves something is alive at that address and reachable from where you are.

The wording varies by client, but it is always the same TCP failure:

ssh: connect to host 192.168.1.42 port 22: Connection refused   # OpenSSH: Linux, macOS, iOS, Android
ssh: connect to host example.com port 22: Connection refused    # same thing with a hostname
Network error: Connection refused                                # PuTTY on Windows
channel 2: open failed: connect failed: Connection refused       # a port-forward, not the SSH login itself

Note two things about when it happens. First, it appears instantly — under a second. If your client sits there for 30 seconds or more before failing, you have a timeout, not a refusal, and the causes are completely different. Second, it happens before any banner, password prompt, or key exchange, so your username, password, and SSH key cannot be the cause. If you got as far as a prompt, see Permission denied (publickey); if sshd answered and then hung up, see Connection closed by remote host.

The causes, most likely first

Almost every refusal is one of these. The classic four come first, but the bottom half of the table is where the stubborn cases live — and it's the half most guides skip.

#CauseTypical situationFastest check
1sshd not running (or not installed)Fresh Pi, Ubuntu Desktop, WSL, minimal imagesystemctl status ssh on the box
2Wrong portSSH was moved off 22 for noise reductionnc -vz host 22 vs nc -vz host 2222
3Firewall rule set to REJECTufw/iptables/cloud rule that refuses rather than dropssudo ufw status verbose
4Wrong host at that addressDHCP handed the box a new IP; you hit a printerRouter device list, hostname -I on the box
5sshd bound to localhost onlyListenAddress 127.0.0.1 in sshd_configss -tlnp shows 127.0.0.1:22, not 0.0.0.0:22
6sshd died on a config errorRefused right after you edited sshd_configsudo sshd -t, journalctl -u ssh -n 50
7fail2ban / CrowdSec banned your IPWorked fine, then refused after failed loginssudo fail2ban-client status sshd
8Port forward points at nothingRouter forwards 22 to a LAN IP that movedRouter's port-forward table vs the box's current IP
9Container/VM with no sshd or no published portDocker, WSL, Vagrant, Multipassdocker ps port mapping; ss -tlnp inside
10Host still booting, or out of disk/memoryInstance just started or is thrashingProvider console/serial console, not SSH

1 — Is sshd actually running?

You need another way onto the box: a monitor and keyboard, the provider's web console or serial console, or another machine you can already reach. Then:

sudo systemctl status ssh      # Debian, Ubuntu, Raspberry Pi OS
sudo systemctl status sshd     # RHEL, Fedora, Rocky, Alma, Arch, openSUSE
sudo systemctl enable --now ssh

ss -tlnp | grep ':22'          # the real question: is ANYTHING listening?

That last line is the one that ends arguments. If it prints nothing, no service owns port 22 and every refusal is explained. If it prints 0.0.0.0:22 and [::]:22, sshd is listening on all interfaces and your problem is further out — a firewall or the wrong address.

Three traps live in this step:

  • The package isn't installed at all. Ubuntu Desktop, many minimal cloud images, WSL distros, and slim containers ship the SSH client but not the server. sudo apt install openssh-server (or dnf install openssh-server) then enable it. Ubuntu Server images normally include it already.
  • "inactive (dead)" can be perfectly normal. On Ubuntu 22.10 and newer, OpenSSH uses systemd socket activation: ssh.socket holds port 22 and only spawns sshd when a connection arrives. So systemctl status ssh showing inactive is expected, and the unit you actually care about is systemctl status ssh.socket. If that socket is stopped, disabled, or masked, you get refused with a perfectly healthy-looking ssh.service.
  • It was fine until you edited the config. A syntax error means sshd refuses to start on restart, and the listener disappears. sudo sshd -t validates the file and names the offending line; sudo journalctl -u ssh -n 50 --no-pager shows why it exited. Fix, then sudo systemctl restart ssh.

Fresh installs are the single most common version of this error. Raspberry Pi OS ships with SSH disabled deliberately — enable it once and it persists (details in the platform section below and in enabling SSH on a Pi).

2 — Right port?

If SSH was moved off 22 (a common noise-reduction step), connecting to 22 gets refused instantly while the real port works fine. Check the port field in your connection profile, then probe from any machine on the same network:

nc -vz host 22
nc -vz host 2222
nc -zv host 22-2222 2>&1 | grep succeeded   # sweep a range if you've forgotten

On the box itself, sudo ss -tlnp | grep sshd tells you the truth in one line: which address and which port sshd owns. Two subtleties account for most "but I set the port!" cases:

  • Socket activation ignores your Port line. On Ubuntu 22.10+, changing Port in /etc/ssh/sshd_config alone may not move the listener, because ssh.socket is what binds the port. You have to override the socket too:
    sudo systemctl edit ssh.socket
    # in the editor, add:
    [Socket]
    ListenStream=
    ListenStream=2222
    
    sudo systemctl daemon-reload
    sudo systemctl restart ssh.socket
    ss -tlnp | grep 2222
    The empty ListenStream= clears the inherited port 22; the second line sets the new one. Skip it and you'll swear the config is being ignored — because it is.
  • ListenAddress is scoped too narrowly. ListenAddress 127.0.0.1 means sshd accepts only local connections, so ssh localhost on the box works while every LAN or internet attempt is refused. ss -tlnp showing 127.0.0.1:22 instead of 0.0.0.0:22 is the giveaway. The same applies if it's pinned to an interface address the box no longer has.

3 — Firewall rejecting

Here's the distinction that decides where to look: a firewall set to REJECT sends a refusal back, so you get "Connection refused" immediately. A firewall set to DROP says nothing, so you get "Connection timed out" after a long wait. Cloud security groups almost always drop — so a refusal from a VPS points at the OS firewall or sshd, not the provider's rules. On the box:

sudo ufw status verbose                        # Ubuntu/Debian: is 22/tcp ALLOW IN?
sudo ufw allow 22/tcp

sudo firewall-cmd --list-all                   # RHEL/Fedora/Rocky
sudo firewall-cmd --permanent --add-service=ssh && sudo firewall-cmd --reload

sudo iptables -S | grep -iE 'reject|dport 22'  # an explicit REJECT rule?
sudo nft list ruleset | grep -i ssh            # nftables systems

Also check the provider's security group / cloud firewall in the dashboard — it blocks before the OS ever sees the packet, so ufw status can look perfectly healthy while nothing arrives. And on a home network, check the router: a port-forward rule for 22 that still points at an old LAN address will refuse on your behalf.

The "it worked an hour ago" case: you've been banned. fail2ban and CrowdSec block IPs after repeated auth failures, and some default actions reject rather than drop — producing a sudden, instant refusal from a server that everyone else can still reach. Two tells: it started right after a burst of failed logins, and switching networks (cellular instead of Wi-Fi) fixes it, because the ban is on your IP. From another way in:

sudo fail2ban-client status sshd
sudo fail2ban-client set sshd unbanip 203.0.113.7

4 — Wrong machine at that address

Home networks reassign IPs (DHCP). If your saved connection points at 192.168.1.42 but the box rebooted onto .57, you may now be talking to a printer, a smart plug, or a router admin page — none of which run SSH, all of which will refuse port 22. The tell is that the address answers ping happily while port 22 is refused.

Re-check the router's device list, or run hostname -I on the box. Then sidestep the whole class of problem: give each machine a DHCP reservation, or use Tailscale, where every box keeps one stable private address no matter what the LAN or your carrier does. TermAI has Tailscale built in. See Tailscale SSH setup.

A working SSH session over a stable Tailscale address on a phone
Stable addresses end the 'IP changed, connection refused' loop: over Tailscale the box keeps one private address, wherever you and it are.

"ssh localhost port 22: Connection refused" on your own machine

Connecting to yourself removes the network from the equation entirely, which makes this the most diagnostic version of the error: if ssh localhost is refused, there is genuinely no SSH server running on that machine. Not a firewall issue, not a routing issue — install and start the server (step 1). This is the normal state of a Mac with Remote Login off, an Ubuntu Desktop install, and virtually every WSL distro and Docker container.

One lookalike is worth separating: channel 2: open failed: connect failed: Connection refused during a port forward. Your SSH login succeeded — the refusal is on the far end of the tunnel, where the service you forwarded to isn't running or is bound to 127.0.0.1 on a different host than you think. Test with curl localhost:PORT on the remote box before blaming SSH.

Refused on a specific platform

Raspberry Pi

Raspberry Pi OS ships with SSH turned off by default (a deliberate security decision, since a device with default credentials reachable on the network is a liability). A Pi that's on the network but refusing port 22 is almost always just this. Three ways to enable it:

  • Before first boot — put an empty file named exactly ssh (no extension) in the boot partition of the SD card, labelled bootfs on current images and boot on older ones. The Pi enables SSH on boot and deletes the file.
  • In Raspberry Pi Imager — the advanced/customisation settings let you enable SSH and set the username and password before writing the card. Current Raspberry Pi OS no longer ships a default pi/raspberry account, so you must set credentials somewhere.
  • With a keyboard attachedsudo raspi-config → Interface Options → SSH → Enable, or sudo systemctl enable --now ssh.

If the Pi times out instead of refusing, it never joined the network — a different problem. See SSH to a Raspberry Pi.

macOS

macOS has sshd built in but disabled: it's called Remote Login, and until you turn it on, every connection to that Mac is refused. Enable it in System Settings → General → Sharing → Remote Login (older versions: System Preferences → Sharing), or from a terminal on that Mac:

sudo systemsetup -setremotelogin on
sudo systemsetup -getremotelogin        # confirm: "Remote Login: On"
ssh localhost                            # confirm it answers locally

Two follow-on gotchas on the Mac side: the Remote Login pane has an "Allow access for" setting, so a user not in the permitted list may be blocked; and the macOS firewall can be set to block incoming connections, in which case add /usr/sbin/sshd to its allowed list. Also remember that a sleeping Mac stops answering — enable "Prevent automatic sleeping" or wake-for-network access if you rely on it. Connecting from a Mac or iPhone, the wording is identical; see SSH from iPhone to Mac.

Windows and PuTTY

PuTTY words it Network error: Connection refused; Windows' built-in OpenSSH client uses the standard wording. Either way, the meaning is unchanged, and since it happens before any credential prompt it is never a password problem.

If Windows is the target, the OpenSSH Server is an optional component that is not installed by default. In an elevated PowerShell:

Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Start-Service sshd
Set-Service -Name sshd -StartupType Automatic
Get-NetFirewallRule -Name *ssh*          # the sshd inbound rule should be enabled

Third-party antivirus and endpoint suites also filter connections independently of Windows Defender Firewall, and can reject rather than drop.

WSL

WSL distros install with an SSH client but usually no server, so ssh localhost inside WSL is refused until you sudo apt install openssh-server and start it. Two extra WSL-specific traps once it is running: WSL2 lives on its own virtual network, so connecting from another machine on your LAN needs a netsh interface portproxy rule on the Windows host pointing at the WSL IP; and if Windows itself is running the OpenSSH Server on port 22, you'll want WSL's sshd on a different port to avoid a conflict. Note also that older WSL setups without systemd need sudo service ssh start rather than systemctl.

Docker containers and VMs

Container images almost never run an SSH daemon — there's nothing to refuse a connection to. If you're trying to reach a container, either use docker exec -it NAME bash (the normal way) or, if you genuinely need sshd inside, install it, run it, and publish the port: docker run -p 2222:22 ..., then ssh -p 2222 user@localhost. A container whose port isn't published refuses on the host side even when sshd is happily running inside. docker ps shows the actual mapping; run ss -tlnp inside the container to confirm sshd is listening on 0.0.0.0 rather than only 127.0.0.1.

Local VMs (Vagrant, VirtualBox, Multipass, UTM) hit the same shape of problem through NAT port forwarding: the forwarded host port is refused if the guest's sshd hasn't started yet, or if the guest is still booting. During vagrant up, a burst of refusals that eventually succeeds is normal — the tool is polling a VM that hasn't finished booting.

VPS, EC2, and other cloud servers

On a cloud instance the direction of the error is informative. A timeout normally means the provider's security group is dropping your packets; a refusal normally means you got through the cloud firewall and the instance itself has no listener. So work in this order:

  1. Is the instance actually up and healthy? A machine that failed a status check, filled its root disk, or is out of memory will not have a running sshd. Check the provider console, not SSH.
  2. Did it just boot? Refusals during the first minute of a launch or reboot are simply sshd not up yet. Retry before debugging.
  3. Get in without port 22. AWS gives you EC2 Instance Connect, Session Manager, and the serial console; Azure, Google Cloud, DigitalOcean, Hetzner, and Oracle Cloud all provide a browser or serial console. Use one, then run ss -tlnp | grep :22 and systemctl status ssh.
  4. Check the OS firewall. Oracle Cloud images are notorious here: they ship restrictive iptables rules in addition to the cloud-level security list.
  5. Check for a ban. A public-facing server accumulates brute-force attempts constantly; a fail2ban rule that rejected your address looks exactly like this.

More phone-side context: managing a VPS from your phone.

"github.com port 22: Connection refused" on git clone or push

GitHub's SSH endpoint is up; something between you and it is refusing — typically a corporate proxy or filtered network. GitHub publishes an alternate SSH endpoint on the HTTPS port that gets through nearly everything. Test it:

ssh -T -p 443 [email protected]

If that greets you by username, make it permanent in ~/.ssh/config:

Host github.com
  Hostname ssh.github.com
  Port 443
  User git

The hostname is ssh.github.com, not github.com — port 443 on the main domain is the website. Expect a one-time host-key prompt for the new host; if that looks wrong, see host key verification failed.

A 60-second diagnosis

From any machine that can reach the network the server is on:

ping HOST                          # replies? the address is live
nc -vz HOST 22                     # "refused" (instant) vs hang (dropped)
nc -vz HOST 2222                   # is SSH somewhere else?
ssh -vvv user@HOST 2>&1 | tail -20 # where exactly does it stop?

And on the box, once you have any way in:

ss -tlnp | grep ':22'              # is anything listening, and on which address?
systemctl status ssh ssh.socket    # service and socket unit
sudo sshd -t                       # config valid?
sudo journalctl -u ssh -n 50 --no-pager
sudo fail2ban-client status sshd   # if it's installed

Read the result by speed and shape, not by the last line of text:

  • Instant refusal, ping works → right machine, no listener on that port: this article.
  • Instant refusal, ping fails too → you may be hitting a router or gateway answering on the address's behalf. Verify the IP before anything else.
  • Long hang, then failure → not this error. Go to Connection timed out.
  • Banner appears, then it fails → you connected. It's auth, host keys, or algorithms — see the troubleshooting index.

Refused vs timed out vs closed vs denied

ErrorHow far you gotWhere to look
Connection refusedMachine answered "nothing here" (instant)sshd not running, wrong port, REJECT rule (this article)
Connection timed outNo response at all (slow)Wrong address, host down, DROP firewall, private IP from outside
Connection closed by remote hostsshd answered, then hung upfail2ban, MaxStartups, host limits
Permission denied (publickey)Fully connected, auth failedKeys, permissions, authorized_keys
Broken pipeSession worked, then diedIdle timeouts, network change

All of them in one place: the SSH troubleshooting index.

Debugging from a phone

The awkward part of this error on mobile is that you often can't SSH to the box to check it — the fix lives on the machine you just got locked out of. Three ways through:

  • Hop through a neighbour. If any other machine on that network is reachable, SSH there and probe from inside: nc -vz 192.168.1.42 22 and ping 192.168.1.42 tell you immediately whether the box is alive and whether the port is dead.
  • Use the provider's console. On a VPS, the web/serial console doesn't use port 22 at all, and it works fine from a phone browser.
  • Ask instead of remembering. If the diagnostic incantations aren't at your fingertips, describe the situation to TermAI's assistant — "check whether anything is listening on port 22 of 192.168.1.42 from here" — and run the suggested command on the box you are connected to.

For the recurring version of this problem — an address that keeps changing under you — Tailscale is the durable fix, and it's built into TermAI so the phone side needs no second app.

FAQ

What does "ssh: connect to host port 22: Connection refused" mean?
Your packets reached the machine and it actively refused the connection with a TCP reset — nothing is accepting SSH on that port. It happens before authentication, so it is never a key, password, or username problem. The four usual causes: sshd isn't running, SSH is on a different port, a firewall is rejecting the port, or you're connecting to the wrong host.

What causes SSH connection refused?
Most often the SSH server isn't running or isn't installed; next, SSH listening on a non-standard port; then a firewall rule set to REJECT; then the wrong machine at that IP after a DHCP change. Less obvious causes worth checking: sshd bound to 127.0.0.1 only, sshd failing to start after a config edit, a fail2ban ban on your IP, and a router port-forward pointing at a stale address.

How do I fix SSH connection refused?
Get onto the box another way (monitor, provider console, or another machine on the LAN), then run ss -tlnp | grep ':22'. Nothing listening → install/start sshd (sudo systemctl enable --now ssh). Listening on another port → connect with -p. Listening on 127.0.0.1 → fix ListenAddress. Listening correctly → check ufw/firewalld and the cloud firewall, then fail2ban.

What's the difference between "connection refused" and "connection timed out"?
Refused = the machine answered "nothing here", instantly. Timed out = no answer at all, after a long wait. Refused is better news: it proves you reached a live machine and nothing is silently dropping your traffic. A REJECT firewall rule gives you refused; a DROP rule gives you a timeout.

Does connection refused mean my SSH key or password is wrong?
No — never. The refusal happens at the TCP layer, before SSH exchanges a single byte. Credential problems show up as Permission denied (publickey) after a successful connection.

How do I fix connection refused on a Raspberry Pi?
SSH is disabled by default on Raspberry Pi OS. Enable it by placing an empty file named ssh in the boot partition (bootfs on current images) before boot, by ticking the SSH option in Raspberry Pi Imager, or with sudo raspi-config on an attached keyboard. It persists after that.

Why does my Mac refuse SSH connections?
macOS ships sshd disabled. Turn on Remote Login in System Settings → General → Sharing, or run sudo systemsetup -setremotelogin on. Then check the "Allow access for" list and make sure the Mac isn't asleep.

Why is "ssh localhost" refused?
Because there is no SSH server running on that machine — a firewall or network problem can't be involved when you're connecting to yourself. Install and start openssh-server. This is the normal starting state on Ubuntu Desktop, WSL distros, and most containers.

I changed the SSH port on Ubuntu and now everything is refused. Why?
On Ubuntu 22.10 and later, sshd is socket-activated: ssh.socket owns the listening port, so a Port line in sshd_config may not move the listener. Run sudo systemctl edit ssh.socket, add a [Socket] section with an empty ListenStream= followed by ListenStream=YOURPORT, then daemon-reload and restart ssh.socket. Confirm with ss -tlnp.

SSH worked yesterday and is refused today from my network only. What changed?
Suspect an IP ban. fail2ban and CrowdSec block addresses after repeated failed logins, and some configurations reject rather than drop. Test from cellular or another network: if that connects, it's a ban on your address, not a server fault. Unban with sudo fail2ban-client set sshd unbanip YOUR.IP.

Why does my EC2 / VPS refuse port 22?
A refusal (rather than a timeout) usually means you passed the cloud firewall and the instance has no listener: it's still booting, it failed a status check, its disk is full, or sshd didn't start. Use EC2 Instance Connect, the serial console, or your provider's web console to get in and check ss -tlnp. Security-group problems normally cause timeouts instead.

Why does Docker refuse SSH on my container?
Most images don't run an SSH daemon at all, and a container port isn't reachable unless it's published (-p 2222:22). Use docker exec -it NAME bash instead — it's the intended way in and needs no sshd.

Can a wrong username cause connection refused?
No. The username is only sent after the TCP connection and key exchange succeed. A bad username produces Permission denied, not a refusal.

What does "channel 2: open failed: connect failed: Connection refused" mean?
Your SSH session connected fine; the refusal is at the far end of a port forward. The service you forwarded to isn't running, or it's bound to 127.0.0.1 on a machine other than the one you assumed.

How do I check whether port 22 is open when I only have my phone?
SSH into any other machine on that network and run nc -vz HOST 22 from there; an instant "refused" versus a hang tells you refusal from drop. If nothing on the network is reachable, use the server provider's web console. TermAI's assistant will produce the right probe command from a plain-language description.

How do I stop this happening again?
Give the box a static IP or DHCP reservation so the address stops moving, enable sshd at boot (systemctl enable ssh), keep a second way in (provider console or physical access), and test sshd -t before restarting after any config change. Putting the machine on Tailscale removes the address-drift class of failure entirely.

Quick Facts

  • Meaning: reachable machine, nothing listening on that port — a TCP reset, delivered instantly, always pre-authentication
  • Top causes: sshd not running or not installed · wrong port · REJECT firewall rule · wrong host after an IP change
  • Overlooked causes: ListenAddress 127.0.0.1 · sshd died on a config error · fail2ban ban · unpublished container port · Ubuntu socket activation
  • One command that settles it: ss -tlnp | grep ':22' on the box — nothing listening explains everything
  • Refused vs timed out: refused is instant and means REJECT/no listener; a slow failure is a timeout (DROP or wrong address)
  • Never the cause: your key, password, or username — those fail later as Permission denied
  • Avoid IP drift: stable Tailscale addresses instead of LAN DHCP IPs
Try TermAI

Free on iOS and Android. 5 AI requests/day on the free tier, plus unlimited SSH/SFTP and built-in Tailscale.

CC
Chen Chen — Founder of TermAI

Writes about mobile DevOps, terminal UX, and the surprising depth of "boring" infrastructure.

Was this useful? ← Back to blog