本文介绍每次上手一台新的 Linux 服务器后一些 基本设置步骤,包括:
使用一键 DD 脚本重装纯净的 Debian GNU/Linux 操作系统
并不一定适合所有人的需求,仅作为快速设置新服务器的速查手册。建议搭配以下内容一起使用:
Cloudflare WAF 防护策略简易指南
服务器使用 Cloudflare CDN 的最佳实践
网络上支持一键 DD 重装系统的脚本有很多,主要使用这两个:
前者支持的系统选择更多,后者则专注于 Debian GNU/Linux 的最小发行版。两者都在 GitHub 上开源,且经过长期的社区关注和审查,安全性大可以放心。
| 无论如何,在正式开始之前,仔细阅读一遍脚本作者的说明(文档说明详细易读),知道自己在做什么,预期会出现什么。 |
我在所有服务器上都一直使用 Debian,下面以 debi 脚本为例。
curl -fLO https://raw.githubusercontent.com/bohanyang/debi/master/debi.sh |
赋予脚本可执行权限并开始重装系统
chmod +x debi.sh sudo ./debi.sh \ --version 13 \ --architecture arm64 \ --cloudflare \ --user viamoe \ --authorized-keys-url https://github.com/githubUserName.keys \ --ssh-port 22122 \ --bbr |
上面参数的意义:
一切就绪,开始重装系统
sudo reboot |
期间可以使用 VNC 查看系统安装进度,根据服务器性能或网络环境,稍等几分钟,新的系统即将就绪。之后,使用重装阶段预设的用户名密码或 SSH 密钥登录服务器。
内核启用 BBR 只需设置 net.ipv4.tcp_congestion_control=bbr |
我们的服务器正在运行的是一个纯净、最小、最新的 Debian GNU/Linux 操作系统,需要安装一些基础或常用的软件包。
sudo apt update sudo apt install \ ca-certificates \ apt-transport-https \ man \ build-essential \ git \ curl \ wget \ unzip \ screen \ btop \ net-tools \ tree \ neovim |
参考 Docker 官方文档 说明的步骤,下列命令不保证时效性
# Add Docker's official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # Add the repository to Apt sources: echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl status docker |
同样地,参考 Nginx 项目文档——在 Debian 上的 安装步骤
# install the prerequisites sudo apt install curl gnupg2 ca-certificates lsb-release debian-archive-keyring # import gpg key curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \ | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null # verify gpg key mkdir -m 700 ~/.gnupg gpg --dry-run --quiet --no-keyring --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \ http://nginx.org/packages/debian `lsb_release -cs` nginx" \ | sudo tee /etc/apt/sources.list.d/nginx.list # install nginx sudo apt update sudo apt install nginx # start nginx sudo systemctl start nginx |
一般而言,swap 设置为 RAM 大小即可,适当 的 swap 在一定程度上避免 Linux 内核 OOM。这里我们通过设置 Swap File 的方式,它更加灵活。
# check swap status, there should be no output sudo swapon --show # create a swapfile equal to RAM # my vds has a 8GB RAM sudo fallocate -l 8G /swapfile # or instead of fallocate, use dd command # 8192 * 1MB = 8GB sudo dd if=/dev/zero of=/swapfile bs=1M count=8192 # set permission sudo chmod 600 /swapfile # format a swap area sudo mkswap /swapfile # enable swapfile sudo swapon /swapfile # check swap file sudo swapon --show free -h # update fstab auto mount when linux started sudo nano /etc/fstab # add following line to end /swapfile none swap sw 0 0 # check fstab sudo mount -a |
Linux 还有一个 swappiness 参数控制系统对 swap 的倾向,感兴趣可以 Google 一下。
编辑 SSH 配置文件
sudo nvim /etc/ssh/sshd_config |
这里有一些需要关注的配置项目:
保持当前的 SSH 会话不要断开连接,重启 SSH 服务
sudo systemctl restart sshd |
重开一个 SSH 会话,再次登录当前服务器,确保连接正常,否则返回第一个 SSH 会话中进行检查。
使用 Fail2ban 保护我们的服务器免受 SSH 爆破,并合理封禁异常的 IP 地址
sudo apt update sudo apt install fail2ban |
创建一个最小的,仅用于守护 SSH 登录的 Fail2ban 配置文件
sudo nvim /etc/fail2ban/jail.local |
配置内容如下:
[DEFAULT] # 忽略 IP 地址:防止将自己封禁。 # 如果您使用动态 IP,不要设置您的公网 IP。 # 127.0.0.1/8 和 ::1 忽略本地回环地址。 ignoreip = 127.0.0.1/8 ::1 # 封禁时间:封禁 1 小时(3600 秒) bantime = 1h # 查找时间:在 10 分钟内(600 秒) findtime = 10m # 最大重试次数:如果在 findtime (10 分钟) 内失败了 3 次,则封禁。 maxretry = 3 [sshd] # 启用 SSH 保护 enabled = true # SSH 服务的监听端口:确保这里设置为您的自定义端口 22122 port = 22122 # 日志文件路径:Debian/Ubuntu/CentOS 通常使用 auth.log,默认配置会自动检测。 # logpath = /var/log/auth.log # filter:使用默认的 sshd 过滤器(无需修改) # filter = sshd |
sudo systemctl enable fail2ban sudo systemctl restart fail2ban |
sudo fail2ban-client status sshd sudo fail2ban-client status |
如果输出显示 Jail status: ... Status: running,在 Jail list 中看到了 sshd,应该就配置成功了。
sudo apt install ufw |
默认拒绝所有传入/入站连接
sudo ufw default deny incoming |
默认允许所有传出/出站连接
sudo ufw default allow outgoing |
现在大多数服务器有 IPv6 公网 IP,启用 IPv6 支持
sudo sed -i 's/IPV6=no/IPV6=yes/' /etc/default/ufw |
允许 SSH 传入/入站连接
| 千万不要忘记 SSH 端口,以免自己被「锁在」UFW 外面! |
替换为实际 SSH 端口
sudo ufw allow 22122/tcp |
Nginx 默认监听端口 80(HTTP) 和 443(HTTPS),要允许 Cloudflare CDN 回源访问,必需将 Cloudflare IP 段添加到 UFW 白名单里。
新建一个 Cloudflare IPs 自动更新脚本
sudo nvim /usr/local/bin/ufw-cf-whitelist |
该脚本用于:
脚本内容如下:
#!/bin/bash # Cloudflare 官方 IP 列表 IPV4_URL="https://www.cloudflare.com/ips-v4" IPV6_URL="https://www.cloudflare.com/ips-v6" echo "=== 1. 清除现有的 Cloudflare UFW 规则..." # 清除旧的规则(通过删除带有 "Cloudflare" 注释的规则) # 注意:UFW 不直接支持注释删除,所以这个步骤需要手动或使用更复杂的工具, # 这里我们采用简单的清理,如果规则不多,可以手动检查 `ufw status numbered`。 # 更好的方法是在每次运行前删除所有 80/443 的 ALLOW 规则,但风险更高。 # 暂时只删除明确的 Cloudflare 规则,以防万一 # sudo ufw delete allow in on any to any port 80/tcp # sudo ufw delete allow in on any to any port 443/tcp echo "=== 2. 下载最新的 Cloudflare IP 范围列表..." # 使用 tempfile 确保下载的文件安全 IPV4_TEMP=$(mktemp) IPV6_TEMP=$(mktemp) curl -s $IPV4_URL -o $IPV4_TEMP curl -s $IPV6_URL -o $IPV6_TEMP echo "=== 3. 添加 IPv4 规则 (80/443)..." while read ip; do if [[ ! -z "$ip" ]]; then sudo ufw allow proto tcp from $ip to any port 80 comment 'Cloudflare IPv4 HTTP' sudo ufw allow proto tcp from $ip to any port 443 comment 'Cloudflare IPv4 HTTPS' fi done < $IPV4_TEMP echo "=== 4. 添加 IPv6 规则 (80/443)..." while read ip; do if [[ ! -z "$ip" ]]; then sudo ufw allow proto tcp from $ip to any port 80 comment 'Cloudflare IPv6 HTTP' sudo ufw allow proto tcp from $ip to any port 443 comment 'Cloudflare IPv6 HTTPS' fi done < $IPV6_TEMP echo "=== 5. 清理临时文件..." rm $IPV4_TEMP $IPV6_TEMP echo "=== 6. 完成 Cloudflare 白名单配置。" ufw status numbered |
赋予脚本可执行权限
sudo chmod +x /usr/local/bin/ufw-cf-whitelist |
执行脚本,更新 ufw 规则
sudo ufw-cf-whitelist |
启动 ufw 服务
sudo ufw enable sudo ufw reload |
若是提示:
Command may disrupt existing ssh connections. Proceed with operation (y|n)?
检查 ufw 规则
sudo ufw status numbered |
实际上 Cloudflare IP 段几乎很少变动(最起码最近两年多完全没有变化),因此这个步骤不是必须的。使用 Crontab 计划:
sudo crontab -e |
比如每天凌晨 3 点执行一次
0 3 * * * /usr/local/bin/ufw-cf-whitelist.sh > /dev/null 2>&1 |
Docker 容器的网络,如果没有做到最佳实践,它是可以在 ufw 上「打洞」的(本质是 iptables),详情可见 chaifeng/ufw-docker。
这个 check-if-docker-break-ufw.sh 脚本用于检查是否出现了这种情况
#!/usr/bin/env bash
# ===============================================================
# check-docker-ports.sh
# 检查 Docker 容器端口暴露与 UFW 状态
# ===============================================================
RED="\033[0;31m"
GREEN="\033[0;32m"
YELLOW="\033[1;33m"
RESET="\033[0m"
echo "🔍 正在检查 Docker 端口暴露情况..."
echo "=============================================================="
# 获取 docker ps 输出
docker_ps=$(docker ps --format '{{.Names}}|{{.Ports}}')
if [ -z "$docker_ps" ]; then
echo "❌ 没有正在运行的容器。"
exit 0
fi
printf "%-25s %-25s %-20s\n" "容器名" "绑定地址" "状态"
echo "--------------------------------------------------------------"
while IFS='|' read -r name ports; do
if [[ -z "$ports" ]]; then
printf "%-25s %-25s ${GREEN}%-20s${RESET}\n" "$name" "无对外端口" "安全"
continue
fi
# 分析每个映射
for port in $(echo "$ports" | tr ',' '\n'); do
port=$(echo "$port" | sed 's/ //g')
if [[ "$port" =~ 0\.0\.0\.0 ]]; then
printf "%-25s %-25s ${RED}%-20s${RESET}\n" "$name" "$port" "⚠️ 公网暴露"
elif [[ "$port" =~ 127\.0\.0\.1 ]]; then
printf "%-25s %-25s ${GREEN}%-20s${RESET}\n" "$name" "$port" "本机安全"
else
printf "%-25s %-25s ${YELLOW}%-20s${RESET}\n" "$name" "$port" "内部网络"
fi
done
done <<< "$docker_ps"
echo "--------------------------------------------------------------"
echo "🧱 检查宿主机端口监听状态..."
ss -tlnp | grep -E 'docker|LISTEN' | awk '{print $4, $6}' | sed 's/users://g' | sed 's/"//g' | sed 's/,//g' | sed 's/\[::\]://g'
echo "--------------------------------------------------------------"
echo "🧩 检查 UFW 规则..."
sudo ufw status numbered
echo "=============================================================="
echo "✅ 检查完成。绿色=安全,黄色=内部安全,红色=公网暴露。" |
最简单的方式,不要将任何 Docker 容器的端口绑定到 0.0.0.0 上,只需将暴露端口绑定到 127.0.0.1 上即可。
Cloudflare IP 段是纯网段,检查时需要做 CIDR 运算,这里使用 ipcalc 和 sipcalc
sudo apt install ipcalc sipcalc |
下面是一个巡检脚本,用于检查所有的 Nginx 虚拟主机的访问日志中的 IP 是否属于 Cloudflare IPs
#!/usr/bin/env bash
set -u
NGINX_DIR="/var/log/nginx"
RECENT_LINES=2000
TMP_CF="/tmp/cf_all_ips.txt"
TMP_LOG="/tmp/nginx_logs_combined.txt"
cleanup(){
rm -f "$TMP_CF" "$TMP_LOG"
}
trap cleanup EXIT
echo "=== 获取 Cloudflare IPv4/IPv6 列表… ==="
curl -s https://www.cloudflare.com/ips-v4/ > "$TMP_CF"
curl -s https://www.cloudflare.com/ips-v6/ >> "$TMP_CF"
if [ ! -s "$TMP_CF" ]; then
echo "❌ 失败:无法获取 Cloudflare IP 列表"
exit 1
fi
echo "Cloudflare IP 段已加载:"
wc -l "$TMP_CF"
echo ""
echo "=== 收集 Nginx 日志… ==="
mapfile -d $'\0' LOGS < <(find "$NGINX_DIR" -maxdepth 1 -type f \( -iname "*access.log" -o -iname "*.log" -o -iname "*.gz" \) -print0)
if [ ${#LOGS[@]} -eq 0 ]; then
echo "未找到任何 Nginx access 日志"
exit 0
fi
# 合并最近日志
> "$TMP_LOG"
for LOG in "${LOGS[@]}"; do
if [[ "$LOG" =~ \.gz$ ]]; then
zcat "$LOG" 2>/dev/null >> "$TMP_LOG"
else
cat "$LOG" 2>/dev/null >> "$TMP_LOG"
fi
done
echo "合并日志完成:$(wc -l < "$TMP_LOG") 行"
echo ""
###################################
# 提取最近访问 IP 并对比 CF
###################################
echo "=== 检查最近 $RECENT_LINES 行访问中非 Cloudflare IP ==="
# 提取 IP
tail -n "$RECENT_LINES" "$TMP_LOG" | awk '{print $1}' | sort -u > /tmp/nginx_ips.txt
echo ""
echo "开始比对..."
# 逐个判断是否属于 CF 段
NON_CF_IPS=()
while read -r ip; do
if ! grep -qE "^$(echo "$ip" | sed 's/\./\\./g')(/|$)" "$TMP_CF"; then
# 不是直接文本匹配,而是 CIDR 检查
match=""
while read -r cidr; do
if [[ "$cidr" == *.* ]]; then
# IPv4 CIDR check
if ipcalc -c "$ip" "$cidr" >/dev/null 2>&1; then
match="1"
break
fi
else
# IPv6 CIDR
if sipcalc "$ip" "$cidr" >/dev/null 2>&1; then
match="1"
break
fi
fi
done < "$TMP_CF"
if [ -z "$match" ]; then
NON_CF_IPS+=("$ip")
fi
fi
done < /tmp/nginx_ips.txt
echo ""
if [ ${#NON_CF_IPS[@]} -eq 0 ]; then
echo "✔ 没有发现绕过 Cloudflare 的 IP(全部访问来自 Cloudflare)"
else
echo "⚠ 检测到 **非 Cloudflare** 源站访问(注意安全):"
printf '%s\n' "${NON_CF_IPS[@]}"
fi
echo ""
echo "=== 检查完成,临时文件已删除 ✔ ===" |
示例效果:
sudo ./check-security.sh === 获取 Cloudflare IPv4/IPv6 列表… === Cloudflare IP 段已加载: 20 /tmp/cf_all_ips.txt === 收集 Nginx 日志… === 合并日志完成:26384 行 === 检查最近 2000 行访问中非 Cloudflare IP === 开始比对... ✔ 没有发现绕过 Cloudflare 的 IP(全部访问来自 Cloudflare) === 检查完成,临时文件已删除 ✔ === |