新服务器必做
本文是一份面向 Debian 12/13 与 Ubuntu Server 22.04/24.04/26.04 的新服务器初始化清单,默认系统使用 APT 与 systemd。命令按截至 2026-08-09 的官方文档整理;RHEL、CentOS、其他 init 系统和容器宿主的额外安全策略不在本文范围内。
远程操作前必须保留恢复入口
修改 SSH、网络、防火墙、磁盘或自动更新策略前,先确认云厂商控制台/VNC/串口控制台可用,并始终保留当前 SSH 会话。只有在第二个终端完成新路径登录测试后,才能关闭旧端口、root 登录或密码登录。
文中的 adminuser、203.0.113.10、22、22222 和 192.0.2.10/32 都是示例值,执行前必须替换。CURRENT_SSH_PORT 表示当前仍能登录的端口,SSH_PORT 表示准备迁移到的新端口;建议先在服务器当前 shell 中集中定义后续会复用的值:
ADMIN_USER=adminuser
SERVER_IP=203.0.113.10
CURRENT_SSH_PORT=22
SSH_PORT=22222
ADMIN_CIDR=192.0.2.10/32一、更新软件包
先刷新索引并查看待升级包,再交互式确认升级。不要在第一次维护时无条件使用 -y,否则包管理器的移除、服务重启或配置文件提示容易被忽略。
sudo apt update
apt list --upgradable
sudo apt upgradeapt upgrade 可以在满足依赖时安装新包,但不会删除已安装包。若确实需要处理会删除或替换包的依赖变更,先审阅模拟结果,再执行:
sudo apt --simulate full-upgrade
sudo apt full-upgrade升级内核或底层库后检查是否需要重启:
if [ -f /run/reboot-required ]; then
cat /run/reboot-required
fi二、安装常用工具
按需安装,不必为了“标准配置”堆积不使用的软件:
sudo apt install ca-certificates curl git jq tmux vim如果更习惯 Nano,可以安装或直接使用 nano。配置系统默认编辑器:
sudo update-alternatives --config editor精简镜像可能没有 command-not-found;它只用于交互式提示,并非服务器必需组件:
sudo apt install command-not-found安装后重新打开交互式 Bash。若提示数据库尚未更新,先运行一次 sudo apt update;Zsh、Fish 与非交互式 shell 不保证使用这套提示机制。
三、创建普通管理员用户
如果当前已经是具有 sudo 权限的普通用户,可以跳过本节。下列命令中的 adminuser 必须替换;若当前是 root 且系统尚未安装 sudo,先执行 apt install sudo,其余命令可去掉 sudo 前缀。
ADMIN_USER=adminuser
sudo adduser "$ADMIN_USER"
sudo adduser "$ADMIN_USER" sudo
id "$ADMIN_USER"切换到该用户的完整 login environment:
sudo -iu "$ADMIN_USER"然后在新用户的 shell 中检查 sudo 权限:
sudo -l
exit不要使用 sudo su adminuser:它通常不会建立与真正登录一致的 HOME、PATH 和 shell 环境。这里的 exit 返回原管理 shell;新用户的 sudo 组权限还要在后续真实 SSH 登录中再次验证。
四、配置主机防火墙
云厂商 Security Group/网络 ACL 与主机防火墙是两层独立控制,不能互相替代。UFW 是 Ubuntu 常用的简化前端;Debian 也可安装 UFW,但 Debian 原生防火墙框架是 nftables。若主机已有 nftables、firewalld、Docker 或其他工具维护规则,应先审计并沿用现有管理方式,不要让多个前端无意间共同改写同一 ruleset。
先确认 CURRENT_SSH_PORT 确实是当前 SSH 连接使用的服务器端口。远程 shell 应直接采用 SSH_CONNECTION 的最后一个字段;从云控制台操作时,必须根据现有 sshd/socket 配置手工设置并交叉检查监听状态。
下面的代码把“校验端口、先放行当前 SSH、再收紧默认策略、最后启用 UFW”组成一个 fail-closed block。若端口不是纯数字,block 只报错,不会启用防火墙:
if [ -n "${SSH_CONNECTION:-}" ]; then
CURRENT_SSH_PORT=${SSH_CONNECTION##* }
fi
case "${CURRENT_SSH_PORT:-}" in
''|*[!0-9]*|0|0*|??????*)
echo 'CURRENT_SSH_PORT is missing or invalid; UFW was not enabled' >&2
false
;;
*)
if [ "$CURRENT_SSH_PORT" -le 65535 ]; then
printf 'preserving current SSH TCP port: %s\n' "$CURRENT_SSH_PORT"
sudo apt install ufw &&
sudo ufw allow "${CURRENT_SSH_PORT}/tcp" comment 'temporary SSH access' &&
sudo ufw default deny incoming &&
sudo ufw default allow outgoing &&
sudo ufw enable &&
sudo ufw status verbose
else
echo 'CURRENT_SSH_PORT must be in 1..65535; UFW was not enabled' >&2
false
fi
;;
esac只有服务器实际提供 Web 服务时才开放 80/443:
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'不要照着“常用端口表”批量开放 FTP、数据库、远程桌面或管理端口。数据库和管理面板原则上只监听 loopback、私网,或仅允许可信管理 CIDR。
Docker 与 UFW
Rootful Docker 发布的容器端口可能绕过 UFW 的常规 INPUT/OUTPUT 规则。安装 Docker 后,不能仅凭 ufw status 判断容器未暴露公网;详见第七节。
五、安全迁移 SSH 登录
1. 安装并确认 OpenSSH Server
sudo apt install openssh-server
sudo systemctl status --no-pager ssh.service ssh.socket不同 Debian/Ubuntu 版本可能使用 ssh.service 或 ssh.socket。后文会检测当前激活方式,不要假定服务名是 sshd.service。
2. 在客户端生成密钥
默认推荐 Ed25519。-f 的含义是指定私钥输出文件,公钥会写入同名的 .pub 文件;它不是“友好输出”选项。生成时应设置一个强 passphrase,并使用 ssh-agent 管理。
Windows PowerShell:
New-Item -ItemType Directory -Force "$env:USERPROFILE\.ssh" | Out-Null
ssh-keygen -t ed25519 -a 64 -f "$env:USERPROFILE\.ssh\id_ed25519_server_admin"Windows CMD:
if not exist "%USERPROFILE%\.ssh" mkdir "%USERPROFILE%\.ssh"
ssh-keygen -t ed25519 -a 64 -f "%USERPROFILE%\.ssh\id_ed25519_server_admin"Linux/macOS:
install -d -m 700 ~/.ssh
ssh-keygen -t ed25519 -a 64 -f ~/.ssh/id_ed25519_server_adminEd25519 不适用于部分旧客户端或要求特定 FIPS 策略的环境;确认存在兼容性约束时,使用 RSA 3072 位以上作为回退,而不是使用短 RSA key:
ssh-keygen -t rsa -b 3072 -a 64 -f ~/.ssh/id_rsa_server_admin私钥只能留在客户端,不得上传到服务器、聊天、网盘或仓库。应另外做受保护的离线备份。
3. 安装并验证公钥
Linux/macOS 客户端可在服务器仍允许密码登录时执行:
CURRENT_SSH_PORT=22
ssh-copy-id -p "$CURRENT_SSH_PORT" \
-i ~/.ssh/id_ed25519_server_admin.pub adminuser@203.0.113.10Windows 或没有 ssh-copy-id 时,先登录到目标普通用户,在目标用户自己的 shell 中执行:
install -d -m 700 ~/.ssh
touch ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
nano ~/.ssh/authorized_keys把客户端 .pub 文件的完整单行内容粘贴进去。不要以 root 身份把文件写进错误的 HOME;检查 owner 和权限:
stat -c '%U:%G %a %n' ~/.ssh ~/.ssh/authorized_keys两条路径的 owner 都必须是目标登录用户;推荐权限分别为 .ssh 的 700 和 authorized_keys 的 600。如果 owner 不符,应返回有 sudo 权限的管理 shell 定向修复目标用户路径,不能只反复执行 chmod。
在禁用密码登录前,先从第二个终端验证密钥。Linux/macOS 示例:
ssh -p 22 \
-o BatchMode=yes \
-o IdentitiesOnly=yes \
-o PreferredAuthentications=publickey \
-i ~/.ssh/id_ed25519_server_admin \
adminuser@203.0.113.10其中 22 必须替换为仍在使用的 CURRENT_SSH_PORT。这些 options 禁止回退到密码或其他 identity,确保验收的是刚部署的指定 key;若私钥有 passphrase,应先把它加入 ssh-agent。只有这次全新连接和该连接中的 sudo -v 都成功,才能继续 hardening。
PowerShell 应使用对应的 Windows 私钥路径:
ssh -p 22 `
-o BatchMode=yes `
-o IdentitiesOnly=yes `
-o PreferredAuthentications=publickey `
-i "$env:USERPROFILE\.ssh\id_ed25519_server_admin" `
adminuser@203.0.113.104. 分阶段应用 hardening 与新端口
更换端口主要减少扫描噪声,不能替代密钥认证、更新和访问控制。先在云防火墙和 UFW 中开放新端口,同时保留当前端口:
CURRENT_SSH_PORT=22
SSH_PORT=22222
sudo ufw allow "${SSH_PORT}/tcp" comment 'SSH new port'
sudo ufw status numbered在写入任何 sshd 配置前先识别 activation mode:
systemctl is-active ssh.service
systemctl is-active ssh.socket
test -x /usr/lib/systemd/system-generators/sshd-socket-generator \
&& echo 'Ubuntu sshd socket generator: present' \
|| echo 'Ubuntu sshd socket generator: absent'若 ssh.socket active 但 generator 不存在,在这里停止,不要先写入 hardening drop-in:此时监听端口由 socket unit 而不是 sshd_config 单独决定。Debian 上应选择恢复发行版默认的 ssh.service activation,或审阅并显式维护 ssh.socket override;切换 activation mode 前必须保留当前会话与控制台恢复入口。
Ubuntu 的 /etc/ssh/sshd_config 通常在文件顶部加载 /etc/ssh/sshd_config.d/*.conf,而多数单值选项采用“先读取的值生效”。因此使用靠前的本地 drop-in,并在应用后检查 effective config:
sudoedit /etc/ssh/sshd_config.d/00-local-hardening.conf第一阶段同时监听当前端口与新端口。下面的 22 和 22222 必须分别替换为 CURRENT_SSH_PORT 与 SSH_PORT 的真实数值:
Port 22
Port 22222
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no写入后先校验配置:
sudo sshd -t
sudo sshd -T | grep -E '^(port|permitrootlogin|pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication) '输出必须包含计划中的两个端口,以及 permitrootlogin no、pubkeyauthentication yes、passwordauthentication no 和 kbdinteractiveauthentication no。任一 effective value 不符,都应停止并检查更早读取的 drop-in,不能 restart/reload。
若
ssh.socket不是 active、但ssh.service确实 active,由传统 service 监听;Debian 12/13 与 Ubuntu 22.04 通常属于此路径:bashsudo systemctl reload ssh.service若
ssh.socketactive 且 generator 存在,按 Ubuntu 24.04 的 socket-activation 路径应用端口配置:bashsudo systemctl daemon-reload sudo systemctl restart ssh.socket
应用后从服务器侧确认真实监听者和端口;ssh.socket 模式下进程名可能显示为 systemd,而不是 sshd:
sudo ss -ltnp必须看到当前端口和新端口都处于 LISTEN。若两个 unit 都不是 active,或监听结果与计划不符,停止操作并从旧会话恢复配置。
保持旧会话不动,在第二个终端执行真实验收。下面是 Linux/macOS 示例;Windows 应使用前文对应的私钥路径:
ssh -p 22222 \
-o BatchMode=yes \
-o IdentitiesOnly=yes \
-o PreferredAuthentications=publickey \
-i ~/.ssh/id_ed25519_server_admin \
adminuser@203.0.113.10确认普通用户、sudo、密钥和新端口全部正常后,才可从 00-local-hardening.conf 删除旧端口对应的 Port 行,重新运行 sshd -t、按相同 activation mode reload/restart,并完成第二次外部登录测试。最后删除 UFW 与云防火墙中的旧端口规则;下面的 22 必须替换为真实的 CURRENT_SSH_PORT:
sudo ufw delete allow 22/tcp
sudo ufw status verbose若新连接失败,不要退出旧会话;恢复 drop-in、重新运行 sshd -t,并用旧会话或云控制台回滚。
5. Ubuntu 24.04 的 ssh.socket 说明
Ubuntu 从 22.10 起默认采用 OpenSSH socket activation;Ubuntu 24.04 新增 generator,会从 sshd_config/sshd_config.d 动态生成监听配置。修改 Port、ListenAddress 或 AddressFamily 后,需要 systemctl daemon-reload 并重启 ssh.socket。这套 generator 行为是 Ubuntu 的发行版实现,不能直接类推到 Debian。
早期 Noble 曾有 IPv4 listener 问题,但已在 openssh 1:9.6p1-3ubuntu13.13 修复。不要再用 systemctl edit ssh.socket 手写 ListenStream= workaround;历史 override 反而可能在升级后导致 SSH 只监听 IPv6 或完全失联。
升级过来的主机应先检查已安装版本并审阅当前 unit:
dpkg-query -W -f='${Version}\n' openssh-server
sudo systemctl cat ssh.socket只有确认主机确实是 Ubuntu Noble、ssh.socket 与 generator 正在使用、openssh-server 已不低于 1:9.6p1-3ubuntu13.13,并且 /etc/systemd/system/ssh.socket.d/override.conf 仅包含旧的 ListenStream=22/ListenStream=自定义端口 workaround 时,才可让 generator 接管。先通过云控制台保留恢复入口,再运行下面的 fail-closed migration:
(
set -euo pipefail
. /etc/os-release
[ "${ID:-}" = ubuntu ] && [ "${VERSION_CODENAME:-}" = noble ] || {
echo 'this migration is only for Ubuntu 24.04 Noble' >&2
exit 1
}
generator=/usr/lib/systemd/system-generators/sshd-socket-generator
override=/etc/systemd/system/ssh.socket.d/override.conf
generated=/run/systemd/generator/ssh.socket.d/addresses.conf
installed_version=$(dpkg-query -W -f='${Version}' openssh-server)
[ -x "$generator" ] || {
echo 'sshd socket generator is absent' >&2
exit 1
}
systemctl is-active --quiet ssh.socket || {
echo 'ssh.socket is not active' >&2
exit 1
}
dpkg --compare-versions "$installed_version" ge '1:9.6p1-3ubuntu13.13' || {
echo 'update openssh-server before removing the workaround' >&2
exit 1
}
[ -f "$override" ] && [ ! -L "$override" ] || {
echo 'the expected regular override file is absent' >&2
exit 1
}
invalid_lines=$(sudo grep -Ev \
'^[[:space:]]*(#.*|\[Socket\]|ListenStream=([0-9]+)?|)[[:space:]]*$' \
"$override" || true)
[ -z "$invalid_lines" ] &&
sudo grep -Eq '^[[:space:]]*ListenStream=[[:space:]]*$' "$override" &&
sudo grep -Eq '^[[:space:]]*ListenStream=[0-9]+[[:space:]]*$' "$override" || {
echo 'override contains more than the legacy port-only workaround' >&2
exit 1
}
sudo sshd -t
backup="${override}.bak.$(date +%Y%m%d-%H%M%S)"
sudo cp -a "$override" "$backup"
sudo cmp -s "$override" "$backup" || {
echo 'backup verification failed; override was not removed' >&2
exit 1
}
restore_override() {
sudo cp -a "$backup" "$override"
sudo systemctl daemon-reload
}
sudo rm "$override"
if ! sudo systemctl daemon-reload; then
restore_override
exit 1
fi
ports=$(sudo sshd -T | sed -n 's/^port //p')
if [ -z "$ports" ] || [ ! -f "$generated" ]; then
echo 'generator did not produce an address drop-in; restoring override' >&2
restore_override
exit 1
fi
for port in $ports; do
sudo grep -Eq "^ListenStream=.*:${port}$" "$generated" || {
echo "generated socket config is missing port ${port}; restoring override" >&2
restore_override
exit 1
}
done
if ! sudo systemctl restart ssh.socket; then
echo 'socket restart failed; restoring override' >&2
restore_override
sudo systemctl restart ssh.socket || true
exit 1
fi
sudo systemctl status --no-pager ssh.socket
sudo ss -ltnp
)脚本会验证发行版、activation mode、版本、目标文件类型与内容,并用 cmp 检查备份;daemon-reload 后还会确认 generator 为 sshd -T 中的每个端口生成了 ListenStream,通过后才重启 socket。若 override 还有其他有意配置,脚本会拒绝删除;应逐项迁移到 sshd_config.d 并完成双会话验收。
六、可选的交互式 shell 工具
服务器不需要为了美化 shell 安装框架。若确实使用 Oh My Zsh,先安装前置工具,再下载和审阅脚本,避免把 mutable remote script 直接通过管道交给 shell:
sudo apt install zsh git curl
installer=$(mktemp)
curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh \
-o "$installer"
less "$installer"
sh "$installer"
rm -f "$installer"需要改默认 shell 时单独执行,并重新登录验证:
chsh -s "$(command -v zsh)"Tmux 可直接从发行版仓库安装:
sudo apt install tmux
tmux new -s worknvbn/thefuck 的 PyPI 最新发布仍停留在 2022 年,仓库维护也较不活跃,不再作为新服务器基线推荐。不要让自动纠错工具未经确认执行高权限命令。
七、Docker 配置(可选)
1. 从官方 APT 仓库安装 Docker Engine
get.docker.com convenience script 只适合测试/开发,不适合作为需要版本可控、可审计和可重复执行的生产服务器基线。Debian 与 Ubuntu 的仓库 URI 不同,以下流程从 /etc/os-release 严格识别二者,并自动使用当前架构。
先检查可能冲突的软件包。新服务器可按 Docker 官方列表移除;已有容器运行环境的服务器必须先审计数据和迁移方案,不能直接执行删除:
dpkg-query -W -f='${binary:Package}\n' \
docker.io docker-doc docker-buildx docker-compose docker-compose-v2 \
podman-docker containerd runc 2>/dev/null || true确认这些包无需保留后再执行:
for pkg in docker.io docker-doc docker-buildx docker-compose docker-compose-v2 \
podman-docker containerd runc; do
sudo apt-get remove "$pkg"
done配置 scoped keyring 与 deb822 source:
(
set -eu
. /etc/os-release
case "$ID" in
ubuntu)
DOCKER_OS=ubuntu
DOCKER_CODENAME=${UBUNTU_CODENAME:-${VERSION_CODENAME:-}}
;;
debian)
DOCKER_OS=debian
DOCKER_CODENAME=${VERSION_CODENAME:-}
;;
*)
echo "Unsupported distribution: $ID" >&2
exit 1
;;
esac
DOCKER_ARCH=$(dpkg --print-architecture)
: "${DOCKER_CODENAME:?distribution codename is missing}"
sudo install -m 0755 -d /etc/apt/keyrings
tmp_key=$(mktemp)
trap 'rm -f "$tmp_key"' EXIT
curl -fsSL "https://download.docker.com/linux/${DOCKER_OS}/gpg" -o "$tmp_key"
sudo install -m 0644 "$tmp_key" /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/${DOCKER_OS}
Suites: ${DOCKER_CODENAME}
Components: stable
Architectures: ${DOCKER_ARCH}
Signed-By: /etc/apt/keyrings/docker.asc
EOF
)安装当前 Compose V2 与 Buildx plugin:
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
sudo systemctl is-active docker
sudo docker run --rm hello-world
sudo docker compose version不要安装已退役的 Compose V1 docker-compose 包;当前命令是 docker compose。
2. Docker 权限模型
默认使用 sudo docker ...。docker 用户组不是普通的“免 sudo”便利组:其成员可以挂载主机根目录、启动特权容器,权限实质上接近 root。
只有完全受信任的管理员才可加入该组:
sudo usermod -aG docker "$USER"重新登录后再验证。多用户或需要真正降权的环境应使用 Rootless Docker,而不是依赖 docker 组。
3. Docker、UFW 与公网暴露
Docker published ports 可能绕过 UFW。敏感服务优先只绑定 loopback:
services:
app:
image: example/app:latest
ports:
- "127.0.0.1:8080:8080"需要公网发布时,应同时验收云防火墙、监听地址、Docker DOCKER-USER 链和真实外部连通性。不要仅依据 ufw status 下结论。
4. Docker 日志轮转
Docker 推荐 local logging driver,它默认支持轮转。若要全局限制为 3 份、每份 10 MiB,先备份已有配置,并使用 JSON-aware 方式合并;不要覆盖或直接拼接已有 /etc/docker/daemon.json。
若文件尚不存在,可以创建:
sudo install -d -m 0755 /etc/docker
sudoedit /etc/docker/daemon.json完整的新文件示例:
{
"log-driver": "local",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}已有配置时只合并 log-driver 和 log-opts 键。先验证 JSON 和 dockerd 配置,再在维护窗口重启:
sudo jq empty /etc/docker/daemon.json
sudo dockerd --validate --config-file=/etc/docker/daemon.json
sudo systemctl restart docker
sudo docker info --format '{{.LoggingDriver}}'仅修改 daemon.json 不需要 systemctl daemon-reload。重启 Docker 可能中断现有容器;新的默认 logging 配置只对重建后的容器生效。
单个 Compose service 的配置必须放在 services.<name>.logging 下,并用 Compose 自身解析验证:
services:
app:
image: example/app:latest
logging:
driver: local
options:
max-size: "100m"
max-file: "3"sudo docker compose config >/dev/null八、配置 swap(按需)
swap 大小取决于 workload、内存压力、磁盘空间和是否需要休眠;“内存的 2–3 倍”不是服务器通用规则。不要先运行 swapoff -a:它会关闭所有 swap,在低内存 VPS 上可能直接触发 OOM。
先检查现状、根分区空间和文件系统:
free -h
swapon --show
df -h /
findmnt -no FSTYPE -T /以下示例新增 2 GiB /swapfile,不会覆盖同名文件。对 ext4/XFS 等普通文件系统:
sudo dd if=/dev/zero of=/swapfile bs=1M count=2048 \
status=progress oflag=excl
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfileBtrfs swapfile 需要 NODATACOW、无洞、single-device/single-profile 等额外约束,并会影响 snapshot、balance 与 scrub。btrfs-progs 6.1+ 可用专用命令;Ubuntu 22.04 自带的旧版本通常没有该命令:
if sudo test -e /swapfile; then
echo '/swapfile already exists; stop and inspect it' >&2
else
sudo btrfs filesystem mkswapfile --size 2G /swapfile
sudo swapon /swapfile
fi旧版 btrfs-progs 应按 Btrfs 官方 swapfile 流程 显式创建 NODATACOW 文件,且仅在确认 /swapfile 不存在时执行:
if sudo test -e /swapfile; then
echo '/swapfile already exists; stop and inspect it' >&2
else
sudo truncate -s 0 /swapfile
sudo chattr +C /swapfile
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
fi确认新 swap 已启用后,备份并幂等地追加 fstab。绝不能“替换最后一行”:
sudo cp --archive /etc/fstab "/etc/fstab.bak.$(date +%Y%m%d-%H%M%S)"
if grep -qE '^[[:space:]]*/swapfile[[:space:]]' /etc/fstab; then
grep -E '^[[:space:]]*/swapfile[[:space:]]' /etc/fstab
echo 'An existing /swapfile entry was preserved; inspect it before editing.'
else
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
fi
sudo findmnt --verify --verbose
sudo systemctl daemon-reload
swapon --show若要替换旧 swap,应在新 swap 和 fstab 均验证成功后,只对明确的旧路径执行 sudo swapoff /旧路径,再精确删除对应 fstab 项;不要使用 swapoff -a。
九、日志保留:journald 与 logrotate
先识别当前日志栈。Debian 12+ 默认可能只有 journald,不一定存在 /var/log/syslog;Ubuntu 或安装了 rsyslog 的系统通常还会使用文本日志。
systemctl is-active systemd-journald
dpkg-query -W rsyslog 2>/dev/null || true
systemctl is-active rsyslog 2>/dev/null || true
journalctl --disk-usage1. journald-only 或统一限制 journal
创建 drop-in,而不是直接覆盖主配置:
sudo install -d -m 0755 /etc/systemd/journald.conf.d
sudoedit /etc/systemd/journald.conf.d/limits.conf示例策略:最多使用 1 GiB,同时为文件系统保留 1 GiB 空间,日志最长保留 30 天。systemd 会取各约束中最先触发者:
[Journal]
SystemMaxUse=1G
SystemKeepFree=1G
RuntimeMaxUse=256M
MaxRetentionSec=30daySystemMaxUse/SystemKeepFree 约束持久化目录 /var/log/journal,RuntimeMaxUse 约束 volatile 目录 /run/log/journal;最终行为仍取决于 Storage= 和可用空间。
检查有效配置后再重启:
systemd-analyze cat-config systemd/journald.conf
sudo systemctl restart systemd-journald
journalctl --disk-usage2. rsyslog 文本日志
rsyslog 包已经提供 /etc/logrotate.d/rsyslog。应调整这个现有文件,不能另建一个包含相同日志路径的 stanza,否则会出现 duplicate log entry 并影响轮转。
sudo apt install logrotate
sudoedit /etc/logrotate.d/rsyslog在现有 stanza 中按需求调整以下 directives,并保留发行版提供的日志路径和 postrotate 命令:
rotate 3
maxsize 100M
missingok
notifempty
compress
delaycompressmaxsize 只在 logrotate 被调度运行时检查,不是实时硬上限。高吞吐日志应提高调度频率,或使用应用/日志驱动自身的轮转功能。Debian/Ubuntu 的不同版本可能由 logrotate.timer 或 /etc/cron.daily/logrotate 驱动;先检查实际机制,不要为了 logrotate 盲目安装第二套调度器:
sudo logrotate --debug /etc/logrotate.conf
systemctl list-timers --all logrotate.timer
if [ -x /etc/cron.daily/logrotate ]; then
ls -l /etc/cron.daily/logrotate
fi十、Fail2ban 保护 SSH(可选)
Fail2ban 只能降低暴力尝试速率,不能弥补弱密码或不安全的 SSH 配置。本文仅覆盖 Debian/Ubuntu;RHEL/EPEL 的仓库启用方式应查对应 major version 的官方文档。
sudo apt install fail2ban
sudo install -d -m 0755 /etc/fail2ban/jail.d
SSH_PORT=22222创建本地 override,不要修改包自带 .conf:
sudo tee /etc/fail2ban/jail.d/sshd.local >/dev/null <<EOF
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
[sshd]
enabled = true
backend = systemd
port = ${SSH_PORT}
maxretry = 5
findtime = 10m
bantime = 1h
EOF如果有固定管理出口,可把 192.0.2.10/32 之类的真实可信 CIDR追加到 ignoreip;不要把大网段或动态地址误加入白名单。port 必须与 SSH 最终监听端口一致。
应用前先测试完整配置:
sudo fail2ban-client -t
sudo systemctl enable fail2ban
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd部署后应从外部确认合法 SSH 登录仍正常,并检查 Fail2ban 使用的 firewall action 是否与主机 nftables/iptables 环境一致。
十一、服务器管理面板(高风险、可选)
1Panel、宝塔等面板会新增高权限 Web 管理入口、监听端口和第三方组件,显著扩大 attack surface。能用 SSH、systemd 和声明式配置完成维护时,不应把面板视为“新服务器必装”。
若确实选择 1Panel,只使用官方安装文档。不要把 mutable installer 直接通过 curl | bash 执行;先下载并审阅:
installer=$(mktemp)
curl -fsSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh \
-o "$installer"
less "$installer"
sudo bash "$installer"
rm -f "$installer"安装后至少做到:
- 用云防火墙与 UFW 将面板端口限制到真实的
ADMIN_CIDR,不要直接暴露给全网; - 使用独立强密码和产品支持的 MFA,禁用默认账户/默认端口;
- 及时安装面板及其管理组件的安全更新;
- 对配置、数据库和应用数据做可恢复、异机备份;
- 定期从公网重新审计实际暴露端口。
限制面板端口的 UFW 示例:
sudo ufw allow from 192.0.2.10/32 to any port 12345 proto tcp \
comment '1Panel admin only'其中 CIDR 和端口必须替换成真实值,并同步配置云防火墙。
十二、自动安全更新
unattended-upgrades 只会处理配置允许的 APT origins,不能保证“系统一直安全”,也不会自动安全地更新所有第三方仓库。Docker CE、面板、手工二进制和应用依赖需要各自受控的更新流程。
sudo apt install unattended-upgrades
sudoedit /etc/apt/apt.conf.d/20auto-upgrades确保包含:
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";审阅 /etc/apt/apt.conf.d/50unattended-upgrades 中的 Allowed-Origins/Origins-Pattern,默认只允许发行版官方安全来源。不要为了省事笼统允许所有第三方仓库。
应用前 dry-run,并检查 timer 与日志:
sudo unattended-upgrade --dry-run --debug
systemctl list-timers --all apt-daily.timer apt-daily-upgrade.timer
sudo journalctl -u apt-daily-upgrade.service --since '7 days ago'Ubuntu 24.04 的 needrestart 可能在升级后自动重启受影响服务;整机自动重启默认通常关闭。生产主机应明确维护窗口、通知、服务重启策略,并监控:
if [ -f /run/reboot-required ]; then
cat /run/reboot-required
cat /run/reboot-required.pkgs 2>/dev/null || true
fi自动更新不是备份、监控、最小权限和漏洞响应的替代品。
官方资料
- Ubuntu Server:Package management
- Ubuntu Server:Firewall with UFW
- Ubuntu Server:OpenSSH server
- Ubuntu Launchpad:OpenSSH IPv4 socket 修复
- Ubuntu Launchpad:旧 ssh.socket override 导致失联
- Debian Wiki:sudo
- Debian Wiki:nftables
- Debian manpages:sshd_config(5)
- Microsoft Learn:OpenSSH key management
- Docker:Ubuntu installation
- Docker:Debian installation
- Docker:Linux post-installation
- Docker:Firewall limitations
- Docker:Logging drivers
- Debian manpages:swapon(8)
- Debian manpages:fstab(5)
- Debian manpages:logrotate(8)
- Ubuntu Server:Automatic updates