Safety on a New VPS
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 ipv6 router discovery, neighbor-solicitation, etc
ip6 nexthdr icmpv6 icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert, packet-too-big } accept
# Allow ping(Optional)
# ip protocol icmp accept
# ip6 nexthdr icmpv6 accept
# Allow SSH
tcp dport 2015 accept
# Allow HTTP/HTTPS traffic
tcp dport 80 accept
tcp dport 443 accept
# If your site supports QUIC
udp dport 443 accpept
}
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 ipv6 router discovery, neighbor-solicitation, etc
ip6 nexthdr icmpv6 icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert, packet-too-big } accept
# Allow ping(Optional)
# ip protocol icmp accept
# ip6 nexthdr icmpv6 accept
# Allow SSH
tcp dport 2015 accept
# Allow HTTP/HTTPS traffic
tcp dport 80 accept
tcp dport 443 accept
# If your site supports QUIC
udp dport 443 accpept
}
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.
—————-我是分割线—————–
考虑到本博客的受众更多使用中文,所以还是再写一份中文版本。
新 VPS 的安全管理
不管是为了扩容服务、科学上网还是就是想屯,大家总会买新服务器~~(即使是完全不缺)~~。但是最近发现很多人部署服务的方式并不安全,有些甚至是在暴露更多的攻击面。所以就有了这篇文章,写一下拿到 VPS 之后的简单加固。
SSH 登录
这部分的调整需要更改 sshd 的配置,有一些点需要注意。如果你的 VPS 商家使用了 cloud-init 进行初始化(非常常见),就会有一个 50-cloud-init.conf 文件在 /etc/ssh/sshd_config.d 的路径下。如果这个文件包含了和 /etc/ssh/sshd_config 中相同的选项,则会覆盖这个选项,所以更好的(也是推荐的)方式是新建一个 00-custom.conf 文件在 /etc/ssh/sshd_config.d 路径下。前缀 “00” 表示他会以最高优先级加载并覆盖其他文件的所有相同关键字,即保证该文件的设置生效。
登录端口
默认情况下,SSH 连接使用 22 端口,但是使用这个端口容易吸引爆破攻击。把它改了是一个很常见的规避暴力攻击的方式。
在 00-custom.conf 文件中添加这样一行可以把端口改到 2025,当然你也可以使用任何没有被其他软件占用的端口。
Port 2025
不过需要注意的是,这只能减少一定自动化扫描和暴力破解的数量,并不能带来本质上的安全性提升。
身份验证
这是 SSH 安全最关键的部分。首先,我极其不推荐在任何暴露 SSH 端口在公网上的服务器只使用密码验证,而是使用密钥对进行登录。对于如何生成密钥对,可以参考 SSH Academy,中文教程可以参考 USTC LUG 101手册。要禁用密码登录,添加这样一行:
PasswordAuthentication no
其次,直接使用 root 账号登录也是不太安全的,万一哪天敲出来了 但是维护服务器经常需要 rm -rf / 就炸了,root 权限,所以要不要用 root 登录自行决定吧。如果选择关掉 root 登录:
PermitRootLogin no
或者只禁用 root 的密码登录:
PermitRootLogin prohibit-password
但其实这不是必要的,如果你已经跟随第一步禁用了密码登录。
当然你也可以添加其他的组件来加固 SSH,比如使用 fail2ban 以及引入多因素认证(2FA)。
我比较推荐的配置如下:
# /etc/ssh/sshd_config.d/00-custom.conf
Port 2025
PasswordAuthentication no
# 可选
# PermitRootLogin no
sshd_config 其实有很多选项,感兴趣可以移步 man page。
更改完配置之后,重启 ssh 服务:
# 用的是 Ubuntu/Debian 的话
sudo systemctl restart ssh
注意,一定要确认你能用新的 session 连上服务器之后再关闭现有连接!!
防火墙
如果你只是想过滤非法请求包,可以直接使用 ufw 来配置而不是使用 iptables 或者 nftales 这种路由表,ufw 会简单很多。
使用 ufw
确保你已经安装了它,如果没有的话就装一下:
# Ubuntu/Debian
sudo apt update && sudo apt install -y ufw
默认情况下,ufw 会拒绝所有入站请求,允许所有出站请求,并拒绝所有路由请求(其实是 forwarding)。一般来说我们需要用 SSH 连接服务器,所以要添加相关规则:
# 注意!如果你跟随上个章节更改了 SSH 的端口号,这里也要改,
# 不然的话启动防火墙你就连不上服务器了
sudo ufw allow 2015/tcp
如果你有静态 IP 地址来访问这台服务器,你可以只允许白名单地址访问 SSH 端口。这样的话规则应该这样写:
sudo ufw allow from 12.34.56.78 proto tcp to any port 2015
如果你要提供网页服务(HTTP/HTTPS)或者其他什么服务,添加对应的规则:
sudo ufw allow 443
sudo ufw allow 80
添加完规则之后,可以检查一下:
sudo ufw status
如果看起来OK,那就可以启动了:
sudo ufw enable
使用 nftables
在新版本的 Ubuntu 和 Debian 中,nftables 是默认安装但不启用的,可以先修改规则再启动服务。nftables 默认使用 /etc/nftables.conf 中的配置,建议在修改前备份原始文件。
这个文件应该长这样:
#!/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;
}
}
我们要允许 SSH 连接还有什么其他服务,并且丢弃所有乱七八糟的包,就可以在 input 链加上这些规则:
chain input {
type filter hook input priority filter; policy drop;
# 本地环路得通
iif "lo" accept
# 允许已经建立的连接
ct state established,related accept
# 丢弃所有状态无效的包
ct state invalid drop
# 允许 ipv6 路由发现、邻居发现等协议
ip6 nexthdr icmpv6 icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert, packet-too-big } accept
# 允许 ping (可选)
# ip protocol icmp accept
# ip6 nexthdr icmpv6 accept
# 允许 SSH
tcp dport 2015 accept
# 允许 HTTP/HTTPS
tcp dport 80 accept
tcp dport 443 accept
# 如果你要支持 QUIC 的话
udp dport 443 accept
}
如果你只想让特定 IP 访问 SSH,把规则改成:
tcp dport 2015 ip saddr { 12.34.56.78 } accept
这样的话,整个配置文件就成了:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
# 本地环路得通
iif "lo" accept
# 允许已经建立的连接
ct state established,related accept
# 丢弃所有状态无效的包
ct state invalid drop
# 允许 ipv6 路由发现、邻居发现等协议
ip6 nexthdr icmpv6 icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert, packet-too-big } accept
# 允许 ping (可选)
# ip protocol icmp accept
# ip6 nexthdr icmpv6 accept
# 允许 SSH
tcp dport 2015 accept
# 允许 HTTP/HTTPS
tcp dport 80 accept
tcp dport 443 accept
# 如果你要支持 QUIC 的话
udp dport 443 accept
}
chain forward {
type filter hook forward priority filter;
}
chain output {
type filter hook output priority filter;
}
}
编辑完文件之后,可以通过这条命令来检查是否存在语法错误:
sudo nft --check -f /etc/nftables.conf
如果没有输出,也就是没有错误,就可以启用服务了:
# 在 Ubuntu/Debian 上
sudo systemctl enable --now nftables
再次强调,一定要在这些更改后保证自己还能连上服务器再关闭现有的连接!
服务使用的权限
即使你为了方便选择使用 root 登录,直接使用 root 权限跑所有的服务也是不明智的,只要有一个服务被抓到了洞,整个 VPS 都危险了。有一个算是简单的方式,就是对不同的服务创建 shell 为 nologin 的账户,然后启动时使用各自的用户,这样的话风险相对小一些。就比如网页服务器们,nginx、Apache 或者 Caddy,都是只用高权限启动父进程,处理请求都使用低权限(比如 www-data)的子进程。
并且,在 root 下执行脚本某种程度上是把 root 权限在这段时间给了脚本作者,所以这其实是高危行为。在你执行任何命令或者脚本前,确保你知道它是干什么的,尤其是在 root 权限下的时候。