You SSH into a fresh server, type telnet 443 out of muscle memory, and the shell replies that the command is not installed. There is a moment where you almost install telnet just to see if a port is open. That moment is what this article is about, because Linux already has four tools that do the same job better, and none of them are telnet.
Telnet has been removed from most modern distributions because it sends everything in plaintext, including passwords. That made it useless for remote administration, but it had a quiet second life as a quick way to check whether something was listening on a port. Modern distributions solved the security problem by removing telnet and forgot to mention that the port-testing job has better answers. nc, nmap, ss, and a one-liner in Bash cover every realistic port-checking scenario, and each one gives you more information than the old telnet trick ever did.
What a successful port check actually proves
Before walking through the tools, it helps to be clear about what a port check tells you. When nc reports succeeded for a TCP connection to port 22, it means the target host accepted the TCP handshake on that port. That is the only thing it proves. The SSH daemon could be crashed, half-configured, or hung in a way that still completes the handshake. A successful TCP connection is a precondition for a service being healthy, not proof that the service is responding to its protocol. For HTTP and HTTPS, the cleanest confirmation is a protocol-aware test with curl or wget. Anything beyond TCP-open is application-layer and needs the application’s own client.
People see nc report success and assume the service is fine. People see nc report timed out and assume the service is down. Both readings are wrong. The service might be down with a closed port, or up but filtered by a firewall, or up and listening but not responding to its protocol. Each of those states needs a different tool.
The four tools, in plain order of when to use them
ncfor one port, one host. Fastest path to a yes-or-no answer about a single TCP port. Default behavior isnc -zv, which performs a connection check without sending any application data and prints the verdict. For UDP,ncis the wrong tool (see the UDP section below).- Bash and
/dev/tcpfor a no-install check. Useful on locked-down servers where you cannot run a package manager. The check is one line wrapped intimeout, and the answer is in the exit code. nmapfor ranges, multiple ports, or richer diagnostics. When you need to scan a block of ports or see the difference between a closed port and a filtered port,nmapis the tool. It also distinguishesopen,closed,filtered, and a few other states thatncdoes not report.ssfor the local machine. When the question is whether your own server is listening on a port,ssanswers it directly.ss -tulnpshows every listening TCP and UDP socket along with the process that owns it. This is the cleanest answer to “the service is running but I cannot connect to it.”
For most debugging sessions, two of these are enough. nc answers whether the remote host is reachable on the port. ss answers whether the local host is listening on it. Adding nmap only becomes necessary when the two contradict each other and you need the intermediate states.
nc is the closest telnet replacement
The package name is the first thing that trips people up. On Debian and Ubuntu, the package is netcat-openbsd. On RHEL, Rocky Linux, and Fedora, it is nmap-ncat. Both packages ship a binary called nc, and the basic options are the same, but a few flags differ between the OpenBSD variant and the traditional one. If a flag does not work, run nc -h to see which version is installed.
The canonical remote port test is:
nc -zv 192.168.1.50 22
The three flags do specific things. -z performs the connection check without sending any data, which is what makes this safe to run against services that are not telnet. -v enables verbose output so you get a result line instead of silence. The IP and the port are the only required arguments.
Three results are possible:
Connection to 192.168.1.50 22 port [tcp/ssh] succeeded!means the host accepted the TCP connection. Something is listening.nc: connect to 192.168.1.50 port 8080 (tcp) failed: Connection refusedmeans the host responded but no service accepted the connection. Usually nothing is listening, though firewalls can also produce this message.nc: connect to 192.168.1.50 port 8080 (tcp) timed outmeans no response arrived before the connection timed out. A firewall silently dropping packets is the most common cause, but routing problems, ACLs, and unreachable hosts can produce the same symptom.
When nc reports a successful connection, that is only the first step. To verify the service is actually responding, use curl for HTTP and HTTPS. For SSH, the simplest sanity check is ssh -o BatchMode=yes -o ConnectTimeout=5 user@host echo OK. For databases, use the database’s own client. None of those need telnet.
Bash and /dev/tcp when you cannot install anything
Some servers have package managers disabled or locked down by policy. On those machines, the right tool is the one that is already in the shell. Bash has a special redirection target at /dev/tcp/host/port that opens a TCP connection when you write to it. Wrapping that in timeout and an exit-code test gives a one-liner that does not need any binary:
timeout 3 bash -c "echo > /dev/tcp/192.168.1.50/22" && echo "Port open" || echo "Port closed"
The pieces work like this. timeout 3 caps the test at three seconds, so a filtered port cannot leave you hanging. bash -c runs the inner command explicitly in Bash, because /dev/tcp is a Bash feature and will not work under dash or sh. echo > /dev/tcp/host/port triggers Bash to attempt the TCP connection. The && and || chain print a readable verdict based on the exit code.
Two limitations to keep in mind. The trick is Bash-specific and will fail silently under /bin/sh on systems where that points to dash. Some hardened Bash builds disable network redirections. In both cases, fall back to nc or nmap if you can install either.
nmap is the tool for ranges and richer state
nc answers yes-or-no for a single port. nmap answers the same question for a list or a range, and it distinguishes states that nc lumps together:
nmap -p 22,80,443,3306 192.168.1.50
The output includes a STATE column with values worth knowing:
openmeans a service is accepting connections.closedmeans the host responded but nothing is listening. This is the most common state for ports the server never opened.filteredmeans nmap could not determine whether the port is open because packet filtering prevented a definitive response. This is the firewall state.
The distinction between closed and filtered is the practical reason to keep nmap around. If you see 3306/tcp filtered mysql, restarting MySQL will not help. A firewall or a security group is blocking the traffic before it reaches the server. Restarting the database in that state is wasted effort and may take the working service down.
For a wider scan, give nmap a range:
nmap -p 1-1000 192.168.1.50
This walks ports 1 through 1000 and produces the same per-port STATE breakdown. On a busy host this takes a couple of minutes; on a quiet one it finishes in seconds.
ss is what shows the local listening sockets
Every tool so far tests a port on a remote host. The other half of “is this port open” is the local question, and the right tool is ss:
sudo ss -tulnp
The options are short and worth memorizing:
-tshows TCP sockets.-ushows UDP sockets.-llimits the output to sockets that are listening, which is what you almost always want.-ndisplays numeric addresses and ports instead of resolving service names.-pshows the owning process.sudois often needed to see processes owned by other users.
The Local Address column answers the classic “the service is running but I cannot connect to it” puzzle. A line like 127.0.0.1:3306 means MariaDB is listening only on the loopback interface. Connections from the same machine can reach it; remote clients cannot. By contrast, 0.0.0.0:22 means SSH is listening on every IPv4 interface and will accept connections arriving through any of them.
When you only care about one port, pipe through grep:
sudo ss -tulnp | grep :3306
This is the single most useful command for answering “what is listening on this machine, and which process owns the port.” It catches services bound to the wrong interface, daemons that crashed without exiting, and duplicate processes holding the port.
UDP needs a different approach
Everything above is TCP. UDP is harder, because UDP has no handshake. There is no equivalent of nc‘s -z for UDP, and a successful UDP test does not prove much. For UDP, the safest answer is a dedicated protocol-aware client for the specific service you are testing (a DNS resolver for DNS, an NTP client for time, and so on). For broader UDP port checks, nmap‘s UDP probing is the closest general-purpose tool, but expect open|filtered results when the service does not reply to unsolicited packets.
Putting the four together
The order for a debugging session is short. Confirm the remote port with nc -zv. If it reports success and you still cannot connect, check the local machine with sudo ss -tulnp | grep :port. If the local side looks right but the remote side is filtered, run nmap -p port host. For one-off scripts and CI checks, the Bash /dev/tcp trick is the lightest option. For bulk scanning, nmap is the only one of the four that scales.
Trade-offs
nc is not free in time. On a filtered port with no timeout, the tool waits for the operating system’s connection timeout, which can be tens of seconds. The fix is the -w flag (nc -zvw 3 host port waits up to three seconds and gives up).
Bash and /dev/tcp is the right tool only when no package manager is available. The technique is Bash-only, and it does not give you the richer states nmap reports. If you can install one binary, nc is the better default.
nmap is heavier than nc and produces more output than you usually want. It also triggers intrusion detection systems on some networks, which is worth knowing before running a wide scan on infrastructure you do not own.
ss requires sudo to show process names for sockets owned by other users. Without sudo, the process column shows only the PID and a numeric UID, which is enough to identify the process by ps but less convenient.
None of these tools can confirm a service is healthy at the protocol level. TCP-open is a precondition, not a result. For HTTP, run curl. For SSH, run a real SSH command. For databases, use the database client. The cleanest test is always the one the service wants to be talked to.
Bottom line
If you only do one thing from this article, install nmap and nc on whatever servers you administer, and learn sudo ss -tulnp. Between those three commands, you can answer almost every “is the port open” question in under ten seconds. Skip telnet entirely.