A TCP port is reachable when the three-way handshake completes: your machine sends a SYN, the server answers with a SYN-ACK. Every tool below does that one thing and reports how it went. Run them from the machine that has the problem, since a port can be open to one network and closed to another.
Test the port with nc
-z connects and closes again without sending data, -v prints the result, and -w 3 gives up after 3 seconds. Debian and Ubuntu install netcat-openbsd:
$ nc -zv -w 3 db.example.net 5432Connection to db.example.net (203.0.113.10) 5432 port [tcp/postgresql] succeeded!$ nc -zv -w 3 db.example.net 5433nc: connect to db.example.net (203.0.113.10) port 5433 (tcp) failed: Connection refused$ nc -zv -w 3 db.example.net 6379nc: connect to db.example.net (203.0.113.10) port 6379 (tcp) timed out: Operation now in progress
RHEL 9 ships Ncat from the nmap project as nc (package nmap-ncat). It takes the same options and words the result differently:
$ nc -zv -w 3 db.example.net 5432Ncat: Version 7.92 ( https://nmap.org/ncat )Ncat: Connected to 203.0.113.10:5432.Ncat: 0 bytes sent, 0 bytes received in 0.02 seconds.$ nc -zv -w 3 db.example.net 6379Ncat: Version 7.92 ( https://nmap.org/ncat )Ncat: TIMEOUT.
Both exit with 0 when the connection succeeded and 1 when it didn’t, so they work in scripts:
$ nc -z -w 3 db.example.net 5432 && echo open || echo closedopen
Without nc installed
Bash can open a TCP connection itself through the path /dev/tcp/host/port. It only works in bash, not in sh or dash, and needs timeout to stop it waiting on a port that doesn’t answer:
$ timeout 3 bash -c '</dev/tcp/db.example.net/5432' && echo open || echo closedopen
curl is installed on most servers and prints each step. With the telnet:// scheme it connects and waits for you to type, so any port works. Press Ctrl+C once it says Connected.
$ curl -v --connect-timeout 3 telnet://db.example.net:5432* Trying 203.0.113.10:5432...* Connected to db.example.net (203.0.113.10) port 5432
Ports that speak TLS
For HTTPS and other TLS ports, a completed handshake only proves the first step. Make a real request, so you see the TLS handshake and the server’s answer too:
$ curl -sv -o /dev/null https://app.example.net/$ openssl s_client -connect app.example.net:443 -servername app.example.net </dev/null
What the certificate in that output says, and how to read it, is in the article on checking a certificate with openssl.
What the result tells you
| Result | What happened | Look next at |
|---|---|---|
| succeeded, Connected | The handshake completed. Something listens, and nothing on the way blocks it. | The application and its log |
| Connection refused | The host answered with a reset. Nothing listens on that port, or a firewall rule rejects with a TCP reset. | ss -tlnp on the server, then its firewall |
| timed out, TIMEOUT | No answer at all. A firewall drops the packets, the route is wrong, or the host is down. | Firewalls on the way, including your provider’s |
| No route to host | An ICMP “host unreachable” came back. firewalld sends this for traffic its zone doesn’t allow. | firewall-cmd --list-all on the server |
| Name or service not known | The name didn’t resolve. Nothing was sent. | The name with dig |
Test from both ends
If the test fails from outside, run it on the server itself, first against 127.0.0.1 and then against the address clients use:
$ nc -zv -w 3 127.0.0.1 5432$ nc -zv -w 3 10.0.0.5 5432
If the first one succeeds and the second is refused, the service listens on 127.0.0.1 only. ss shows the address it’s bound to (Find which process is listening on a port). If both succeed on the server but the test from outside times out, a firewall in between drops the traffic.
Check the server’s firewall
Each distribution has its own front end. They all end up as nftables rules in the kernel.
# Debian 12: nftables directly$ sudo nft list ruleset# Ubuntu 24.04: ufw$ sudo ufw status verbose# RHEL 9: firewalld$ sudo firewall-cmd --list-all
Open the port to the network that needs it, rather than to everyone:
$ sudo ufw allow from 10.0.0.0/24 to any port 5432 proto tcp$ sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/24" port port="5432" protocol="tcp" accept'$ sudo firewall-cmd --reload
UDP ports need their own client
UDP has no handshake, so a connection test can’t prove anything. nc -zu reports success when no answer comes back, which is also what happens when a firewall drops the packet. Test a UDP service with a client that speaks its protocol, for example dig @203.0.113.10 example.net for DNS on port 53.
From a Windows machine
PS> Test-NetConnection db.example.net -Port 5432ComputerName : db.example.netRemoteAddress : 203.0.113.10RemotePort : 5432InterfaceAlias : EthernetSourceAddress : 10.0.0.21TcpTestSucceeded : True
More PowerShell checks are in the Windows Server category.
Last checked on 24 October 2026 against Debian 12, Ubuntu 24.04, RHEL 9, netcat-openbsd 1.219+, nmap-ncat 7.92, and curl 7.76+. If a step doesn’t match what you see, contact us with the command and what it printed.