We often need to buy servers for service scalability, bypassing The Firewall, or for various other reasons. However, I’ve noticed that many people are deploying services in an unsafe way, and some of their practices are actually exposing vulnerabilities to attackers. So here are some security tips you should keep in mind after booting up a new VPS.
SSH Login
We’re going to change the configuration of sshd, but there are a few things you should be aware of. If your VPS provider uses cloud-init (which is quite common), there will be a 50-cloud-init.conf file in the /etc/ssh/sshd_config.d directory. Files in this directory override the same options in /etc/ssh/sshd_config, so a better (and recommended) way to change the configuration is to create a 00-custom.conf file in /etc/ssh/sshd_config.d. The “00” prefix means this file will be loaded with the highest priority and will override the same settings in any other files.
Login Port
By default, port 22 is used for SSH connections, but keeping this port open attracts a lot of brute‑force attacks. Changing it to another port is a common practice to harden your server.
Adding the following line to the 00-custom.conf file will change the port to 2025. Of course, you can use any port that isn’t already in use by other software.
Port 2025
Keep in mind that this is not a real security measure; it only reduces noise from automated scans and brute‑force attempts.
Authentication
This is the most important part of SSH security. First of all, I strongly recommend not using password authentication for any server exposed to the Internet. Instead, you should use key pairs to log in to the server. For instructions on how to generate key pairs, you can refer to SSH Academy. To disable password logins, add the following line:
PasswordAuthentication no
Logging in directly as root is also not very safe — a single mistaken command like rm -rf / can be disastrous — but root privileges are often needed for maintenance. Whether you allow direct root login is up to you. You can choose to disable it:
PermitRootLogin no
or just disable password logins for root:
PermitRootLogin prohibit-password
However, this is actually not necessary, because we have already disabled password logins earlier.
You can always add more features to harden the server, such as fail2ban or two‑factor authentication (2FA).
The configuration I recommend is:
# /etc/ssh/sshd_config.d/00-custom.conf
Port 2025
PasswordAuthentication no
# Optional
# PermitRootLogin no
The whole options available could be found at man page.
After changing the configuration, restart the SSH service:
# If you are using Ubuntu/Debian
sudo systemctl restart ssh
Make sure you can connect to your server in a new session before closing the existing connection!
Firewall
For people who just want to drop unwanted packets and don’t need to manipulate traffic, you can simply use ufw instead of iptables or nftables. It’s quite simple.
Use ufw
Make sure you have it installed. If not, install it with:
# Ubuntu/Debian
sudo apt update && sudo apt install -y ufw
By default, ufw denies incoming packets, allows outgoing packets, and denies routed packets. Normally we need to connect to the server over SSH, so allow it by running:
# Note: If you changed the SSH port in the previous section,
# update the port number here, otherwise you will not be able to
# connect to the server.
sudo ufw allow 2015/tcp
If you use a static IP address to access your server, it’s safer to allow only whitelisted IPs to access the SSH port. In that case, the rule should look like this:
sudo ufw allow from 12.34.56.78 proto tcp to any port 2015
If your server is going to provide HTTP/HTTPS or any other services, add the corresponding rules:
sudo ufw allow 443
sudo ufw allow 80
After adding all the firewall rules, you can check them at any time with:
sudo ufw status
If everything looks good, you can enable it now:
sudo ufw enable
Use nftables
On recent versions of Ubuntu and Debian, nftables is installed but not enabled by default, and you can edit the rules before enabling it. nftables uses /etc/nftables.conf by default. Remember to back it up before editing, just in case.
The file should look like this:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter;
}
chain forward {
type filter hook forward priority filter;
}
chain output {
type filter hook output priority filter;
}
}
Now we want to allow SSH connections and some other services while denying all unexpected packets, so add the rules in the input chain:
chain input {
type filter hook input priority filter; policy drop;
# allow local loop
iif "lo" accept
# allow established connections
ct state established,related accept
# drop all invalid packages
ct state invalid drop
# Allow ping(Optional)
# ip protocol icmp accept
# ip6 nexthdr icmpv6 accept
# Allow SSH
tcp dport 2015 accept
}
If you only want a specific IP to access SSH, change the rule to:
tcp dport 2015 ip saddr { 12.34.56.78 } accept
Then the whole configuration file should look like this:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
# allow local loop
iif "lo" accept
# allow established connections
ct state established,related accept
# drop all invalid packages
ct state invalid drop
# Allow ping(Optional)
# ip protocol icmp accept
# ip6 nexthdr icmpv6 accept
# Allow SSH
tcp dport 2015 accept
}
chain forward {
type filter hook forward priority filter;
}
chain output {
type filter hook output priority filter;
}
}
After editing the configuration, check it with:
sudo nft --check -f /etc/nftables.conf
If there’s no output, it means there are no errors, and you can enable it by running:
# On Ubuntu/Debian
sudo systemctl enable --now nftables
Again, you should always check that you can still connect to the server before closing existing connections.
Service Permissions
Even if you keep using the root account for more convenient maintenance, it’s not wise to run all services with root privileges, because if a single service is compromised, the entire VPS will be at risk. A simple approach is to create separate accounts with nologin shells to run different applications. When running web servers like nginx, Apache, or Caddy, you’ll notice that they only launch the parent process with elevated privileges and use child processes running under accounts like www-data to handle requests.
Also, running a script as root means you are effectively giving the author root privileges for that period of time. Make sure you know what it does before executing any command or script, especially when using the root account.