Skip to content
ohsudo, Home
Networking

"Connection timed out" vs "Connection refused": what each one tells you, and how to fix it

Timed out means a firewall dropped the packets; refused means nothing listens there. Tell them apart with nc and curl, then fix ufw, firewalld or the bind address.

Beginner5 min readApplies to Linux · Ubuntu (ufw) · RHEL / Rocky / AlmaLinux (firewalld) · cloud security groups · any TCP client

by OhSudo ··

Not sure this is your cause?Find it step by step: Website down →Find it step by step: Can't connect over SSH →

You can’t reach a service on another machine, and the error is one of these:

curl: (28) Failed to connect to app01 port 8080 after 135168 ms: Couldn't connect to server
ssh: connect to host app01 port 22: Connection timed out
curl: (7) Failed to connect to app01 port 8080 after 2 ms: Couldn't connect to server
nc: connect to app01 port 8080 (tcp) failed: Connection refused

They look alike, but they point to opposite ends of the problem. Refused comes back in milliseconds: your packet reached the machine, and the machine answered “nothing listens on this port” (a TCP reset). Timed out takes seconds or minutes: nothing answered at all — something on the way dropped the packet, usually a firewall. A third answer, No route to host, often means a firewall that rejects the packet out loud.

You see What happened Look at
Connection refused The machine answered: nothing listens on that address and port The service: is it running, on which port, on which address?
Connection timed out No answer: the packets were dropped Firewalls: on the server (ufw, firewalld, iptables), in the cloud (security groups), on the network
No route to host A firewall rejected it (or the host is down on the local network) firewalld on RHEL/Rocky, REJECT rules
  1. Clientconnects to app01:8080
  2. app01nothing listens: answers with a reset(breaks here)
Refused: the packet arrived, and the machine answered at once that nothing listens on that port.
  1. Clientconnects to app01:8080
  2. A firewalldrops the packet(breaks here)
  3. app01never sees it
Timed out: something on the way dropped the packet, so no answer ever comes back.

TL;DR

Terminal window
nc -zv -w 5 app01 8080 # timed out / refused / succeeded, in 5 seconds at most
nc -zv -w 5 app01 22 # is the machine itself reachable?
sudo ss -tlnp | grep 8080 # on the server: listening? on 127.0.0.1 or 0.0.0.0?
sudo ufw status # Ubuntu
sudo firewall-cmd --list-all # RHEL / Rocky / AlmaLinux

1. Test from the client, with a short timeout

Without a limit, a timed-out connection hangs for about two minutes (the kernel retries the first packet several times). Give your test tools a few seconds instead:

Terminal window
curl -m 5 http://app01:8080/health
nc -zv -w 5 app01 8080

Then test a port you know works — SSH is usually open:

Terminal window
nc -zv -w 5 app01 22

If port 22 answers and 8080 times out, the machine is up and the network is fine: something drops traffic to that port only. That’s a firewall rule, almost always.

2. Check the service on the server

Log in to the server and look at what really listens:

Terminal window
ssh app01
ss -tln | grep 8080
curl -s http://localhost:8080/health
LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:*

Two things to read here. The service runs, and it answers locally — so the app itself is fine. And it listens on 127.0.0.1: only programs on the same machine can reach it. Keep that in mind; it will come back in section 4. (Use sudo ss -tlnp to also see which process holds the port.)

3. Timed out: the firewall drops it

Ubuntu / Debian — ufw. With ufw active, everything that isn’t allowed is dropped: the client waits and times out.

Terminal window
ssh app01
sudo ufw status
sudo ufw allow 8080/tcp
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)

Better still, allow only who needs it: sudo ufw allow from 10.0.0.0/24 to any port 8080 proto tcp.

RHEL / Rocky / AlmaLinux — firewalld. The default public zone rejects what isn’t allowed, so the client gets No route to host instead of a timeout:

Terminal window
ssh app01
sudo firewall-cmd --list-all
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload

--permanent alone changes the saved configuration but not the running one, and a rule added without --permanent works only until the next reload or reboot. Add it with --permanent, then --reload — as above.

Note

In the cloud there’s a second firewall. AWS security groups, Azure network security groups and GCP firewall rules sit in front of the machine and drop traffic silently too. If the server’s own firewall allows the port and you still time out, allow the port there — and only from the addresses that need it.

Is the port one that should be reachable from outside at all? Databases, Redis or the Docker API should not — the Port Lookup tells you, port by port.

4. Refused: nothing listens on that address

After opening the firewall, try again from the client:

Terminal window
ssh app01 sudo ufw allow 8080/tcp
curl -m 5 http://app01:8080/health
curl: (7) Failed to connect to app01 port 8080 after 0 ms: Couldn't connect to server

Progress: the packets now reach the machine, and the machine answers — “refused”. Remember section 2: the app listens on 127.0.0.1, so connections that arrive on the network interface find nothing there. Make it listen on all addresses (0.0.0.0), or on the server’s private address. Where that’s set depends on the app: a --bind/--host option, listen in nginx, listen_addresses in PostgreSQL, bind in Redis. Here it’s a config file:

Terminal window
ssh app01 cat /etc/myapp/config.env
ssh app01
sudo sed -i "s/^BIND_ADDRESS=.*/BIND_ADDRESS=0.0.0.0/" /etc/myapp/config.env
sudo systemctl restart myapp && ss -tln | grep 8080
exit
curl -m 5 http://app01:8080/health

ss now shows 0.0.0.0:8080, and the request from the laptop gets its answer.

Other reasons for “refused”: the service isn’t running at all (systemctl status, see Job for … failed), it listens on a different port than you think, or you’re connecting to the wrong machine (an old DNS record, a typo in the IP). The same applies to databases: see MySQL, SQL Server and MongoDB.

5. Inside containers

The same rules apply one level down. A container that publishes -p 127.0.0.1:8080:80 is reachable only from the host itself; an app inside a container that listens on 127.0.0.1 is unreachable even through a published port — it must listen on 0.0.0.0 in the container. And from inside a container, localhost is the container, not the host: see connecting from a container to localhost.

Shell, we have a problem!

Similar, but different: Address already in use is the server side — your service can’t start because another one holds the port: see who is using port 80. No route to host is usually a firewall that rejects: see that article. And Could not resolve host means the name never became an IP address: a DNS problem, see Temporary failure in name resolution.

$ ./practice.sh

Practice this in the terminal

Open the Terminal Playground with this article’s scenario loaded. It is safe and runs in your browser.

Open the terminal →

Found this useful? Share it.

Found a mistake? Let us know at contact@ohsudo.com.

Tools

Try it yourself

Articles

Related articles