Ports and listening services
Module 5: Shielding the network · Reading time: 10 minutes
Learning objective: After this lesson you can see which services are listening on your server and on which address, explain the difference between 0.0.0.0 and 127.0.0.1, and check from outside what anyone on the internet can see of your server.
Key concepts: port, TCP and UDP, listening, ss -tulpn, 0.0.0.0 and [::], 127.0.0.1 and [::1], bind-address, open, closed and filtered, attack surface, scanners
One IP address with 65,535 doors
Your server has an IP address, for example 192.0.2.10 and 2001:db8::10. Several programs can be reachable on that address at the same time, each on its own port: a number from 1 to 65,535. A program that accepts connections is listening on a port. A few well-known ones:
| Port | Protocol | Service |
|---|---|---|
| 22 | TCP | SSH |
| 80 | TCP | http (web server) |
| 443 | TCP and UDP | https (UDP for HTTP/3) |
| 25 | TCP | mail between mail servers (SMTP) |
| 3306 | TCP | MySQL and MariaDB |
| 5432 | TCP | PostgreSQL |
| 6379 | TCP | Redis |
TCP is the protocol for connections where data must arrive reliably (web, SSH, databases). UDP is for fast, separate packets (DNS, VPNs such as WireGuard, HTTP/3). A service listens on TCP, UDP or both.
Every listening port is a door into your server. Behind every door is software that may contain bugs. All the doors that can be reached from outside together form your attack surface. The smaller, the better: a server with a website only needs 22, 80 and 443 from outside.
Seeing what is listening: ss
The command ss (socket statistics) shows what is listening on your server. Use the options -t (TCP), -u (UDP), -l (listening only), -p (which program) and -n (numbers, not names):
anna@web1:~$ sudo ss -tulpn
Netid State Local Address:Port Peer Address:Port Process
udp UNCONN 127.0.0.54:53 0.0.0.0:* users:(("systemd-resolve",pid=402,fd=16))
tcp LISTEN 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=611,fd=3))
tcp LISTEN 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=902,fd=6))
tcp LISTEN 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=902,fd=7))
tcp LISTEN 127.0.0.1:3306 0.0.0.0:* users:(("mariadbd",pid=755,fd=21))
tcp LISTEN 0.0.0.0:6379 0.0.0.0:* users:(("redis-server",pid=780,fd=6))
tcp LISTEN [::]:22 [::]:* users:(("sshd",pid=611,fd=4))
tcp LISTEN [::]:80 [::]:* users:(("nginx",pid=902,fd=8))
tcp LISTEN [::]:443 [::]:* users:(("nginx",pid=902,fd=9))The most important column is Local Address: the address the program is listening on.
| Address | Meaning |
|---|---|
0.0.0.0 | All IPv4 addresses of the server, including the public one: reachable from outside (unless a firewall blocks it) |
[::] | All IPv6 addresses: the same for IPv6 |
127.0.0.1 or [::1] | Only the server itself (loopback): never reachable from outside |
127.0.0.53, 127.0.0.54 | Also loopback; this is where the local DNS helper of systemd-resolved listens |
192.0.2.10 | Only that one address |
In the example above, MariaDB is fine: it listens on 127.0.0.1, so only programs on the server itself (your website) can reach it. But Redis listens on 0.0.0.0:6379. If the firewall isn't set up properly, the whole internet can reach it. That is exactly the kind of mistake this lesson is about.
Listening on the right address
A database, cache or admin panel should almost never be reachable from outside. The first and best defence is for the service to listen only on loopback. A firewall (lesson 5.2) is the second layer; it can be switched off by accident or not apply (for example with Docker, lesson 9.3).
Where you set this differs per program:
| Program | Setting | File (Debian/Ubuntu) |
|---|---|---|
| MariaDB/MySQL | bind-address = 127.0.0.1 | /etc/mysql/mariadb.conf.d/50-server.cnf |
| PostgreSQL | listen_addresses = 'localhost' | /etc/postgresql/<versie>/main/postgresql.conf |
| Redis | bind 127.0.0.1 -::1 and protected-mode yes | /etc/redis/redis.conf |
The Debian and Ubuntu packages already set these services to localhost by default. Things usually go wrong when someone follows a guide that says "set bind-address to 0.0.0.0 to connect from outside". If you want to reach the database from your own laptop, use an SSH tunnel (lesson 2.3): the port stays closed, and only someone who comes in via SSH can reach it.
After a change, restart the service and check again with ss -tulpn.
Open, closed and filtered
Seen from outside, a port can be in one of three states:
- Open: something is listening and the connection succeeds.
- Closed: nothing is listening, or a firewall actively refuses. The server immediately sends back a "no" (with TCP an RST packet). A connection fails straight away with "Connection refused".
- Filtered: a firewall silently drops the packet. The connection hangs until it times out.
For your database, closed and filtered are both fine: nobody gets in. The difference only tells you why: filtered almost always means your firewall is blocking it.
Looking from outside
ss shows what is listening on the server, but not what can be reached from outside. There is still a firewall in between, and sometimes a network firewall at your provider too. So the only real check is: look from outside, from another computer.
From your own computer (not from the server itself, because then you go via loopback) you can test a single port with nc (netcat):
anna@laptop:~$ nc -vz -w 3 192.0.2.10 6379
nc: connect to 192.0.2.10 port 6379 (tcp) failed: Connection refused
anna@laptop:~$ nc -vz -w 3 192.0.2.10 22
Connection to 192.0.2.10 22 port [tcp/ssh] succeeded!On Windows, PowerShell does the same with Test-NetConnection 192.0.2.10 -Port 6379. A port scanner such as nmap can check several ports at once; only use a scanner like that on your own server.
Don't forget IPv6. A common mistake is a firewall that only has rules for IPv4, while the service also listens on [::]. So test the IPv6 address too: nc -6 -vz -w 3 2001:db8::10 6379.
Why this matters so much
Your server is scanned from the very first minute. Search engines for devices and services on the internet, as well as criminals' bots, constantly probe all IPv4 addresses on well-known ports. An open Redis without a password, an Elasticsearch cluster or a MongoDB without access control is found within a short time. Data breaches caused by open databases and open search clusters make the news regularly, and they almost always come down to exactly this mistake: a service meant for internal use was listening on all addresses.
The SSH port gets knocked on all day too. That's normal and no problem with keys (lesson 2.2) and fail2ban (lesson 5.3).
The course check
In the exercise for this lesson, the course program checks five fixed database ports of your server from outside: 3306 (MySQL/MariaDB), 5432 (PostgreSQL), 6379 (Redis), 9200 (Elasticsearch/OpenSearch) and 27017 (MongoDB). The program only tries whether a connection can be made, sends no data and doesn't log in anywhere. It doesn't scan any other ports. The checks come from the IP address shown in the exercise, so you can recognise them in your own logs. An address can only be checked a few times per hour.
On AlmaLinux and Rocky Linux
ss works the same. Service files are in other places (for example /etc/my.cnf.d/ for MariaDB, /var/lib/pgsql/data/postgresql.conf for PostgreSQL). In addition, SELinux can block a service that wants to listen on an unusual port; you'll see that in journalctl (lesson 6.2) and not in ss.
Summary
- Every listening port is a door; from outside, only keep open what you really need (usually 22, 80 and 443).
sudo ss -tulpnshows which program is listening on which address and port.0.0.0.0and[::]mean "all addresses",127.0.0.1and[::1]mean "only the server itself". Databases, caches and search clusters belong on loopback.- Closed and filtered are both fine for a database; open is wrong.
- Always check from outside, for IPv4 and IPv6, because
sscan't see the firewall.
Quiz
Exercise
Exercise Z5.1: Database ports closed from outside. Run sudo ss -tulpn on your server and find out which addresses your services are listening on. Then let the course program check from outside that ports 3306, 5432, 6379, 9200 and 27017 of your server can't be reached, for IPv4 and (if your server has an AAAA record) IPv6. Also fill in which of your services listen on 0.0.0.0 or [::]. The program only makes a connection to those five ports and sends nothing.
