Open ports are network “entry points” to a server. SSH, web servers, databases, control panels, mail services, Docker applications, and other services work through them.
The more ports are open to the outside world, the larger the potential attack surface. Therefore, after installing a VPS or dedicated server, it is important to check which services are listening on the network, which of them are accessible from the internet, and what really needs to remain open.
In this article, we will look at 5 main solutions:
- Check open ports on the server
- Check which ports are visible from the outside
- Determine which ports are actually needed
- Stop or restrict unnecessary services
- Close unnecessary ports through the firewall
1. Check Open Ports on the Server
First, you need to understand which services are listening on the network on the server.
Main command:
sudo ss -tulpn
What the parameters mean:
-t — TCP ports
-u — UDP ports
-l — only listening sockets
-p — show the process
-n — do not resolve ports and IPs into names
Example output:
Netid State Local Address:Port Process
tcp LISTEN 0.0.0.0:22 users:(("sshd",pid=812,fd=3))
tcp LISTEN 0.0.0.0:80 users:(("nginx",pid=1043,fd=6))
tcp LISTEN 0.0.0.0:443 users:(("nginx",pid=1043,fd=7))
tcp LISTEN 127.0.0.1:3306 users:(("mysqld",pid=1201,fd=21))
It is important to understand the difference:
0.0.0.0:PORT — the service is listening on all interfaces
127.0.0.1:PORT — the service is available only locally
PUBLIC_IP:PORT — the service is listening on the public IP
For example:
0.0.0.0:22 — SSH is potentially accessible from the outside
0.0.0.0:80 — HTTP is accessible from the outside
0.0.0.0:443 — HTTPS is accessible from the outside
127.0.0.1:3306 — MySQL is available only locally
Alternatively, you can use lsof:
sudo lsof -i -P -n | grep LISTEN
This command is useful when you need to quickly see which process is using a specific port.
2. Check Which Ports Are Visible from the Outside
The ss and lsof commands show what the server itself is listening on. However, this does not always mean that the port is accessible from the internet. A firewall may block external access.
Therefore, it is important to check the server from the outside, for example with nmap from another computer:
nmap -Pn SERVER_IP
Check specific ports:
nmap -Pn -p 22,80,443,3306,5432 SERVER_IP
Check a port range:
nmap -Pn -p 1-10000 SERVER_IP
The difference is simple:
ss/lsof — shows what the server is listening on
nmap — shows what is visible from the outside
If ss shows a port, but nmap does not see it from the outside, it means the port is listening locally or is blocked by the firewall. If nmap shows the port as open, it means it is accessible from the outside.
3. Determine Which Ports Are Actually Needed
Not every open port is a problem. The problem appears when a service that should not be public is accessible from the outside.
For a regular web server, the following ports are usually enough:
22/tcp — SSH, preferably only for trusted IPs
80/tcp — HTTP
443/tcp — HTTPS
All other ports should be checked separately.
Typical Ports and Recommendations
|
Service |
Port |
Should it be open to the outside? |
|
SSH |
22/tcp |
Preferably only for trusted IPs or through a VPN |
|
HTTP |
80/tcp |
Yes, if the server hosts websites |
|
HTTPS |
443/tcp |
Yes, if the server hosts websites |
|
MySQL/MariaDB |
3306/tcp |
Usually no |
|
PostgreSQL |
5432/tcp |
Usually no |
|
Redis |
6379/tcp |
No |
|
MongoDB |
27017/tcp |
Usually no |
|
Proxmox VE |
8006/tcp |
Preferably only through a VPN or allowlist |
|
WHM/cPanel |
2086/2087/tcp |
Preferably restricted by IP |
|
Plesk |
8443/tcp |
Preferably restricted by IP |
|
Portainer |
9000/9443/tcp |
Preferably restricted by IP or VPN |
Important
Databases, Redis, MongoDB, and control panels usually should not be open to the entire internet.
4. Stop Unnecessary Services or Restrict Them to localhost
If a service is not needed, it is better to stop it and disable it from autostart.
Check running services:
systemctl --type=service --state=running
Check a specific service:
systemctl status SERVICE_NAME
Stop a service:
sudo systemctl stop SERVICE_NAME
Disable autostart:
sudo systemctl disable SERVICE_NAME
Stop and disable it at the same time:
sudo systemctl disable --now SERVICE_NAME
For example, if Apache is not used:
sudo systemctl disable --now apache2
After that, check the ports again:
sudo ss -tulpn
If the service is needed, but only locally, it is better to bind it to 127.0.0.1.
For example, for MariaDB/MySQL:
bind-address = 127.0.0.1
For PostgreSQL:
listen_addresses = 'localhost'
For Redis:
bind 127.0.0.1 ::1
protected-mode yes
After changing the configuration, restart the service and check the port:
sudo systemctl restart SERVICE_NAME
sudo ss -tulpn | grep PORT
Expected result for a local service:
127.0.0.1:PORT
Good Practice
If a database is used only by a website or application on the same server, do not expose it on the public IP.
5. Close Unnecessary Ports Through the Firewall and Check Docker
Even if a service needs to run, it does not mean it should be accessible to the entire internet. Unnecessary ports should be closed through the firewall.
If you use UFW, first check the status:
sudo ufw status verbose
Allow only the required ports:
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
If SSH should be accessible only from your IP:
sudo ufw allow from YOUR_TRUSTED_IP to any port 22 proto tcp
sudo ufw deny 22/tcp
Close an unnecessary port, for example MySQL:
sudo ufw deny 3306/tcp
Enable the firewall:
sudo ufw enable
Check the rules:
sudo ufw status numbered
Warning
Before enabling the firewall, make sure the SSH port is allowed for your IP. Otherwise, you may lose access to the server.
Separately, check Docker if it is installed:
docker ps
Dangerous example:
0.0.0.0:5432->5432/tcp
0.0.0.0:6379->6379/tcp
This means that PostgreSQL or Redis is exposed to the outside.
If the database is needed only by other containers, do not publish it through ports.
Bad:
ports:
- "5432:5432"
Better:
services:
postgres:
image: postgres:16
networks:
- internal
networks:
internal:
Important
Docker can publish ports on the host. After starting containers, always check docker ps, ss -tulpn, and an external nmap scan.
Common Mistakes
Open Database
Port 3306, 5432, 6379, or 27017 is open on 0.0.0.0 and accessible from the internet. This is dangerous, especially if the password is weak or the service is not configured with additional protection.
SSH Was Closed and Access Was Lost
Before changing firewall rules, always check that SSH is allowed for your IP. Do not close the current SSH session until you open a new one and confirm that login works.
The Service Was Stopped, but Autostart Was Not Disabled
After a reboot, the server opens the port again because the service remains enabled in autostart.
Docker Exposed a Port to the Outside
In docker-compose.yml, this was specified:
ports:
- "5432:5432"
As a result, the database became accessible from the public IP. If access is needed only between containers, use an internal Docker network without publishing the port.
The Service Listens on 0.0.0.0 Instead of 127.0.0.1
If the service is needed only locally, it is better to bind it to localhost.
Conclusion
In this article, we looked at only 5 main solutions.
For a regular web server, it is usually enough to keep the following ports open:
22/tcp — SSH, preferably only for trusted IPs
80/tcp — HTTP
443/tcp — HTTPS
All other ports should either be closed or accessible only from a trusted network, VPN, or internal Docker network. This approach reduces the risk of attacks and makes the server safer for production workloads.