Skip to content
New

DeepSeek V4.1 Flash free for 7 days: 10M tokens a day after any top-up. Until Nov 30.

How it works

TCP/IP and Ports: How Services Listen on Your Machine

A port is a numbered door on your machine. Learn how TCP and UDP use ports, then start, find and test a local server on 127.0.0.1.

Beginner12 min readBy NotHoly

A port is a number that tells the operating system which program should receive a piece of network traffic. Your machine has an IP address, and it can run dozens of network programs at the same time: a browser that fetches pages, a mail client, a database, a local web server. The IP address gets traffic to the right computer. The port gets it to the right program.

If you have read what DNS does, you already know how a name becomes an address. This article explains where ports come from, how the two main transport protocols use them, and how to see which ports your own machine is listening on. The lab runs on 127.0.0.1, the loopback address that always means "this machine," so nothing leaves your computer.

What a port is

A port is a 16-bit number, which means it runs from 0 to 65535. TCP and UDP headers both carry a source port and a destination port. When a packet reaches your computer, the operating system reads the destination port and hands the data to the program that asked to receive on that number.

Think of an apartment building. The street address gets the delivery to the building. The apartment number tells the carrier which door to knock on. In that picture the IP address is the building and the port is the apartment. Someone decides which apartment is which, and a tenant can move out and let someone else take the number.

A listening port is one where a program has told the operating system, in effect, "send me connections for this number." A program in that state is listening. When the program exits, the operating system releases the port, and nothing answers there anymore.

Why this matters

Every service you run opens at least one port. That includes programs you did not start on purpose: updaters, sync clients, development servers, plugins, and software that installs itself as a background service. The list of listening ports is the simplest accurate picture of what your machine offers to the network.

If you ever review a host you own, that list is the first thing to write down. Before you ask whether a service is patched or configured well, you need to know that it exists. Security work starts with inventory, and the inventory starts with the doors.

The layers in one pass

TCP/IP is the protocol suite the internet runs on. It is organized in layers, and you do not need all of them yet. Four layers matter here:

  • Link: moves frames across one local network segment, such as Wi-Fi or Ethernet.
  • Internet: IP moves packets between hosts across networks, using IP addresses.
  • Transport: TCP or UDP carries data between programs, using ports.
  • Application: the protocol the program speaks, such as HTTP, DNS or SSH.

Each layer wraps the data from the layer above in its own header. The IP header carries the source and destination addresses. The TCP or UDP header carries the source and destination ports. The application data sits inside. When the packet arrives, each layer removes its header and passes the rest upward.

The port lives in the transport layer. IP does not know which program should get the data. That job belongs to the transport layer, and it is why one IP address can serve a web page on port 443 and a database on port 5432 at the same time.

How TCP opens a connection

TCP, specified in RFC 9293, gives a program a reliable, ordered stream of bytes. Before any application data moves, the two ends agree on starting sequence numbers and confirm that each side can send and receive. That agreement is the three-way handshake.

  1. The client sends a SYN segment. In plain terms it says: I want to connect to port 8000, and my starting sequence number is X.
  2. If a program listens on port 8000, the server answers with a SYN-ACK. It acknowledges X and sends its own starting number.
  3. The client answers with an ACK. The connection is now open, and the client can send its request.

When no program listens on the port, the target machine does not send SYN-ACK. It sends a RST (reset) segment, and the client gives up at once with a message such as "connection refused." That fast answer is useful. It tells you the host received your packet and that nothing accepts connections on that port.

A silent wait ending in a timeout means something different. Very often a firewall dropped the packets without replying. So "refused" and "timed out" are two different clues. Learn to read them.

Connections on 127.0.0.1 use the same TCP logic even though the packets never reach a network card. The operating system runs the handshake in software, which makes the loopback address a safe place to practice.

How UDP is different

UDP, specified in RFC 768, is much simpler. Its header has 8 bytes: source port, destination port, length and checksum. There is no handshake, no acknowledgement, no retransmission and no ordering. The sender transmits a datagram, and the network delivers it or it does not. The receiver confirms nothing unless the application builds that logic itself.

That simplicity has uses. DNS queries usually travel over UDP port 53, because a short question and a short answer fit in one exchange, and a handshake would add round trips. Real-time voice and video often use UDP, and so does QUIC, the transport under HTTP/3. Each of these protocols brings its own rules for loss and retries.

Ports work the same way for UDP. The destination port picks the program. The difference is that a UDP port has no connection state. Any program bound to that port receives whatever arrives, from whoever sends it. A TCP listener only sees connections that complete the handshake. Both still answer the same question: which program gets this packet.

Who decides which number means what

The Internet Assigned Numbers Authority (IANA) maintains the Service Name and Transport Protocol Port Number Registry. The registry records which service is commonly associated with each port number for TCP, UDP and a few other transport protocols. Well-known entries include 22 for ssh, 53 for domain (DNS), 80 for http and 443 for https.

RFC 6335 divides the number space into three ranges:

  • 0 to 1023: system ports, also called well-known ports. Many systems allow only privileged processes to bind them.
  • 1024 to 49151: user ports, where vendors register many services.
  • 49152 to 65535: dynamic or private ports. Clients often use these as temporary source ports.

The registry records assignments and conventions. Nothing in TCP or UDP enforces them. Any program with permission can bind almost any free port. The registry tells you what usually runs on a number, and it can be wrong about your machine. Look at what is actually listening before you draw a conclusion.

Hands-on lab: a local web server on 127.0.0.1

You need a terminal and Python 3. The lab uses the HTTP server built into Python, so you install nothing extra.

I ran the Windows commands below on Windows 11 Pro with Git Bash, Python 3.12.10 and curl 8.12.1. I did not run the Linux and macOS commands on this machine, so their output is not shown. Where an output block appears, it came from the command shown above it.

One note on Python: in my Git Bash session python3 was not found, while python ran Python 3.12. If python3 fails for you on Windows, use python. The rest of the command stays the same.

Step 1: Make a folder with one file

The server publishes whatever sits in the folder where you start it, so put something small there first.

bash
mkdir -p ~/port-lab && cd ~/port-lab
printf '<!doctype html><title>lab</title><h1>Port 8000 lab</h1>\n' > index.html

On PowerShell, create the same folder and write those two lines into index.html with any editor.

Step 2: Start the server and leave it running

Keep this terminal open. The server runs until you stop it.

bash
cd ~/port-lab
python3 -m http.server 8000 --bind 127.0.0.1

The flag --bind 127.0.0.1 tells Python to listen only on the loopback interface. Without it, Python listens on every interface of the machine. That difference matters later when you read the listening list.

Step 3: Find the listening socket

Pick the command for your operating system. Each one lists the sockets in the LISTEN state that use port 8000.

Linux:

bash
ss -tlnp | grep 8000

macOS:

bash
lsof -iTCP -sTCP:LISTEN -n -P | grep 8000

Windows PowerShell:

powershell
Get-NetTCPConnection -State Listen -LocalPort 8000

This is the output I got from the Windows command on my machine, piped through Format-Table:

text
LocalAddress LocalPort  State OwningProcess
------------ ---------  ----- -------------
127.0.0.1         8000 Listen         45608

The process ID will differ on your machine. Run Get-Process -Id <OwningProcess> with your own number to confirm that the owner is the Python process you started. In my run it was python.

What to look for:

  • LocalAddress is 127.0.0.1. The socket accepts connections only from this machine. If you had left out --bind, you would expect to see 0.0.0.0 here instead. I did not test that variant on this machine, so treat that as the expected result, not an observed one.
  • State is Listen. The socket is waiting for connections.
  • OwningProcess points to the program you expect. If it points to something you do not recognize, stop and find out what it is before you go on.

Step 4: Connect to it

Open a second terminal and run:

bash
curl -v http://127.0.0.1:8000/

This is the output from my run, trimmed to the lines that matter:

text
* Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.12.10
< Content-type: text/html
<!doctype html><title>lab</title><h1>Port 8000 lab</h1>

Read it in order. Trying means curl is opening a TCP connection. The address is already an IP, so no DNS lookup happens. Connected means the three-way handshake finished. GET / HTTP/1.1 is the application layer: curl now speaks HTTP over that connection. 200 OK means the server found the file and answered. The HTML line at the bottom is your file.

The server writes a line about the request to its own terminal. Mine showed:

text
127.0.0.1 - - [08/Nov/2026 06:07:56] "GET / HTTP/1.1" 200 -

The source address is the same loopback address, so the request came from this machine.

Step 5: Stop the server and try again

Go back to the server terminal and press Ctrl+C. That is the clean way to stop it. On my machine I stopped the process with Stop-Process -Force, which ends it without giving it a chance to shut down cleanly. Either way, the listening socket goes away once the process ends.

Confirm the socket is gone:

powershell
Get-NetTCPConnection -State Listen -LocalPort 8000

My output was empty. Then run the same curl command again:

bash
curl -v http://127.0.0.1:8000/

This is the output from my run:

text
* Failed to connect to 127.0.0.1 port 8000 after 2048 ms: Could not connect to server
curl: (7) Failed to connect to 127.0.0.1 port 8000 after 2048 ms: Could not connect to server

The exit code of curl was 7, the code that means it could not connect.

Here is what happened. Your SYN reached 127.0.0.1 on port 8000. Nothing was listening there, so the operating system answered with a reset. curl reported the refusal immediately, without waiting for a timeout. If you see a long wait followed by a timeout instead, suspect a firewall or a route that drops packets.

What the lab shows

The same address and port behaved in two ways. With a listener, the handshake completed and the server answered. Without one, the refusal came back in milliseconds. The listening list is what separates the two cases. When a connection surprises you, check the list first.

Common mistakes

Binding to every interface by accident. Many quick servers listen on all addresses by default. On a laptop connected to a café network, that can expose a development server to other devices on the same network. Use an explicit bind address when you only need local access.

Treating a port number as proof of identity. Port 80 usually means HTTP, but nothing forces a program to speak HTTP on it. Connect and read what the service sends, or check which process owns the socket.

Forgetting UDP. ss -tlnp shows TCP listeners only. Use ss -ulnp for UDP, or Get-NetUDPEndpoint on Windows. A machine can have a busy UDP service that never appears in your TCP check.

Reading a timeout as "no service." A timeout often means a filter sits between you and the port. A refusal means the host answered and nothing listens. They call for different next steps.

Killing the wrong process. Confirm the owning process name and its command line before you stop it. A leftover server from an earlier attempt can still hold the port and answer with files from a folder you did not mean to publish. Check the owner before you assume the port is yours.

Why this comes first in security work

Listening ports describe what your machine offers to its network, whether you meant to offer it or not. Before you test anything else on a host you own, list what listens, match each entry to a program you know, and close what you do not need. A development server you forgot to stop is a good example of an entry that looks harmless and still answers.

The same commands can be used on machines you do not control, but scanning or probing a system you do not own requires written permission from its owner. Keep your practice on your own machines and your own lab.

How to go further

Repeat the exercise with UDP. A short Python listener on 127.0.0.1 port 5399 received a datagram from a sender in my run. The listener did nothing special:

python
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("127.0.0.1", 5399))
data, addr = s.recvfrom(1024)
print("got", data, "from", addr)

Run it in one terminal, then send a datagram from another with socket.sendto. The output I got was got b'hello' from ('127.0.0.1', 63411). Notice that no handshake happened. The packet simply arrived. Port 5353 was already in use on my machine, and binding it failed with a permission error, so I picked 5399 for the test.

Next, read the connection establishment part of RFC 9293 and compare it with the packet flow above. Then move on to how HTTP works, which covers how HTTP runs on top of TCP and what a request and a response contain.

Sources

RFC 9293, Transmission Control Protocol (TCP), IETF, August 2022. https://www.rfc-editor.org/rfc/rfc9293

RFC 768, User Datagram Protocol, IETF, August 1980. https://www.rfc-editor.org/rfc/rfc768

Service Name and Transport Protocol Port Number Registry, IANA. https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml

RFC 6335, Internet Assigned Numbers Authority (IANA) Procedures for the Management of the Service Name and Transport Protocol Port Number Registry, IETF, August 2011. https://www.rfc-editor.org/rfc/rfc6335

Sources

  1. RFC 9293: Transmission Control Protocol (TCP)
  2. RFC 768: User Datagram Protocol
  3. IANA: Service Name and Transport Protocol Port Number Registry
  4. RFC 6335: IANA Procedures for the Service Name and Port Number Registry

More in Networks and the web

Networks and the web

What Is TLS? How HTTPS Protects Traffic

TLS 1.3 gives HTTPS confidentiality, integrity and server authentication. Learn the handshake, certificate chains and what the padlock does not prove.

Beginner11 min read