← blog

Open, closed, or filtered: reading a port check

You forward a port, restart the service, and the dashboard still says "connection refused." Was the port never open, or is something between you and the world quietly dropping packets? An external port check answers that — but only if you know what its three words actually mean.

What a port check is checking

A port is a number, 1 to 65535, that a machine uses to sort incoming traffic to the right service — 443 for HTTPS, 22 for SSH, and so on. One IP address, many ports, each potentially a different program listening. Strictly, ports are a transport-layer concept: only a protocol like TCP or UDP carries the port number. Cloudflare's explainer on ports is a clean primer if the concept is fuzzy.

An external port check asks one narrow question: if I sit out on the public internet and try to open a TCP connection to this IP on this port, what happens? The answer comes back as one of three states, and the names come from Nmap's port-scanning model, which is the de facto vocabulary for this.

Open, closed, filtered — what each actually means

To read the states, you need the TCP handshake. A client sends SYN. If a service is listening, the host replies SYN-ACK, the client sends ACK, and the connection is established. The three states describe the fate of that exchange.

  • open — the handshake completed. In Nmap's words, "an application is actively accepting TCP connections" on this port. This is the only state that means a service answered.
  • closed — the host is reachable and answered, but with a RST (reset) instead of SYN-ACK. RFC 9293 requires a host to reset a connection to a port with no listener, so the reset is information: the machine is up and the path works, the port is just empty.
  • filtered — Nmap "cannot determine whether the port is open because packet filtering prevents its probes from reaching the port." Usually that means a firewall silently dropped the packet, though an ICMP unreachable error counts too. Either way, you can't tell whether anything is listening.

The difference between closed and filtered is the whole game. A RST is an answer — the host chose to say "no." A dropped packet is the absence of an answer — something upstream decided you don't get to know. A firewall configured to DROP rather than REJECT does exactly this, precisely so a scanner learns as little as possible.

closed means "the door is there and locked." filtered means "there may not even be a door — a wall is in the way."

Why the public internet sees something different than localhost

This is the part that trips people up. curl localhost:8080 working on your own machine tells you nothing about whether the port is reachable from outside, because the packet never left the building. Between your service and the public internet sit several layers that can each change the answer.

  • NAT — your router has one public IP and hands out private addresses (RFC 1918 ranges like 192.168.0.0/16 and 10.0.0.0/8) to devices behind it. Inbound connections die at the router unless you've explicitly forwarded the port.
  • CGNAT — many home and mobile connections sit behind carrier-grade NAT, sharing one public IP across many customers using the 100.64.0.0/10 shared range (RFC 6598). You often can't port-forward at all, because the address you'd forward to isn't yours alone.
  • Firewalls — host-based (ufw, Windows Firewall), cloud security groups, and provider edge filters can each silently drop inbound traffic. Any one of them turns open into filtered from the outside while the service runs fine locally.

So a check from your own laptop and a check from the public internet are genuinely different measurements. The external one is the only one that reflects what a remote client experiences. Our port checker runs that external view: the server dials back to the public IP you connected from, which you can't reproduce from inside your own network. If you want to confirm which public address that even is — useful when CGNAT is in play — the connection inspector shows the IP, reverse DNS, and country we see for your connection.

What an open port implies for attack surface

Every open port is a place where the outside world can talk to a program on your machine. OWASP defines the attack surface as "all of the different points where an attacker could get into a system, and where they could get data out" — and an exposed, listening service is one of the most literal versions of that. An open port isn't a vulnerability by itself, but it's the precondition for one: the service behind it has to be patched, authenticated, and configured, because anyone can now reach it.

This is why "is it open?" is a security question, not just a connectivity one. A database on 5432 that you meant to keep internal but that reads open from the public internet is a finding, not a feature. The honest default is to close what you don't need open, and to verify from outside rather than assume.

Why a responsible checker only probes you

Port scanning arbitrary hosts is the reconnaissance step in plenty of attacks, and depending on where you are, scanning machines you don't own can violate acceptable-use policies or law. The technique itself — generating SYN probes and reading the replies, sometimes without completing the handshake (half-open SYN scanning) — is the same whether the intent is benign or not. What separates the two is authorization.

That's why our checker only probes the address you're connecting from. You can't point it at a server across the internet, because we can't verify you're allowed to scan it. Checking your own connection's reachability is self-evidently authorized; checking someone else's is not, and we'd rather not be the tool that makes it easy.

Limits worth knowing

A single external check is a snapshot of one TCP port at one moment. A filtered result can mean a firewall, or just transient packet loss — Nmap retries for exactly this reason, and a one-shot check can't fully disambiguate. The result reflects the path to whatever IP you presented, which behind CGNAT may not be uniquely yours. And like everything on exl.ink, the check is ephemeral and rate-limited: we run it, hand you the answer, and keep nothing. If we don't keep it, it can't show up later.

Further reading