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:

  1. Check open ports on the server
  2. Check which ports are visible from the outside
  3. Determine which ports are actually needed
  4. Stop or restrict unnecessary services
  5. 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.

Tagged: