ssh: connect to host port 22: Connection refused, from a Mac or into a Mac
Frank Zhang
- 6 minutes read - 2909 wordsTL;DR
Connection refused means this TCP attempt was not accepted. See whether you are leaving the Mac or logging into it, then read the real host, port, and listener before you change Remote Login or sshd.
This is the message to match before you touch a firewall or restart anything:
ssh: connect to host <name> port 22: Connection refused
The words after the colon are the whole diagnosis the client is willing to give you. Keys and passwords have not been tried yet. <name> is whatever you typed, unless ~/.ssh/config rewrote it. The same sentence covers two directions: you are on a Mac connecting out to a server, or someone (including ssh localhost on that Mac) is connecting in. Those are different machines to inspect.
The table is the first command only. These commands were not re-run on a Mac or a VPS for this page. Flag meanings come from the manuals. If man on the machine in front of you disagrees, that copy wins.
| Text after the colon | Likely cause | First read-only command |
|---|---|---|
Connection refused | connect() returned ECONNREFUSED. Linux connect(2) says a stream socket found no one listening on the remote address. That covers sshd not running, sshd listening on another port, and sshd bound to another address. A firewall that rejects the handshake with a TCP reset produces the same errno. The reset half is general guidance; the manual sentence is “no one listening.” | ssh -v user@host |
Operation timed out when you are matching a Mac client. On Linux, strerror(ETIMEDOUT) is Connection timed out | Nothing answered in time. connect(2) describes ETIMEDOUT as a timeout while attempting the connection. A wrong address, a filter that drops packets, a cloud security group that drops, and macOS stealth mode (no answer to probes) sit here. Stealth mode is quoted in the firewall section below. | ssh -v user@host |
No route to host, or Network is unreachable | Another sentence. Network is unreachable matches ENETUNREACH in that connect(2) page. No route to host is strerror(EHOSTUNREACH). The error list in the connect(2) page cited here does not name EHOSTUNREACH. Trust the words on your screen. | ssh -v user@host |
The OpenSSH client builds the line in sshconnect.c, as read for this page: after every address has failed, it prints ssh: connect to host %s port %s: %s with strerror(errno) in the last slot. On the glibc used while writing this page, those strings are Connection refused (ECONNREFUSED), Connection timed out (ETIMEDOUT), No route to host (EHOSTUNREACH), and Network is unreachable (ENETUNREACH). That is one C library, not a Mac. The macOS timeout string to match is Operation timed out. This page did not open Darwin’s errno header, so the words on your screen win.
With -v, each attempt also logs Connecting to <host> [<ip>] port <port>. Read those lines, then the final error. The final error is the errno of the last failure.
Which side of the connection you are on
Leaving the Mac: the other end is a VPS, a Linux box, or some other host. Remote Login on the Mac you are typing on does not decide whether port 22 is open over there. Read the host and port the client actually used, then look at the far machine from its own console.
Logging into the Mac: another computer is connecting to this Mac, or you ran ssh localhost on it. Apple’s Remote Login guide says you turn on Remote Login to reach the Mac with SSH or SFTP. With that switch off, nothing is listening on port 22, and ssh localhost gets Connection refused.
One earlier incident on this site, written in Chinese, is a real session connecting to a Mac through a cpolar tunnel whose address and port kept changing: 一次ssh排错经历. The start command used that day stays in that post. This page is the read-only order.
On the client, before any edit
None of these change sshd, a firewall, or known_hosts.
See the attempt
ssh(1) says -v prints debugging messages about connection, authentication, and configuration problems. One -v is enough for the host, the address, and the port. You can stack it up to three times.
ssh -v user@host
On the Connecting to line, check the name, the address in brackets, and the port. If that port is not 22, every later nc has to use the port printed there.
See what the config file rewrote
The same manual says -G prints the configuration after evaluating Host and Match blocks, then exits. It does not open a connection.
In ssh_config(5), Host selects which settings apply to the name you typed. Hostname is the real host to log into, which is how a short name becomes another name or an IP. Port defaults to 22. User is the login name. A set ProxyCommand or ProxyJump means the refusal may be coming from the jump, not from port 22 on the final machine.
ssh -G host | grep -iE '^(hostname|port|user|proxycommand|proxyjump) '
The grep is a filter. If it prints nothing, read the full ssh -G host output and look for those keywords. The manual treats keywords as case-insensitive.
Ask the port, without sending a payload
macOS nc is described in the republished nc(1), not on a developer.apple.com page. -z scans for listening daemons and sends them no data. -v is the verbose switch. The manual’s own example, nc -z host 20-30, prints Connection to … port [tcp/ssh] succeeded! for an open SSH port.
nc -vz host 22
Use the IP or name from the previous section, and the port ssh -v actually tried. On the Mac itself, split the families. That manual also has -4 and -6:
nc -4 -vz 127.0.0.1 22
nc -6 -vz ::1 22
If one family connects and the other refuses, line that up with each Connecting to line. Setting AddressFamily is a client configuration change. It changes which addresses later attempts will use. This page stops at showing you the split.
If nc -z is an illegal option, this machine’s nc is a different program from the manual above. Stay with ssh -v. Installing another netcat is not a step here.
The Mac that is supposed to accept the connection
Run these at the keyboard, or on a screen you already have. They ask whether Remote Login is on and whether anything is listening. Turning the switch on is the later section.
Is Remote Login on?
The republished systemsetup(8) says -getremotelogin displays whether remote login (SSH) is on or off. Apple’s Remote Desktop note says to put sudo in front of systemsetup.
sudo systemsetup -getremotelogin
That flag only reports. The manual attaches “Requires Full Disk Access privileges” to -setremotelogin, not to the get flag.
The current user guide path is: Apple menu, System Settings, General in the sidebar, Sharing (scroll if needed), then the button next to Remote Login. When Remote Login is on, the guide says the sheet shows an SSH command under the On indicator. The same sheet has “Allow full disk access for remote users,” and the guide tells you to turn that on only if you want remote users to have full access to the disk. That option does not start the listener. It is also a different switch from the Full Disk Access permission Terminal needs for systemsetup -setremotelogin.
Who is listening on 22?
sudo lsof -nP -iTCP:22 -sTCP:LISTEN
This lists listening sockets. -n and -P keep addresses and ports numeric, -iTCP:22 selects that TCP port, and -sTCP:LISTEN keeps the LISTEN state. Confirm the flags with man lsof on that Mac. Empty output means nothing is listening there, which matches Connection refused.
Read the Application Firewall, and stop
Firewall settings live under System Settings, Network, Firewall, in Apple’s firewall guide. The same page describes Enable stealth mode as preventing the Mac from responding to probing requests that would reveal its existence. Authorized apps still get answers. Unauthorized requests such as ICMP (ping) get no response.
The republished socketfilterfw(8) has the two queries:
/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate
/usr/libexec/ApplicationFirewall/socketfilterfw --getstealthmode
The current guide’s example of “no response” is ping. It does not say that closed TCP ports omit a RST. Stealth mode therefore lines up with a timeout, the no-answer case, more tightly than with Connection refused. Treating stealth mode as a proven cause of TCP timeouts goes past that page; call that part general guidance. Add sudo only if the query says it cannot read. It is still a read.
The other machine, still read-only
Use the provider’s console, VNC, or a session you already have. When the only SSH session is the one that cannot connect, that session is the wrong place to restart sshd.
Is anything listening?
ss(8): -t is TCP, -l is listening sockets (hidden by default), -n skips service-name lookups, -p shows the process. Seeing the process name takes root.
sudo ss -tlnp | grep -E 'ssh|:22'
Read the address. A listener on 127.0.0.1:22 still produces Connection refused for everyone who is not on that loopback. No line means nothing is accepting that port.
To print the effective config without starting a listener, sshd(8) -T checks the file, writes the effective configuration to stdout, and exits. It includes the extra checks -t performs. The default file is /etc/ssh/sshd_config.
sudo sshd -T | grep -E '^(port|listenaddress) '
If port or listenaddress is not what the client used, point the client at the listener that already exists (ssh -p and the address ss printed). Moving the server back to port 22 can wait until you know why it moved.
Ask this distro what the unit is called
Debian’s sshd(8) says the daemon is normally started at boot from /etc/init.d/ssh. Ubuntu, since 22.10, uses systemd socket activation by default. The units in Ubuntu’s socket-activation notice (Steve Langasek, 24 August 2022) are ssh.socket and ssh.service. That notice says sshd is not started until a connection arrives, which is why an inactive ssh.service can be normal. Check ssh.socket and ss before you treat “inactive” as “nothing listening.”
The same notice is why a Port line you just edited may not be the listener. On 22.10, 23.04, and 23.10, existing Port and ListenAddress settings were migrated to /etc/systemd/system/ssh.socket.d/addresses.conf, except when more than one ListenAddress was set. On 24.04 the notice says a systemd generator pulls the port and address from the sshd configuration. The sentence there names sshd.conf. The file people edit is /etc/ssh/sshd_config, plus sshd_config.d on that machine. General guidance for “I changed Port and 22 still refuses”: compare sshd -T with ss, then read the notice. This page does not include a port-change or “disable the socket” recipe. That thread is full of people locking themselves out.
Red Hat’s SSH access post uses the service name sshd and says port 22 is the default.
Status only, in that order:
systemctl status ssh.socket
systemctl status ssh
systemctl status sshd
A missing unit is an answer. All three commands query.
Firewalls and fail2ban, as reads
The host firewall and the cloud security group are separate layers. General guidance: a rule that drops packets becomes the timeout sentence; a rule that rejects and sends a RST becomes Connection refused. How a given cloud’s default deny behaves is in that provider’s docs. This page did not re-check each console. Changing a security group belongs in the provider’s console, for a specific source address. Opening port 22 to the world is not a step on this page.
On the host, status only:
sudo ufw status
sudo firewall-cmd --list-all
sudo nft list ruleset
Run the one this machine actually has. A missing command means you skip it. Installing a firewall so you can inspect it is a change. nft list ruleset can be long; it still only reads.
fail2ban’s ban action is configuration. A reject action and a drop action become the two different client sentences above. List jails, and leave them in place:
sudo fail2ban-client status
If the command is not found, fail2ban is not on the path. Jail names are whatever this command prints.
Changes, after the read-only pass agrees
Both steps below change who can connect. Do them when the read-only output says the switch is off, or says the config is actually wrong, and when you accept that.
Turn on Remote Login
Apple’s path is the one above: System Settings, General, Sharing, the button next to Remote Login, then turn Remote Login on. The guide says allowing remote login can make the Mac less secure. The listener is for the network, not only for localhost. If you are only connecting out from this Mac, leave the switch as it is.
The command form is in the systemsetup manual: -setremotelogin on|off sets remote login and requires Full Disk Access. Apple’s archived Catalina note, which says it applies to macOS Catalina 10.15 or later and is no longer updated, tells you to give the parent process Full Disk Access first. The click path in that article is still System Preferences, Security & Privacy, Privacy, Full Disk Access. Where today’s System Settings labels differ, search settings for Full Disk Access and add Terminal. This page does not invent a second click path.
sudo systemsetup -setremotelogin on
If the parent process lacks that privilege, the command fails. A privilege failure is not a finding about port 22. The privilege is for Terminal. “Allow full disk access for remote users” on the Sharing sheet is the other switch, and it grants remote users disk-wide access. Fixing Connection refused does not need it.
Older notes load /System/Library/LaunchDaemons/ssh.plist with launchctl. The switch on this page is Remote Login in System Settings. The Chinese incident used sudo launchctl kickstart -k system/com.openssh.sshd; that record stays in 一次ssh排错经历.
After the switch is on, run lsof again and nc -vz 127.0.0.1 22.
On a server, test the file before a restart
sshd(8) says -t only checks the validity of the configuration file and the sanity of the keys, so sshd can be updated when options change. It does not start a listener.
sudo sshd -t
A zero exit status is the gate for a reload or restart. A restart drops the session you are using. Keep the provider console, or keep a second session you are not about to close. Restart the unit systemctl status already identified: ssh is the usual name on Debian and Ubuntu, sshd on the Red Hat family. An unidentified name does not get a restart.
Then repeat ss -tlnp and the client’s nc -vz. The listener and the address the client used have to be the same one.
Whether a new box should expose port 22 at all is a deployment decision, covered in 10 things to confirm before deploying a GitHub project. That article does not clear this error.
When the port starts working, the next sentence may be a host key
After a tunnel or a port forward (frp, cpolar, or a router rule) the TCP connection can succeed and the client can then report a host key mismatch. That is a later sentence. While the message is still Connection refused, leave known_hosts alone.
For Host key verification failed or REMOTE HOST IDENTIFICATION HAS CHANGED, the known_hosts fix command generator builds a command that removes only that host’s old entry, after you check the fingerprint. The tunnel incident is the Chinese post 一次ssh排错经历.
Where this page stops
These are adjacent problems, and they are outside the steps above:
Permission denied (publickey)or a rejected password. Authentication text means the TCP connection was accepted.- A corporate VPN, an HTTP proxy, or an MDM profile. If
ssh -Galready printedproxycommandorproxyjump, finish reading that line. VPN setup is not here. - Designing an IPv6-only network. The
-4and-6checks only show which family this refusal used. - The Windows OpenSSH client. The Chinese incident notes that Windows needs different commands.
- Search volume. This page does not add an external measurement.
Other paths on the site start at Start Here. The Chinese writeup of this same topic is SSH 报 connect to host port 22: Connection refused:从 Mac 连出去,还是连进这台 Mac.
If this post helps, you can sponsor the blog. This is voluntary personal support, not a charitable donation.