Find out which ports your server has open (and close the ones you don't need)
Every open port is a door that anyone on the internet can knock on. A PHP web server needs three. This guide shows how to list them all, check them from outside, and close the rest.
Before you start
- You need a user with
sudoon the server (see our sudo guide), and a second machine for step 4, such as your own computer. - The examples use Ubuntu 24.04 and Debian 13 with Nginx, PHP-FPM, MariaDB and Redis. The server's public address is
203.0.113.5. Use yours.
1. List everything that listens
sudo ss -tulpn
The letters mean: t TCP, u UDP, l only ports that listen, p show the program, n show numbers instead of names. Use sudo, or the program column stays empty.
You get one line per port. A trimmed example from our test server:
Netid State Local Address:Port Process
tcp LISTEN 0.0.0.0:22 users:(("sshd",pid=3480,fd=6))
tcp LISTEN 0.0.0.0:80 users:(("nginx",pid=2914,fd=5),...)
tcp LISTEN 127.0.0.1:3306 users:(("mariadbd",pid=8537,fd=24))
tcp LISTEN 127.0.0.1:6379 users:(("redis-server",pid=8260,fd=8))
tcp LISTEN [::1]:6379 users:(("redis-server",pid=8260,fd=9))
tcp LISTEN [::]:22 users:(("sshd",pid=3480,fd=7))
tcp LISTEN [::]:80 users:(("nginx",pid=2914,fd=6),...)
The column that matters is Local Address:Port. The number after the last colon is the port. The part before it says who can connect.
2. Read the address
| You see | It means |
|---|---|
127.0.0.1:3306 or [::1]:6379 | Only programs on the server itself can connect. Good for databases and caches. |
0.0.0.0:80, [::]:80 or *:80 | Every network, including the internet (IPv4, IPv6, or both). Only the firewall stands in the way. |
A public address, such as 203.0.113.5:80 | Open on that one address. Treat it like the line above. |
To hide the local-only lines and see just what's open to the network:
sudo ss -tulpn | grep -vE '127\.0\.0\.|\[::1\]'
Lines with 127.0.0.53 or 127.0.0.54 port 53 belong to systemd-resolved, which looks up domain names for the server. They're local, and fine.
3. Know what should be open
A typical server for PHP websites needs only these open to the internet:
- 22 for SSH (or the port you moved SSH to)
- 80 for HTTP, which redirects to HTTPS and is used to renew Let's Encrypt certificates
- 443 for HTTPS
These should never be open to everyone:
| Port | What it is | Why it matters |
|---|---|---|
| 3306 | MySQL or MariaDB | Bots try passwords on it all day. |
| 5432 | PostgreSQL | The same, for PostgreSQL. |
| 6379 | Redis | Often has no password. Anyone can read or wipe your data. |
| 11211 | Memcached | Has no password at all. |
| 9000 | PHP-FPM | Whoever reaches it can make PHP run code on your server. |
| 8080, 8443, 8888 and similar | Admin panels, test servers, dashboards | Often left with default passwords or no login. |
On Ubuntu 24.04, port 22 shows systemd as the program, not sshd. That's normal: systemd holds the port and starts SSH when someone connects. systemctl status ssh.socket shows it.
4. Find out what a port belongs to
Take the pid number from the ss line and ask systemd which service it is:
systemctl status 10891
The first line names the service, for example postgresql@17-main.service. Now you know what to stop or change.
5. Check from outside with nmap
ss shows what listens. It doesn't show what the internet can actually reach, because a firewall may block some of it. To see that, scan the server from another machine. Install nmap there (sudo apt install nmap on Ubuntu or Debian; there are downloads for Windows and macOS on nmap.org):
sudo nmap -p- 203.0.113.5
Don't leave out -p-. It scans all 65,535 ports. Without it, nmap only tries the 1,000 most common ones. In our test, a plain nmap missed both Redis (6379) and Memcached (11211), even though both were wide open. It works without sudo too, just a little slower.
Each port gets a state:
- open: something answers. You should only see 22, 80 and 443.
- closed: nothing listens there. Not a risk. If you have a firewall, it lets this port through.
- filtered: the firewall blocks it. This is what you want for everything else.
If the server has an IPv6 address, scan that too, with nmap -6 -p- 2001:db8::5. Your own computer needs IPv6 for this to work.
6. Stop services you don't use
The best closed port is one where nothing runs. Maybe a package pulled in a service you never needed, or you tried something once. Stop it now and keep it off after a restart:
sudo systemctl disable --now memcached
If you'll never need it again, remove the package: sudo apt purge memcached. For PostgreSQL, sudo systemctl disable --now postgresql stops every database cluster on the server. Run sudo ss -tulpn again to check the port is gone.
7. Make the rest listen on the server only
Databases and caches are used by your websites on the same server, so they don't need the network at all. On Ubuntu and Debian, most are local by default. If ss shows one on 0.0.0.0, [::] or *, someone changed it. Change it back:
- MySQL or MariaDB:
bind-address = 127.0.0.1. Full steps in our MySQL and MariaDB guide. - Redis:
bind 127.0.0.1 -::1. Full steps in our Redis guide. - PostgreSQL: in
/etc/postgresql/17/main/postgresql.conf(your version number),listen_addresses = 'localhost', thensudo systemctl restart postgresql. - Memcached: in
/etc/memcached.conf, the lines-l 127.0.0.1and-l ::1, thensudo systemctl restart memcached. - PHP-FPM: the default is a socket file, such as
listen = /run/php/php8.4-fpm.sock, which uses no port at all. If a pool in/etc/php/8.4/fpm/pool.d/sayslisten = 9000, that means every network. Use the socket, orlisten = 127.0.0.1:9000. Check withsudo php-fpm8.4 -t, then restartphp8.4-fpm. - An admin panel in Nginx on 8080:
listen 127.0.0.1:8080;
Restart Nginx after changing a listen line. Don't just reload. In our test, systemctl reload nginx said nothing, but Nginx couldn't take the new address and kept the old one open to everyone. The error was only in /var/log/nginx/error.log. After a change, always run sudo ss -tulpn again.
To use a panel that now listens on the server only, open an SSH tunnel from your computer:
ssh -L 8080:127.0.0.1:8080 you@203.0.113.5
Leave it open and browse to http://127.0.0.1:8080 on your own computer. The panel works for you and stays closed to everyone else.
8. Add the firewall as a second lock
A firewall catches what you missed: a service that comes back after an update, or a new one somebody installs. Set it to block everything coming in except SSH, HTTP and HTTPS. Our UFW firewall guide walks you through it. Then run the nmap scan from step 5 again: every other port should now say filtered.
Don't rely on the firewall alone. If it's ever turned off, or a rule is too wide, everything that listens on every network is open again. Steps 6 and 7 are the real fix.
9. Watch out for Docker
If you run containers with Docker, this one catches a lot of people. Ports that Docker publishes skip UFW. Docker writes its own firewall rules, and traffic for containers never reaches UFW's rules. In our test, UFW was blocking everything except 22, 80 and 443, and ufw status showed nothing else. But a container started with -p 8081:80 was still open to the outside.
In ss, these ports show up as docker-proxy. The fix is to publish only to the server itself, and put Nginx in front if the internet needs to reach it:
docker run -d -p 127.0.0.1:8081:80 nginx
In a compose.yaml file, write it the same way: "127.0.0.1:8081:80" under ports:. Databases in containers often don't need a published port at all, because other containers on the same Docker network reach them directly. Docker explains the details in its firewall documentation.
If something goes wrong
- The program column in
ssis empty: run it withsudo. - A port is still there after you changed the setting: the service wasn't restarted, or the setting is in a second file. Search for it, for example
sudo grep -rn bind-address /etc/mysql/. For Nginx, check/var/log/nginx/error.logand restart. - The website shows "502 Bad Gateway" after changing PHP-FPM: Nginx's
fastcgi_passmust point at the same socket or address that the poollistenline uses. - nmap says "Host seems down": the server doesn't answer ping. Add
-Pnto scan anyway. - A port is open in nmap even though UFW blocks it: it's probably a Docker container (step 9).
docker pslists the published ports.
Official sources
- ss manual page (Debian)
- nmap reference guide and port selection (nmap.org)
- systemctl manual page (Debian)
- Docker and firewalls (Docker documentation)
- listen_addresses (PostgreSQL documentation)
- PHP-FPM pool settings (php.net)
Next steps: use SSH keys and turn off password login for the one port that must stay open, and work through the security checklist for a new PHP server.
ServerNest is being built with an agent that only makes outgoing connections, so it won't add an open port to your server. Join early access to try it first.