In short. The port your own service listens on is shown by ss -tulpn on Linux, netstat -ano on Windows and lsof -nP -iTCP -sTCP:LISTEN on macOS. Which port a host answers on can only be learned by connecting to it — with a port scanner. Standard numbers live in /etc/services and the IANA registry. A proxy port comes from your access string, never from a list of "typical" values.
"How do I find a server port" hides two different questions that need different tools and produce different answers. The first is which port my process grabbed: you answer that from inside the machine, where you have root and the kernel socket table. The second is which port a host at some address responds on: you answer that from outside, by attempting a connection. Mixing them up is the single most common reason people stare at correct output for half an hour without finding what they need. Both are covered below, plus proxy ports and the classic trap: a port that listens but is invisible from the network.
A service port and a host port are two different questions
A port is not a property of a server. It is a number a specific process asked the kernel for so it could accept connections. That means "the server port" only exists as a pair: the address the process listens on, plus the number. A single server holds dozens of such pairs at once, and roughly half of them are unreachable from outside by design.
| Question | Vantage point | OS access needed | Tool | What you get |
|---|---|---|---|---|
| Which port does my service listen on | Inside the server | Yes, root preferred | ss, lsof, netstat | Full list: address, port, protocol, process, PID |
| Which process took port 8080 | Inside the server | Yes, root required | ss -tlnp, lsof -i, fuser | Process name and PID |
| Which port does a host answer on | Outside, over the network | No | Port scanner, nc, curl | Exactly three outcomes: answers, refuses, silence |
| Which port should a protocol use | Reference | No | /etc/services, IANA registry | A registered number is a convention, not a fact |
The difference matters. From inside you see ground truth: the kernel hands over the socket table and there is nothing to guess. From outside you only see a reaction to a connection attempt, and that reaction can lie — a firewall can refuse on behalf of someone else's closed port, and it can stay silent in front of an open one.

How to find which port your service listens on in Linux
ss is the primary tool
ss ships in the iproute2 package and is present on every modern distribution by default. It reads straight from the kernel and is noticeably faster than the old netstat on machines with thousands of connections.
# every listening TCP and UDP socket, with processes, no name resolution
ss -tulpn
# same thing, TCP only
ss -tlnp
Typical output looks like this:
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
udp UNCONN 0 0 127.0.0.1:323 0.0.0.0:* users:(("chronyd",pid=1644,fd=5))
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=2498,fd=7))
tcp LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=2498,fd=8))
tcp LISTEN 0 65535 127.0.0.1:3000 0.0.0.0:* users:(("dockerd",pid=2489,fd=115))
tcp LISTEN 0 65535 0.0.0.0:443 0.0.0.0:* users:(("dockerd",pid=2489,fd=111))
Read the Local Address:Port column, and read all of it. 0.0.0.0:443 means listening on every IPv4 address — reachable from the network. [::]:22 is the same for IPv6. But 127.0.0.1:3000 listens on the loopback interface only: the port works from the machine itself and does not exist from the network. That is not a detail — it accounts for roughly half of all "the port is open but nothing answers" tickets.
| Flag | What it does |
|---|---|
-t | TCP only |
-u | UDP only |
-l | Listening sockets only, no established connections |
-p | Show the process and PID holding the socket |
-n | Do not resolve ports to names or addresses to hostnames — faster and unambiguous |
-a | All sockets, including established connections |
ss has its own filter language, which spares you from grep and its substring mistakes (grep :22 also matches ports 2222 and 8022):
# who listens on one specific port
ss -tlnp 'sport = :22'
# every established connection with its process
ss -tnp state established
# connections with one peer only
ss -tnp 'dst 203.0.113.10'
Without root theProcesscolumn comes back empty. Run as an ordinary user,ss -tulpnhonestly lists the ports but not who holds them: the kernel does not expose that mapping without privileges. If processes are missing, you are not looking at a bug — addsudo.
Why netstat says "command not found"
netstat lives in the net-tools package, which many distributions stopped installing by default long ago. On current RHEL-family systems (AlmaLinux, Rocky, CentOS Stream) and in minimal Debian and Ubuntu images it is simply absent — not broken, but deliberately dropped: net-tools is considered legacy and gets no new features.
# AlmaLinux / Rocky / RHEL / Fedora
sudo dnf install net-tools
# Debian / Ubuntu
sudo apt install net-tools
Installing it for one command is rarely worth it — everything has an ss equivalent:
| Task | netstat (net-tools) | ss (iproute2) |
|---|---|---|
| Listening TCP and UDP with processes | netstat -tulpn | ss -tulpn |
| All TCP connections | netstat -tan | ss -tan |
| Established only | netstat -tan | grep ESTABLISHED | ss -tn state established |
| Per-protocol statistics | netstat -s | ss -s |
| Routing table | netstat -rn | ip route show |
The -tulpn flags are identical in both, so there is almost nothing to relearn: swap one word.
lsof, when you need the process-side view
lsof treats a socket as an open file. That helps when the question is not "who is on this port" but "what has this process opened".
# every listening TCP socket
lsof -nP -iTCP -sTCP:LISTEN
# everything happening on one port, established connections included
lsof -nP -i :443
# network activity of one process
lsof -nP -p 2498 -a -i
-n and -P disable resolution of addresses and port numbers into names. Without them lsof prints ssh instead of 22 and issues a DNS lookup per address, which turns an instant command into a minute of waiting on a busy server.
How to find the process by port
The classic case: a port is taken and your application will not start. Three ways, pick any:
# 1. via ss — exact filter, no false matches
ss -tlnp 'sport = :8080'
# 2. via lsof — shows the listener and active connections
lsof -nP -i :8080
# 3. via fuser — shortest, prints PIDs only
fuser -n tcp 8080
With a PID in hand, find out what it is and who manages it:
ps -p 2498 -o pid,user,comm,args --no-headers
# 2498 root sshd sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
# which systemd unit owns the process
systemctl status 2498
That last command saves the most time: systemctl status <PID> accepts a process number and prints the unit name. From there you know what to restart and where the config lives.
How to find the port by process
The reverse case: the service runs, but on which port is unclear.
# find the PID by name
pgrep -a nginx
# list its network sockets
lsof -nP -p 1234 -a -i
# or filter ss output by PID
ss -tulpn | grep 'pid=1234'
If the process runs inside a container, the host shows youdockerdorcontainerdinstead — whichever runtime published the port. That is expected: the runtime publishes the port, not the application. To look inside, rundocker exec <name> ss -tulpn, assuming the image shipsiproute2.
How to find a server port on Windows and macOS
Windows
On Windows netstat is alive and well. The -o switch adds the PID column — without it the output is nearly useless.
:: all connections and listening ports with process IDs
netstat -ano
:: listening only
netstat -ano | findstr LISTENING
:: who took a specific port
netstat -ano | findstr :8080
:: what that PID actually is
tasklist /FI "PID eq 1234"
PowerShell solves the same problem without text parsing — the cmdlets return objects you can filter:
# every listening port
Get-NetTCPConnection -State Listen | Sort-Object LocalPort
# who took port 8080, with the process name resolved
Get-NetTCPConnection -LocalPort 8080 |
Select-Object LocalAddress, LocalPort, State, OwningProcess,
@{Name='Process'; Expression={(Get-Process -Id $_.OwningProcess).ProcessName}}
Addresses read the same way as on Linux: 0.0.0.0 is every interface, 127.0.0.1 is local only, [::] is IPv6.
macOS
macOS ships both netstat and lsof, but netstat here inherits the BSD flag set. The main trap: on macOS -p means "protocol", not "process".
# the primary approach — lsof
lsof -nP -iTCP -sTCP:LISTEN
# who took a specific port
lsof -nP -i :5000
# netstat: -p selects the PROTOCOL here, not the process
netstat -an -p tcp | grep LISTEN
# -v adds a process:pid column
netstat -anv -p tcp
So the muscle-memory netstat -tulpn from Linux will not do what you expect on macOS. lsof is the working tool here.

Which port nginx, Apache and other services use — reading the config
Sometimes you need intent rather than fact: which port the service is supposed to come up on. Then you read the configuration.
# nginx: full config dump with every include resolved
nginx -T | grep -n 'listen'
# where the config lives, if the path is non-standard
nginx -V 2>&1 | tr ' ' '\n' | grep conf-path
# crude, but always works
grep -RnE '^[[:space:]]*listen' /etc/nginx/
# Apache: port and virtual host summary
apachectl -S
grep -RnE '^[[:space:]]*Listen' /etc/httpd/ /etc/apache2/ 2>/dev/null
# OpenSSH: effective configuration after all includes
sshd -T | grep -iE '^port|^listenaddress'
# port 22
# listenaddress [::]:22
# listenaddress 0.0.0.0:22
# Docker: what is published to the host
docker ps --format '{{.Names}}\t{{.Ports}}'
docker port <container>
The Ports column in docker ps has an important subtlety. An entry like 0.0.0.0:8080->80/tcp means a published port: host port 8080 is forwarded into the container. A bare 80/tcp with no arrow is only an EXPOSE in the image — a declaration of intent. Such a port is reachable by other containers on the same network, but it does not exist on the host, and ss -tulpn will not show it. Running docker port <container> against that container returns nothing, which is exactly the check.
The config states intent; ss states fact. They diverge constantly: the service never reloaded, started against a different file, or failed to bind and stayed on the old port. The rule is simple — read the config to understand the plan, but trust the socket table for what is actually happening.
How to find which port a host answers on
With no access to the operating system, one method remains: try to connect. There are exactly three answers and they must not be conflated.
| Outcome | How it looks | What it means |
|---|---|---|
| Open | Connection established immediately | Something is listening and accepting connections |
| Closed | Immediate refusal, Connection refused | The host is reachable, but nothing listens on that port |
| Filtered | Silence until the timeout expires | A firewall dropped the packet; no conclusion about the port itself |
Checking a single port from the command line:
# nc: exit code 0 means connected, 1 means it did not
nc -z -w2 example.com 443 && echo OPEN || echo CLOSED
# curl: shows both the status code and the failure reason
curl -sS --connect-timeout 5 -o /dev/null -w '%{http_code}\n' http://example.com:8080/
curl exit code 7 means "failed to connect". It appears both on refusal and on timeout, so tell them apart by timing: a refusal is instant, filtering hangs until --connect-timeout runs out.
Do not scan hosts you do not own without the owner's permission. That is not diagnostics, it is unauthorised probing: in most jurisdictions it falls under computer-misuse law, and with virtually every provider it violates the acceptable use policy. Test your own servers and those you have written authorisation for. Everything in this article is written for your own infrastructure.
How to interpret an open-port result and which ports are worth closing is covered separately in how to check open ports. The point here is different: a scan answers "does the host respond on this port", but never "which process is behind it". Service banners can be forged, any port can host anything, and the number by itself guarantees nothing.
Standard ports and where to look them up
The number-to-protocol mapping is maintained by IANA in the Service Name and Transport Protocol Port Number Registry, and the registration procedure is defined in RFC 6335. A copy of the registry ships with every system.
# Linux and macOS
grep -wE '443/tcp' /etc/services
getent services 443/tcp
getent services https
# Windows
type %SystemRoot%\System32\drivers\etc\services | findstr 443
The entries you will meet most often:
| Port | Name in /etc/services | Usually behind it |
|---|---|---|
| 22/tcp | ssh | SSH |
| 25/tcp | smtp | Server-to-server mail transfer |
| 53/tcp, 53/udp | domain | DNS |
| 80/tcp | http | Plain HTTP |
| 110/tcp, 143/tcp | pop3, imap | Mail retrieval without TLS |
| 443/tcp | https | HTTP over TLS |
| 587/tcp | submission | Mail submission by a client |
| 993/tcp, 995/tcp | imaps, pop3s | Mail over TLS |
| 1080/tcp | socks | SOCKS proxy |
| 3128/tcp | squid | Caching HTTP proxy |
| 3306/tcp | mysql | MySQL and MariaDB |
| 8080/tcp | webcache, http-alt | Alternative HTTP, proxies, backends |
RFC 6335 splits the space into three ranges: 0–1023 are system ports, and on Unix-like systems only a privileged process may bind them; 1024–49151 are registered to specific services; 49152–65535 are dynamic, and the kernel draws a source port from there for every new outbound connection. That is why the local ports of your outgoing connections in ss -tn look like random high numbers — they are supposed to. The actual bounds on Linux are set by the kernel rather than the RFC: on many systems /proc/sys/net/ipv4/ip_local_port_range holds a considerably wider interval.
A name in/etc/servicesis a label, not a fact. The file translates a number into a caption and verifies nothing. A real example:nc -z 127.0.0.1 3000printstcp/hbcibecause that name is registered for port 3000 — while what actually runs there is a web application returning an HTTP redirect. Never infer the service from the file's caption.
Mail ports are a special case: there the numbers really do imply different behaviour rather than just a label, and confusing 25, 465 and 587 costs you undelivered mail. That is unpacked in ports 25, 465 and 587 explained.
How to find a proxy port and check that it is alive
Proxies follow the same rule as everything else: the authoritative source is the configuration, not a "typical" number. A proxy provider hands you an access string shaped like host:port:user:password, and the port in it is the only trustworthy value. Guessing from lists of "standard proxy ports" is pointless: an operator may bind the service to any number, and usually does.
Where the proxy port is written down
# environment variables — read by curl, wget, apt, pip, docker
env | grep -i proxy
# http_proxy=http://10.0.0.5:3128
# https_proxy=http://10.0.0.5:3128
# no_proxy=localhost,127.0.0.1,.internal
# per-client settings
git config --get http.proxy
npm config get proxy
# macOS: system proxy settings, PAC file included
scutil --proxy
# Windows: proxy for system HTTP clients
netsh winhttp show proxy
In graphical settings the port sits next to the address: on Windows under Settings, Network and Internet, Proxy; on macOS under System Settings, Network, the selected interface, Proxies; in browsers either the system settings or a dedicated section. If a PAC file is configured instead of a plain address, the port is computed by a script — you find it inside the .pac file, in the FindProxyForURL function.
If the proxy is your own, the question collapses into the first half of this article: run ss -tulpn on the proxy host and read its config. The port number is set there and is not predetermined by anything.
How to check the proxy port is alive
# 1. does TCP respond at all
nc -z -w3 10.0.0.5 3128 && echo TCP_OK || echo TCP_FAIL
# 2. does it actually proxy
curl -x http://10.0.0.5:3128 --connect-timeout 5 \
-sS -o /dev/null -w '%{http_code}\n' https://example.com/
# 3. with authentication
curl -x http://10.0.0.5:3128 -U user:password --connect-timeout 5 \
-sS -o /dev/null -w '%{http_code}\n' https://example.com/
# 4. SOCKS5 with hostname resolution on the proxy side
curl --socks5-hostname 10.0.0.5:1080 --connect-timeout 5 \
-sS -o /dev/null -w '%{http_code}\n' https://example.com/
Reading the result:
| What came back | Diagnosis |
|---|---|
200 | The proxy accepts connections and proxies traffic |
407 | Alive but demands authentication — add -U |
403 | Alive, but your address or the target is denied by policy |
curl: (7) instantly | Port closed or wrong, or the proxy is not running |
curl: (7) after a timeout | Packets are filtered: firewall, IP allowlist, network block |
000 in %{http_code} | No response at all — read the curl exit code |
A successful TCP handshake is not a working proxy.nc -zonly proves that something accepted a connection on that port. A different service may sit behind it, the proxy may reject you on authentication or an access list, and aCONNECTtunnel to 443 can be forbidden separately from plain HTTP. Test with the same protocol you intend to use.
The simplest way to confirm traffic really flows through the proxy is to compare your public address with and without it — an IP address check does that in one step. If the address is unchanged, the proxy is not being applied, no matter how alive its port looks. How public addressing works and why a machine has several addresses at once is covered in how to find an IP address.

Why a port is "open" but the service does not answer
The most common complaint and the most underrated cause. Here is a live example. On the server, ss shows the port listening:
tcp LISTEN 0 65535 127.0.0.1:3000 0.0.0.0:* users:(("dockerd",pid=2489,fd=115))
From the machine itself everything works:
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/
# 302
From the same server's public address it does not:
curl -sS -o /dev/null -w '%{http_code}\n' http://203.0.113.10:3000/
# curl: (7) Failed to connect to 203.0.113.10 port 3000: Connection refused
Nothing is broken. The service fundamentally does not listen on the public address — it is bound to 127.0.0.1. A scanner from outside will report the port as closed, and it will be right.
| Symptom | What you see | Likely cause | How to check |
|---|---|---|---|
| Instant refusal | Connection refused, immediate curl: (7) | Nothing listening, or bound to 127.0.0.1 | ss -tulpn on the host; read the address left of the port |
| Hangs until timeout | Silence, then curl: (28) or (7) | OS firewall or cloud security group drops the packets | firewall-cmd --list-ports, ufw status, nft list ruleset, provider console |
| Works over IPv4, not IPv6 | Behaviour differs per address | Socket opened only on 0.0.0.0 or only on [::] | Both rows in ss -tlnp output |
| Connects but never answers | nc succeeds, curl hangs or resets | Wrong protocol: plain HTTP against a TLS port or the reverse | curl -v, openssl s_client -connect host:port |
In the config but not in ss | Nothing in the socket table | Service never started, failed to bind, or it is EXPOSE without publishing | systemctl status, service logs, docker port |
Checking the firewall takes seconds:
# firewalld (RHEL, AlmaLinux, Rocky, Fedora)
firewall-cmd --list-ports
firewall-cmd --list-services
# ufw (Ubuntu, Debian)
sudo ufw status verbose
# nftables and iptables directly
sudo nft list ruleset
sudo iptables -L -n
# which addresses exist to bind to in the first place
ip -br a
Keep in mind one layer that is invisible from inside the machine entirely: cloud security groups and provider network ACLs. Those rules live outside the operating system, so firewall-cmd reports a clean configuration while packets still never arrive. If everything looks right inside and it is silent outside, go to the hosting control panel.
One more situation: NAT or a reverse proxy sits between the internet and the server. Then the port you knock on from outside and the port the application listens on are different numbers, and they need not match. Compare the whole chain rather than the numbers; a route trace helps establish how far traffic actually gets.
How to check ports online
When you need to look at your own server from the outside — from a different address, a different network, without touching the local firewall — an online check is more convenient. It answers immediately the one question you cannot answer from inside: is the port visible from the internet?
A working sequence looks like this:
- IP check — confirm which address your server actually presents to the internet. Scanning the wrong address is the most frustrating way to waste time, especially with a CDN in front of the origin.
- Ping and availability — make sure the host responds at all and latency is sane. A blocked ICMP does not mean TCP ports are blocked too.
- Port check — see which of your host's ports answer from the internet, then compare that list with what
ss -tulpnreported on the server itself. The difference is the answer: what the firewall blocks versus what merely listens on loopback. - Monitoring — if a port is critical, put it under continuous checks. A crashed service and a port silently closed by a firewall rule update look identical, and both get noticed equally late.
Only test hosts you own or are authorised to test.

FAQ
Why does netstat say "command not found" while ss works
netstat belongs to the net-tools package, which many modern distributions no longer install by default because it is considered legacy. ss from iproute2 is essentially always present and does the same job, and the -tulpn flags are identical. Installing net-tools out of habit is usually unnecessary.
Why does ss -tulpn show no processes
The kernel exposes the socket-to-process mapping only to a privileged user. As an ordinary user the Process column stays empty even though the ports are listed. Re-run the command with sudo.
Can I find a port without connecting to the host
No. From outside, a port only reveals itself through its reaction to a connection attempt. No DNS record, WHOIS entry or HTTP header tells you which ports a server listens on. The one exception is a port written explicitly in a URL after the colon — and that belongs to that particular link, not to the host as a whole.
Why do my outgoing connections always use different ports
Those are ephemeral ports. For each new outbound connection the kernel allocates a free number from the dynamic range (RFC 6335 reserves 49152–65535 for this, but Linux defaults to a wider range — read the exact bounds from /proc/sys/net/ipv4/ip_local_port_range). A service's listening port and a client's local port are different things.
I see both 0.0.0.0:443 and [::]:443 — is that two services
Usually not: it is one process with separate sockets for IPv4 and IPv6. Confirm it by checking that the PID in the Process column matches. The worrying case is the opposite — if only one address family is listed, some clients will never reach the service.
The port is allowed in the firewall but a scanner still reports it closed
Check the bind address in ss -tulpn. If it reads 127.0.0.1, the firewall rule is irrelevant: packets from the network never reach the socket at all. The second candidate is a cloud security group, which operates outside the operating system and never appears in firewall-cmd.
Checklist
- Decide which of the two questions you are answering: which port my process listens on, or which port a host responds on.
- From inside Linux:
ss -tulpnundersudo. Without root the processes stay hidden. - A missing
netstatis normal, not a fault — installnet-toolsonly if you genuinely need it. - Read the address to the left of the port:
0.0.0.0and[::]face the network,127.0.0.1is local only. - Process by port:
ss -tlnp 'sport = :N',lsof -nP -i :Norfuser -n tcp N, thensystemctl status <PID>. - On Windows use
netstat -anoplustasklist; on macOS uselsof -nP -iTCP -sTCP:LISTEN, nevernetstat -tulpn. - The config shows intent,
ssshows fact. When they disagree, trust the socket table. - From outside, separate three outcomes: refusal is instant, filtering times out, an open port answers immediately.
- Take the proxy port from your access string or config, never from a list of "typical" numbers.
- Verify a proxy with the protocol you will actually use:
nc -zonly proves TCP reachability. - A name in
/etc/servicesis a caption, not proof of what runs behind the number. - Scan only hosts you own, and only with the owner's permission.