Check if a port is open on Linux
See what is really listening with ss, then test the port from outside with nc or nmap. Learn why a blocked port hangs while a closed one refuses at once.
Check if a port is open on Linux: pick the right question first
To check if a port is open on Linux, decide first which question you are asking, because "open" means something different in each place you can stand. On the server itself, open means a process is bound to that port and waiting. From another machine, open means a packet reaches that process and gets an answer back. When no answer comes back, the real question is which device dropped the packet. sudo ss -ltnp answers the first question. nc -z or nmap answers the second. Firewall counters and tcpdump answer the third.
Running the wrong check is what costs people an afternoon. A test run on the server never touches your provider's network firewall, because that filter sits outside the box. If port numbers are still new to you, how ports and sockets work on Linux covers the model the rest of this guide assumes.
What is listening on this box? Read the ss output
ss ships with iproute2, so it is present on every current distribution. netstat comes from net-tools, which Ubuntu has not installed by default for years, so netstat -tulpn often answers netstat: command not found. Learn ss and skip the disappointment.
sudo ss -ltnp-l shows listening sockets only. -t limits the list to TCP. -n prints numbers instead of resolving names, so the command returns at once. -p names the owning process, and it needs root: without sudo the Process column is empty for every process you do not own. Swap -t for -u to see UDP.
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=612,fd=14))
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=921,fd=3))
LISTEN 0 511 127.0.0.1:8080 0.0.0.0:* users:(("node",pid=1442,fd=19))
LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=921,fd=4))The Local Address column decides everything, and it is the column people skim past.
0.0.0.0:22means every IPv4 address on the machine, so it is reachable from outside if the firewall allows it.[::]:22is the same thing for IPv6.127.0.0.1:8080means loopback only. Nothing outside this machine can reach it.10.20.0.5:5432means that one interface address and no other, which is common on private network setups.- An empty Process column is usually a missing
sudo, not a missing process.
To ask about one port, filter inside ss instead of grepping the whole list:
sudo ss -ltnp 'sport = :8080'
sudo lsof -nP -iTCP:8080 -sTCP:LISTEN
sudo fuser 8080/tcpEmpty output from all three means nothing holds that port. The service is stopped, it failed to start, or it is listening somewhere else. Read systemctl status <unit> and journalctl -u <unit> -n 50 before you touch a single firewall rule.
Why 127.0.0.1 in Local Address costs people an afternoon
A socket bound to 127.0.0.1 cannot be reached from another host, and no firewall rule changes that. The kernel routes 127.0.0.0/8 to the loopback interface only, and a packet carrying that destination address arriving on a real network card is discarded as a martian. So the process runs, ss shows it listening, ufw allow 8080 reports success, and the connection from your laptop still fails. It fails with an instant Connection refused, because the packet reaches your public address, finds no socket bound there, and the kernel answers with a TCP reset.
Plenty of programs bind to loopback on purpose, and for a database or an admin interface that is the right default. You have two honest choices. Change the bind address in the program's own config (listen_addresses in postgresql.conf, bind in redis.conf, the host argument your application takes) and then open the firewall. Or leave it on loopback and reach it through something else, such as an nginx reverse proxy, or an SSH tunnel from your laptop:
ssh -L 8080:127.0.0.1:8080 user@203.0.113.10Docker carries the same distinction in its publish flag. -p 8080:8080 binds 0.0.0.0 and exposes the container to the internet. -p 127.0.0.1:8080:8080 binds loopback and keeps it local.
How to check if a port is open on Linux from another machine
Run this test from a different network. A test from the server proves only that the loopback path works. Even connecting to your own public IP from the server skips your provider's network firewall, because that filter runs outside the VPS.
nc -zv -w 3 203.0.113.10 443-z connects and closes without sending data. -w 3 gives up after three seconds, and that flag matters: with no timeout, a dropped packet leaves the client retrying the SYN for more than two minutes before the kernel stops. Success looks like this:
Connection to 203.0.113.10 443 port [tcp/https] succeeded!If the tool is missing (nc: command not found), install netcat-openbsd on Debian or Ubuntu, or use bash's built in network redirection, which needs no package at all:
timeout 3 bash -c '</dev/tcp/203.0.113.10/443' && echo open || echo "no answer"That syntax is a bash feature, so run it with bash. /bin/sh on Debian and Ubuntu is dash, which has no /dev/tcp and reports that the path does not exist. For a range of ports, or when you want the state named for you, use nmap against hosts you are responsible for:
sudo nmap -Pn -p 22,80,443 203.0.113.10
sudo nmap -Pn -p 1-1024 203.0.113.10-Pn skips host discovery. Most VPS hosts drop ICMP echo, so without -Pn nmap decides the host is down and scans nothing. nmap prints open when something answered and accepted the connection, closed when something answered with a reset, and filtered when nothing answered at all. For a web service, curl -sS -o /dev/null -w '%{http_code}\n' https://example.com separates a network fault from an application fault, because a status code proves the whole path worked.
Why a blocked port hangs and a closed port refuses at once
An instant refusal. The packet reached the machine and something answered it.
nc: connect to 203.0.113.10 port 443 (tcp) failed: Connection refusedTwo different causes produce that exact line. Either no process is bound to that address and port, so the kernel replied with a TCP reset, or a firewall rule rejected the packet with a reset or an ICMP port unreachable message. A refusal is a definite answer and it comes back in one round trip.
A pause, then a timeout. Something dropped the packet and said nothing back.
nc: connect to 203.0.113.10 port 443 (tcp) timed out: Operation now in progressThat is what a DROP rule does, and it is what a provider firewall or a cloud security group does. Silence is the signature of a drop, because the sender cannot tell a drop from a dead host.
The symptom tells you where to look next. Refused means packets are crossing the network fine, so go back to ss -ltnp and check the bind address and the port number. Timed out means packets are being dropped, so read the firewalls from the outside in. refused versus timed out on SSH takes the same split apart for port 22, which is where most people meet it.
ufw offers both behaviours on purpose: ufw deny 8080 drops, and ufw reject 8080 sends a rejection. In nftables the two targets are drop and reject, and in iptables they are -j DROP and -j REJECT. Default policies are almost always a drop, which is why a missing rule produces a hang rather than an error message.
Who is blocking the port? Work from the outside in
sudo ufw status verbose
sudo iptables -L INPUT -n -v --line-numbers
sudo nft list rulesetThe -v counters are the useful part. Run your nc test from outside, run the iptables command again, and look for the counter that moved: the rule whose packet count grows is the rule handling your traffic. That replaces guessing with evidence.
The decisive test runs on the server, watching the wire while you connect from outside:
sudo tcpdump -ni any tcp port 8080A SYN arriving with no SYN-ACK going back means the packet reached your VPS and the host dropped it, so the provider firewall is fine and your local rules are not. No output at all means the packet never arrived, so the provider firewall, a security group, or a wrong IP address is the cause. That one distinction removes most of the work.
Two layers produce results that look impossible. First, IPv6: if the hostname has an AAAA record, your client may connect over IPv6 while your rule only covers IPv4, so test each family with nc -4 and nc -6 before you trust either result. ufw rules and IPv6 ports on a VPS covers that mismatch. Second, Docker: a published container port answers from the internet even while ufw status lists that port as denied, because those packets are handled before ufw's chain ever sees them. why Docker publishes ports straight past ufw has the mechanism and the fix, and the ufw rules to set on a new VPS is the base set worth having first.
Why UDP answers are ambiguous by design
UDP has no handshake, so a probe has nothing to succeed at. nc -zu 203.0.113.10 53 exits 0 as soon as the packet leaves, which proves your own machine sent it and proves nothing about the far end. When a UDP port is closed, the host normally replies with an ICMP port unreachable message, and the kernel reports that error to a connected socket only on the next write, so a single packet probe misses it. Firewalls drop ICMP as a matter of habit, which removes even that clue. This is why nmap prints open|filtered for most UDP ports: no reply is exactly what an open silent service and a filtered port both produce.
Test UDP by speaking the protocol you care about. A DNS server answers dig +short @203.0.113.10 example.com with an address or nothing. A WireGuard peer shows a recent latest handshake line in sudo wg show. Then prove arrival on the server side:
sudo tcpdump -ni any udp port 51820Packets appearing while the client sends means they arrive, so the service or the input chain is the problem. No packets at all means they never got there.
A checklist in the order that finds the fault fastest
- On the server, run
sudo ss -ltnp 'sport = :8080'. No output means nothing is listening, so fix the service first. - With output, read the Local Address column.
127.0.0.1means outside access is impossible until you rebind or put a proxy in front. - From another network, run
nc -zv -w 3 <public ip> 8080. - A refusal sends you back to step 1. The address, the port, or the machine is not the one you think it is.
- A timeout means a drop. Start
sudo tcpdump -ni any tcp port 8080on the server and repeat the test. - SYN arrives, nothing goes back: the host firewall. Find the rule whose counter moves in
sudo iptables -L INPUT -n -v. - No SYN arrives: the provider firewall, a security group, or the wrong IP address.
FAQ
How do I check which ports are open on my own Linux server?
Run sudo ss -ltnp for TCP and sudo ss -lunp for UDP. Every line is one listening socket, and the Local Address column tells you who can reach it: 0.0.0.0 and [::] accept from anywhere the firewall permits, while 127.0.0.1 accepts only from the machine itself. The Process column needs root, so run it with sudo or it comes back empty. ss is part of iproute2 and is always installed; netstat is part of net-tools and usually is not.
Why does ss show my service listening but I still cannot connect?
There are two common causes and one command separates them. If the Local Address is 127.0.0.1, the service is bound to loopback and unreachable from any other host, because the kernel only routes that range to the loopback interface. If it is 0.0.0.0 and connections still fail, run sudo tcpdump -ni any tcp port <port> on the server and connect from outside. A SYN arriving with no reply means a local firewall rule is dropping it. Nothing arriving means the packet is being stopped before your VPS, usually by a provider firewall or a security group.
What is the difference between a connection that is refused and one that times out?
A refusal is an answer. The packet reached the host and got a TCP reset or an ICMP port unreachable back, which means nothing is listening on that address and port, or a rule rejected it. A timeout is silence: a rule dropped the packet and sent nothing, so your client retries until it gives up. Refused points you at the service and its bind address. Timed out points you at a firewall, and the firewall closest to the outside is the one to check first.
How do I check if a UDP port is open?
You cannot get a reliable yes from a generic probe, because UDP has no handshake and a silent service looks the same as a dropped packet. nc -zu returns success as soon as it sends, and nmap reports open|filtered for the same reason. Test by speaking the protocol instead: dig +short @<host> example.com for DNS, or sudo wg show for a WireGuard peer with a recent handshake. To prove packets arrive, run sudo tcpdump -ni any udp port <port> on the server while the client sends.
Can I still use telnet host port to test a port?
It works for TCP, and Escape character is '^]' means the connection was accepted. Leave it with Ctrl+] and then quit. Two things make nc -z the better tool: telnet is not installed on most current server images, and nc takes a timeout with -w and sets an exit status you can test in a script. When neither is available, timeout 3 bash -c '</dev/tcp/<host>/<port>' needs no package at all.