Every service that listens on a network port is something an attacker can probe, and something you have to keep updated. The simplest hardening step on any server is to run only what you need. This guide shows you how to find what is running, switch off what you do not use and keep the remaining services as private as possible.
Before you start
- A Linux VPS running Ubuntu 22.04/24.04, Debian 12, or AlmaLinux/Rocky Linux 8 or 9.
- An SSH session as root or a sudo user.
- A rough list of what the server is for, such as "Nginx, PHP and MariaDB for two websites". Anything outside that list is a candidate for removal.
Warning: Do not stop or disable sshd, systemd-networkd, NetworkManager, systemd-resolved or the firewall unless you understand the effect. Turning off the wrong one can cut your connection. If that happens, log in through the browser console in your VPS provider's control panel.
Step 1: list what listens on the network
sudo ss -tulpnWrite down each line with its port, address and process. For example:
tcp LISTEN 0.0.0.0:22 users:(("sshd",...))
tcp LISTEN 0.0.0.0:80 users:(("nginx",...))
tcp LISTEN 0.0.0.0:3306 users:(("mariadbd",...))
tcp LISTEN 0.0.0.0:111 users:(("rpcbind",...))For each one ask:
- Do I use this at all?
- Does it need to be reachable from the internet, or only from the server itself?
In the example, SSH and Nginx must be public. MariaDB only needs localhost. rpcbind is only for NFS and can usually go.
For more on reading this output, see Check which ports are open and listening on Linux.
Step 2: list enabled services
Some services do not listen on the network but still run at every boot. List them:
systemctl list-unit-files --type=service --state=enabledAnd what is running right now:
systemctl list-units --type=service --state=runningLook up any name you do not recognise before you touch it:
systemctl status rpcbindThe description line usually explains its purpose.
Step 3: stop and disable what you do not need
disable --now stops a service and prevents it starting at boot:
sudo systemctl disable --now rpcbindSome services are started by a socket unit that wakes them on demand. Disable the socket too:
sudo systemctl disable --now rpcbind.socketIf you are sure you will never need a service, mask it. That blocks it from being started, even by another package:
sudo systemctl mask rpcbind.service rpcbind.socketUndo with sudo systemctl unmask <name>.
Common candidates on a web or app server, if you do not use them:
rpcbindand NFS services.cups(printing).avahi-daemon(local network discovery).postfixorexim4listening on the public IP when you do not send mail from this server.- Old FTP servers such as
vsftpd. Use SFTP over SSH instead.
Remove the package if you never need it
Disabling is reversible. Removing is cleaner when you are sure:
sudo apt purge rpcbind # Ubuntu and Debian
sudo dnf remove rpcbind # AlmaLinux and Rocky LinuxRead the list of packages the command will remove before you confirm. Some packages pull others with them.
Step 4: bind private services to localhost
Services used only by programs on the same server should listen on 127.0.0.1. Examples:
- MariaDB/MySQL: set
bind-address = 127.0.0.1in the[mysqld]section of the server config, then restart. - Redis: set
bind 127.0.0.1 -::1inredis.conf, then restart. - Node.js, Python or other apps behind Nginx: listen on
127.0.0.1:3000and let Nginx proxy public traffic. - Docker containers: publish with
-p 127.0.0.1:8080:80instead of-p 8080:80.
Check the result with sudo ss -tulpn. The address should now start with 127.0.0.1.
Tip: If you need to reach a private service from your own computer, such as a database GUI, use an SSH tunnel instead of opening the port. See SSH local port forwarding.
Step 5: firewall the rest
What is left should be the services that must be public. Use a firewall with a default-deny policy for incoming traffic, and allow only those ports.
With UFW on Ubuntu or Debian:
sudo ufw allow 22/tcp
sudo ufw allow 80,443/tcp
sudo ufw default deny incoming
sudo ufw enableAllow your SSH port first. If you use a custom SSH port, replace 22. On AlmaLinux and Rocky Linux, use firewalld instead.
Check it works
- Run
sudo ss -tulpnagain. Only the expected services should be on0.0.0.0or[::]. - From another machine, scan your own server:
nmap -Pn 203.0.113.10Only the public ports should appear as open.
- Reboot once and repeat. Disabled services must stay off.
Troubleshooting
- A website broke after disabling a service: re-enable it with
sudo systemctl enable --now <name>and check its role before trying again. - A disabled service came back: another unit or a socket starts it. Disable the
.socketunit too, ormaskthe service. - App cannot reach the database after binding to localhost: the app connects to the public IP. Change its config to
127.0.0.1. - Container port still public after editing UFW: Docker publishes ports around UFW. Bind it to
127.0.0.1in the-poption instead.
Next steps
- Harden SSH, the one service every server keeps public: SSH hardening checklist.
- Learn which ports to expose: Common server ports and what uses them.
- Keep what is left patched: Turn on automatic security updates.
