本文内容适用于 Ubuntu 系统;部分本地操作以在 macOS 下完成为例。
本文部分命令需要以
root
用户或sudo
权限执行。
最近在 VPS 上搭好了自己的 Tiny Tiny RSS 服务和这个 Blog(有关这两部分的内容我稍后也会写)。而在此之前,我在 VPS 上重新安装了 Ubuntu 18.04 LTS,本文就记录下重装后的一些简单配置过程。
安全
SSH 安全
为了避免自己的 VPS 被 ssh 弱口令扫描,我们不妨使用公私钥对进行 ssh 登录,并关闭密码登录。
生成并添加密钥
1. 在本地生成密钥
在本机 Terminal 中执行:
ssh-keygen -t ed25519 # ED25519 算法,长度固定为 256 bits,安全性和性能最佳,兼容性稍差
# 或者
ssh-keygen -t rsa -b 2048 # RSA 算法,指定密钥长度为 2048 bits,兼容性最佳
提示 Enter file in which to save the key (/Users/~/.ssh/id_ed25519):
# 使用默认文件位置,直接回车即可
提示 Enter passphrase (empty for no passphrase):
# 设置私钥的密码(留空即为免密码)
提示 Enter same passphrase again:
# 再次输入私钥的密码
此时就已经在 ~/.ssh/
下生成了 id_ed25519 (或 id_rsa)
(私钥) 和 id_ed25519.pub (或 id_rsa.pub)
(公钥) 两个文件。
2. 在服务器端添加公钥
使用 scp 命令将公钥上传到服务器:
scp ~/.ssh/id_ed25519.pub root@<VPS域名或IP>:/root/
# 输入 ssh 密码并回车确认
ssh 到服务器上,随后执行
mkdir ~/.ssh && chmod 700 ~/.ssh
mv id_ed25519.pub ~/.ssh && cd ~/.ssh
mv id_ed25519.pub authorized_keys && chmod 600 authorized_keys
此时就可以使用密钥进行 ssh 登录了。
3. macOS 本地配置[1]
如果不想每次使用密钥登录时都要输入密码,那么你可以编辑 ssh 配置文件将密码存储到钥匙串:
编辑 ssh 配置文件:vim ~/.ssh/config
写入如下内容:
Host *
AddKeysToAgent yes # 用 ssh-agent 管理密钥
UseKeychain yes # 将密码存储到钥匙串中
IdentityFile ~/.ssh/id_ed25519 # 指定私钥路径
如果你想通过代理 ssh 到你的服务器,那么再写入如下内容:
Host <VPS域名或IP>
ProxyCommand nc -X 5 -x 127.0.0.1:6153 %h %p
# nc, netcat
# -X 5, 指定使用 SOCKS5 代理
# -x, 指定代理的主机地址和端口,示例中为本机的 6153 端口
# %h %p, 用于替换 ssh 实际连接的主机地址和端口
关闭 ssh 的密码登录 & 防止 Broken pipe 错误
设置好密钥登录后,我们就可以关闭 ssh 的密码登录了。同时,为了避免在使用 ssh 时空闲连接时间过长而导致 Write failed: Broken pipe
错误,我们可以在 ssh 配置文件中设置 ClientAliveInterval
参数,这会允许服务器在一定时间内发送一个特定的包给客户端,一旦超时,则说明断线,就关闭连接,避免了空闲连接时间过长报错。
编辑 ssh 配置文件:vim /etc/ssh/sshd_config
将 PasswordAuthentication yes
改为 PasswordAuthentication no
,并去除行前的注释符号 #
将 ClientAliveInterval
一行改为 ClientAliveInterval 60
,并去除行前的注释符号 #
保存退出,执行 /etc/init.d/ssh restart
重启 ssh 服务即可。
创建普通用户并授予 sudo 权限
创建用户[2]
首先,使用 useradd
命令创建用户:
useradd -m -s /bin/bash <username>
# 添加用户名为 <username> 的用户
# -m, 自动创建用户家目录,并将 /etc/skel 中的文件复制到家目录中
# -s /bin/bash, 指定用户默认 shell 为 bash
然后为该用户设置密码:
passwd <username>
授予用户 sudo 权限[3]
一般来说,Ubuntu 系统已经自带了 sudo
;如果没有,则先安装:
apt install sudo
由于直接修改 etc/sudoers
文件有一定的危险性,因此我们采用将配置文件追加到 /etc/sudoers.d/
目录下的方式进行配置。
echo '<username> ALL=(ALL) ALL' >> /etc/sudoers.d/<username>
# 请自行将 <username> 改为你的用户名
<username> ALL=(ALL) ALL
表示普通用户 <username>
可以在任何主机上,通过 sudo
提权到任何用户/用户组,执行任何命令[4]。
如果你想免密使用 sudo
(不安全),那么上一步可以改为:
echo '<username> ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/<username>
解决部分机器 sudo 提示 unable to resolve host[5]
部分服务器在执行 sudo
时,会弹出以下错误:
$ sudo true
sudo: unable to resolve host <hostname>
尽管不影响使用,但看着总让人不爽。这个错误是因为服务商提供的默认系统镜像没有将主机名 (hostname) 添加到 /etc/hosts
文件中,导致系统无法解析 hostname。使用以下命令即可解决:
echo "127.0.0.1 $(hostname)" | sudo tee -a /etc/hosts
为用户启用 SSH[6]
首先切换到刚刚创建的用户:
su - <username>
随后,建立 ~/.ssh
目录,并将 root
用户下的 authorized_keys
复制过来:
mkdir -p ~/.ssh/
sudo cp /root/.ssh/authorized_keys ~/.ssh/
最后设置正确的访问权限:
sudo chown -R <username>:<username> ~/.ssh
sudo chmod 700 ~/.ssh
sudo chmod 600 ~/.ssh/authorized_keys
为了提高安全性,你还可以关闭 root
用户的 ssh 登录。编辑 ssh 配置文件:vim /etc/ssh/sshd_config
将 PermitRootLogin yes
改为 PermitRootLogin no
,并去除行前的注释符号 #
保存退出,执行 /etc/init.d/ssh restart
重启 ssh 服务即可。
防火墙配置
安装 fail2ban
fail2ban 是 Linux 上一个著名的入侵保护的开源框架,它能通过 iptables 将尝试暴破 ssh 密码的 IP 封停,有效防止暴力破解攻击。
apt install fail2ban
安装并配置 UFW 防火墙[7]
ufw,即 uncomplicated firewall(简单防火墙),是 Ubuntu 系统默认的防火墙组件,它可以简化对 iptables 的配置。
- 如果未安装 ufw,则先进行安装:
apt install ufw
# Ubuntu 系统已经自带了 ufw 防火墙
- 添加基本的 ufw 规则
ufw allow http # 允许 80 端口传入连接
ufw allow https # 允许 443 端口传入连接
ufw allow ssh # 允许 22 端口传入连接
- 添加自定义端口
ufw allow 端口号
ufw allow 10000:20000/tcp # 此处指定了 10000 到 20000 的端口范围,并指定只允许 TCP 连接
- 开启 ufw
ufw enable
# 会提示你可能影响 ssh 连接,务必确认已将你的 ssh 端口加入到 ufw rules 中,然后按 y 确认开启
- 查看 ufw 状态
ufw status
- 删除某条规则
ufw delete allow 端口号
使用 NTP 同步系统时间
许多服务都要求服务器时间与标准时间误差不能过大,因此我们使用 NTP 进行时间同步。
apt install ntp
设置时区[8]
切换到与所在地相同的时区可以方便 crontab 等服务的设置。
查看可用时区列表:
timedatectl list-timezones
设定时区:
timedatectl set-timezone Asia/Hong_Kong # 将时区设置为 Asia/Hong_Kong
查看当前时间设定:
timedatectl
配置无人值守更新(可选)[9]
一般来说,Ubuntu 系统已经自带了 unattended-upgrades
包;如果没有,则先安装:
apt install unattended-upgrades
随后,通过以下命令启用无人值守更新:
echo "unattended-upgrades unattended-upgrades/enable_auto_updates boolean true" | debconf-set-selections
DEBIAN_FRONTEND=noninteractive dpkg-reconfigure unattended-upgrades
通过编辑 /etc/apt/apt.conf.d/
下的 20auto-upgrades
和 50unattended-upgrades
两个配置文件,我们可以设定无人值守更新。
/etc/apt/apt.conf.d/20auto-upgrades
APT::Periodic::Update-Package-Lists "1";
# 自动更新软件包列表周期(天),0 为关闭
APT::Periodic::Download-Upgradeable-Packages "0";
# 自动下载可更新的软件包周期(天),0 为关闭
APT::Periodic::AutocleanInterval "0";
# 自动清理周期(天),0 为关闭
APT::Periodic::Unattended-Upgrade "1";
# 自动更新软件包周期(天),0 为关闭
/etc/apt/apt.conf.d/50unattended-upgrades
以下为一些关键的设置项:
Unattended-Upgrade::Allowed-Origins {
... ...
// "${distro_id}:${distro_codename}-updates";
// 自动更新(除安全更新外的)软件包,去除行前的注释符号 // 以开启
// 注意:开启此选项有风险
... ...
};
... ...
//Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
// 自动移除无用内核,去除行前的注释符号 // 以开启
... ...
//Unattended-Upgrade::Remove-Unused-Dependencies "false";
// 自动移除无用依赖,去除行前的注释符号 // 并将 false 改为 true 以开启
// 等同于自动 apt-get autoremove
本配置文件还提供了许多其他的设置项,并且有详细的注释,可自行查看编辑。
启用 Canonical Livepatch 服务(可选)
在 Ubuntu 16.04 及以上的系统中,可以使用 Canonical Livepatch 服务实现无需重启系统就能安装内核安全补丁。同时,Canonical Livepatch 服务对于不超过 3 台机器的个人用户是免费的。
1. 在 Canonical Livepatch Service 官网 上选择 Ubuntu user
,点击 Get your Livepatch token
2. 按要求注册(或登录)Ubuntu One 账号,获取 token
3. ssh 到服务器上,随后安装 Snapd 守护程序:
apt install snapd
4. 安装 Canonical Livepatch 守护程序:
snap install canonical-livepatch
5. 启用 Canonical Livepatch:
canonical-livepatch enable <token>
# 请自行将 <token> 换成第二步中获取的 token
6. 查看 Canonical Livepatch 状态:
canonical-livepatch status
7. 手动更新补丁:
canonical-livepatch refresh
8. 停用 Canonical Livepatch:
snap run canonical-livepatch disable
注意:安装内核补丁与安装新内核不同,如果安装了新内核,则必须重新启动系统以应用新内核。
优化
提高文件并发数
echo '* soft nofile 51200
* hard nofile 51200' >> /etc/security/limits.conf
然后,执行:
ulimit -n 51200
设置虚拟内存
某些 VPS 服务商会预先设置好虚拟内存,所以我们先查看系统当前可用内存:
free -m
输出样例:
$ free -m
total used free shared buff/cache available
Mem: 982 277 112 1 592 533
Swap: 1023 2 1021
如果输出中没有上面的 Swap
一行或 Swap
后为 0,则表示系统没有设置虚拟内存,可以参考下面的方法设置:
1. 创建 swapfile
dd if=/dev/zero of=/root/swapfile bs=1M count=1024
# if 表示 input file (输入文件),of 表示 output file (输出文件),bs 表示 block size (块大小),count 表示计数
示例中采用的数据块大小为 1M,数据块计数为 1024,所以分配的空间就是 1G 大小。
2. 设置正确的 swapfile 权限
chmod 600 /root/swapfile
3. 格式化 swapfile
mkswap /root/swapfile
4. 启用 swapfile
swapon /root/swapfile
5. 设置开机自动加载虚拟内存
echo "/root/swapfile swap swap defaults 0 0" >> /etc/fstab
此时再执行 free -m
即可看到虚拟内存已经启用了。
开启 BBR 拥塞控制算法
BBR 是 Google 出品的 TCP 拥塞控制算法,其目的是尽量跑满带宽,可以起到单边加速 TCP 连接的效果。BBR 是内嵌在 Linux 内核中的,而 Linux kernel 4.9 已加入了该算法。如果你的系统内核版本低于 4.9,则需要先升级 Linux 内核。
# Ubuntu 18.04 及以上系统内核版本已高于 4.9,可直接开启 BBR
1. 开启 BBR
echo "net.core.default_qdisc=fq" >> /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf
然后,执行 sysctl -p
保存生效。
2. 查看 BBR 是否成功开启
执行 sysctl net.ipv4.tcp_available_congestion_control
, 结果中应有 bbr
。
执行 lsmod | grep bbr
,结果中应有 tcp_bbr
。
输出样例:
自动升级内核
默认情况下,Ubuntu LTS 将保持在最初发布的 Linux 内核上。而 Ubuntu 硬件支持栈(Hardware Enablement Stacks, HWE)可以为现有的 Ubuntu LTS 提供更新的内核支持,实现内核自动升级,方便我们体验较新内核的特性[10]。
1. 查看当前系统内核版本
uname -r
2. 安装 HWE
使用包管理器即可安装 HWE:
apt install --install-recommends linux-generic-hwe-20.04 # For Ubuntu 20.04 LTS
apt install --install-recommends linux-generic-hwe-18.04 # For Ubuntu 18.04 LTS
3. 重启系统以应用新内核
reboot
4. 删除旧内核(可选)[11]
使用包管理器自动卸载即可:
apt autoremove --purge
如果只想保留当前最新的内核,可以使用以下命令 (有风险,慎用):
apt purge $(dpkg -l | grep -E "linux-(image|headers|modules)" | grep -v hwe | grep -v $(uname -r | cut -d '-' -f1,2) | awk '{print $2}')
或者也可以手动卸载:
卸载旧版 linux-image:
获取 linux-image 列表:
dpkg -l | grep linux-image
卸载旧版 linux-image:
apt purge <旧linux-image名>
卸载旧版 linux-headers:
获取 linux-headers 列表:
dpkg -l | grep linux-headers
卸载旧版 linux-headers:
apt purge <旧linux-headers名>
卸载旧版 linux-modules:
获取 linux-modules 列表:
dpkg -l | grep linux-modules
卸载旧版 linux-modules:
apt purge <旧linux-modules名>
其他
安装 Docker-CE
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。iOS 上的 HyperApp 也正是借助 Docker 实现了自动部署应用。
安装 Docker 最方便的方式是使用官方脚本[12]:
wget -qO- get.docker.com | bash
对于国内 VPS,还可以指定使用阿里云镜像进行安装:
wget -qO- get.docker.com | bash -s docker --mirror Aliyun
以普通用户管理 Docker[13]
由于 Docker 守护进程绑定在 Unix socket 上,而 Unix socket 默认仅属于 root
用户,因此在通过普通用户管理 Docker 时,我们需要在命令前加上 sudo
。为了方便使用,我们可以将普通用户添加到 docker
用户组中,这样就能直接使用 docker
命令而不需要 sudo
。
1. 创建 docker
用户组
sudo groupadd docker
# 较新的 Docker 安装中可能已经添加了 docker 用户组
2. 将当前用户加入 docker
组
sudo gpasswd -a $USER docker
3. 登出并重新登入当前用户
或者,你也可以使用 newgrp
命令以 docker
组重新登入系统:
newgrp docker
此时就可以直接使用 docker
命令管理容器了。
限制 Docker 容器日志大小
Docker 容器运行过程中会产生大量日志文件,如果不加限制可能会导致 VPS 磁盘空间不足。通过编辑 Docker 配置文件,我们可以限制容器日志大小。
编辑 /etc/docker/daemon.json
文件:vim /etc/docker/daemon.json
添加 log-dirver
和 log-opts
参数:
{
"log-driver": "json-file",
"log-opts": { "max-size": "50m", "max-file": "3" }
}
# 样例中一个容器的日志大小上限是 50 M,最多创建 3 个日志文件
对于国内 VPS,还可以在配置文件中配置国内镜像源:
{
"log-driver": "json-file",
"log-opts": { "max-size": "50m", "max-file": "3" },
"registry-mirrors": ["https://<阿里云ID>.mirror.aliyuncs.com","https://registry.docker-cn.com"]
}
# 阿里云源地址可以在 容器镜像服务 > 镜像加速器 下查看
保存退出,随后加载配置文件并重启 Docker:
systemctl daemon-reload
systemctl restart docker
# 设置的日志大小,只对新建的容器有效
手动清理 Docker 容器日志
docker ps | awk '{if (NR>1){print $1}}' | xargs docker inspect --format='{{.LogPath}}' | xargs truncate -s 0
解决 UFW 对 Docker 容器无效的问题[14]
在使用 Docker 暴露容器端口时,Docker 会直接操作 iptables NAT 规则,导致 ufw 无法管理 Docker 所发布的端口,这是一个严重的安全问题。好在通过编辑 ufw 的 after.rules
,我们能够解决这一问题。
编辑 ufw 的 after.rules
:vim /etc/ufw/after.rules
在末尾写入如下内容:
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER
保存退出,随后重启 ufw:
systemctl restart ufw
# 如果重启 ufw 之后规则没有生效,则需要重启服务器。
如果你要允许外部网络访问所有 Docker 发布的、内部端口为 80
的服务,则执行:
ufw route allow from any to any port 80
ufw route allow proto tcp from any to any port 80 # 仅允许 TCP 协议
# 此处的 80
是容器内部端口,而非使用 -p 8080:80
选项发布在服务器上的 8080
端口。