ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Debian 12服务器初始化配置:SSH加固、防火墙与时间同步实战

Debian 12服务器初始化配置:SSH加固、防火墙与时间同步实战 一台刚开通的 Debian 12 服务器默认状态其实是半成品root 能直接登录、SSH 密码认证开着、时区大概率还停在 UTC、时间同步服务不一定在跑、防火墙一条规则都没有、日志里很快会塞满各种扫描记录。我第一次给生产环境做初始化的时候图省事只改了个密码就上了业务结果三天后翻/var/log/auth.log发现几万条爆破尝试那一刻才真正理解服务器初始化配置这件事不是走流程而是把一台公网上的裸机变成能放心交钥匙的状态。这篇内容讲的是一次完整的 Debian 12 服务器初始化配置流程从拿到 IP 开始到最终形成一份可以反复复用的初始化脚本。涉及账号体系、SSH 加固、时间同步、软件源、基础工具链、防火墙、内核参数、磁盘与 Swap 规划这些环节。不管你是刚租了一台云服务器想搭个人服务还是手里管着几台小规模集群的运维这套流程都能直接用也可以按需裁剪。我尽量把每一步为什么这么做讲清楚因为参数抄错比不配更麻烦。1. 初始化到底在解决什么问题整体思路拆解1.1 从能连上到能用之间差的那些东西很多人对初始化的理解停留在装几个软件但实际上真正需要解决的是四类问题。第一类是可达性也就是我能稳定、安全地连上这台机器而不是每次登录都提心吊胆怕被爆破第二类是一致性即这台机器上的时区、时间、语言环境、文件编码符合预期不会出现日志时间对不上、定时任务跑错点的情况第三类是可观测与可恢复日志有人管、磁盘有空间、系统崩了能查到原因第四类是可复制性同样的事情再来一台机器我能五分钟做完而不是回忆半小时。这四类问题的优先级是有先后的。可达性永远排第一因为如果 SSH 配置改崩了你连不进去后面所有工作都无从谈起。这也是为什么我强烈建议在动 SSH 配置之前先开一个备用会话保持登录状态不退出等新的会话验证通过再关掉旧的。这个习惯救过我至少三次。1.2 我的四层顺序模型把上面这些拆开我习惯按下面这个顺序推进顺序错了会给自己找麻烦层级处理内容为什么排这个位置第一层账号、sudo、SSH 密钥与 sshd 配置先锁住入口后续所有操作都在安全通道里做第二层主机名、时区、时间同步、DNS时间不对会导致日志错乱、证书校验失败、集群心跳异常第三层软件源、系统更新、基础工具链有了工具才能排查问题这是后续所有操作的弹药第四层防火墙、内核参数、磁盘与 Swap、监控前面稳了再收紧策略避免把自己关在门外举个具体的例子说明为什么时间同步要排在软件安装之前Debian 默认时区是 UTC如果你先装了数据库、后面才改时区某些数据库的日志文件里会混着两种时间戳排查问题时能把人逼疯。而如果先改时区再装从一开始就是一致的。1.3 哪些步骤可以省哪些绝对不能省不是所有场景都需要全套流程。个人玩具机、内网测试机、生产业务机标准完全不同。我的判断标准是这样的只要这台机器有公网 IPSSH 加固、防火墙、fail2ban 这三样一个都不能少因为公网上的扫描是全自动的从开通到第一波爆破通常不超过半小时。而如果只是内网跳板或者本地虚拟机防火墙可以放宽时间同步用默认的 systemd-timesyncd 也够用。至于磁盘规划、内核参数调优这类我建议个人用户先跳过等真正遇到性能瓶颈再回头处理提前优化往往是在优化一个不存在的瓶颈。下面几节我会重点讲公网机器必须做的部分内网场景可以按标注跳过。一个我自己踩过的坑第一次配置云服务器时我在没确认云厂商安全组规则的情况下先开了 ufw 并且只放行了 22 端口结果把 HTTPS 流量全挡了业务健康检查直接失败。本机防火墙和云平台安全组是两层要一起看。2. 账号体系与 SSH 加固把门先锁好2.1 root 直登还是普通用户加 sudoDebian 12 安装完成后默认是可以 root 登录的云镜像通常也保留了 PermitRootLogin 的默认值。我的做法是保留 root 账号但禁止 SSH 登录日常操作全部走普通用户 sudo。这么做有两个实际好处一是审计日志里能看出是谁执行的而不是一片 root二是降低误操作风险因为删根目录这种命令普通用户会被 sudo 拦一道。创建运维账号的命令很直白adduser deploy usermod -aG sudo deployadduser是 Debian 系的交互式脚本会顺带创建家目录、提示设置密码比useradd省心。加进sudo组之后Debian 12 默认的/etc/sudoers里已经有%sudo ALL(ALL:ALL) ALL这一行不需要额外改。如果你想让某个账号免密 sudo可以放到/etc/sudoers.d/下单独一个文件echo deploy ALL(ALL) NOPASSWD:ALL /etc/sudoers.d/90-deploy chmod 440 /etc/sudoers.d/90-deploy注意/etc/sudoers和/etc/sudoers.d/里的文件权限必须是 0440否则 sudo 会直接拒绝加载。这个报错信息很隐晦我第一次遇到时查了半小时。2.2 SSH 密钥登录的完整落地过程密钥登录的流程分三步本地生成、公钥上传、服务端配置。本地生成建议用 ed25519比 RSA 短、比 RSA 快OpenSSH 9.x 也早已默认支持ssh-keygen -t ed25519 -C deploymy-server -f ~/.ssh/id_ed25519_debian12上传公钥最省事的方式是ssh-copy-idssh-copy-id -i ~/.ssh/id_ed25519_debian12.pub deploy1.2.3.4这一步执行完会往目标机的~/.ssh/authorized_keys里追加内容并且自动处理目录权限。如果你手动上传务必确认权限chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys chown -R deploy:deploy ~/.ssh权限不对是密钥登录失败最常见的原因sshd 对组可写其他人可读非常敏感一旦不符合要求会直接忽略这个 key而且不会告诉你原因。2.3 sshd_config 逐条说明与验证方法Debian 12 的/etc/ssh/sshd_config里有一行Include /etc/ssh/sshd_config.d/*.conf我习惯把自定义项放到独立文件里方便日后对比和回滚# /etc/ssh/sshd_config.d/99-custom.conf PermitRootLogin no PasswordAuthentication no KbdInteractiveAuthentication no PubkeyAuthentication yes MaxAuthTries 3 LoginGraceTime 20 ClientAliveInterval 300 ClientAliveCountMax 2逐条说下取舍。PermitRootLogin no是切断最直接的攻击路径PasswordAuthentication no关掉密码认证这一条要确认密钥能正常登录之后再改KbdInteractiveAuthentication no是配合上一条因为 PAM 可能绕过密码认证MaxAuthTries 3限制单次连接的认证次数LoginGraceTime 20把默认的 120 秒缩短减少闲置连接占用ClientAliveInterval和CountMax控制空闲会话自动断开防止忘关的终端一直挂着。改完之后不要急着重启 sshd先做语法检查sshd -t更关键的是确认最终生效值因为 sshd 的规则是第一个出现的同名指令生效而 Include 的位置会影响到谁先谁后sshd -T | grep -E permitrootlogin|passwordauthentication|pubkeyauthentication确认输出符合预期后再重载systemctl reload ssh然后再开一个新终端验证登录成功之后才关闭当前会话。这个新开验证的动作我强烈建议固化成肌肉记忆。3. 网络、主机名与时间同步让机器对得上表3.1 主机名与 hosts 的连带影响主机名看着是小事但很多分布式组件默认用主机名做节点标识一旦后面改了名字注册到集群里的旧记录会变成僵尸。所以主机名要在装业务之前一次定好hostnamectl set-hostname web-prod-01Debian 12 使用 systemdhostnamectl会自动更新/etc/hostname。同时要检查/etc/hosts至少要有一行把主机名映射到本机地址127.0.1.1 web-prod-01这个127.0.1.1是 Debian 系的传统作用是让本机解析自己的主机名时不需要依赖 DNS。如果没有这一行某些程序启动时会卡在主机名解析上几秒钟这种延迟很难定位。验证方式hostname -f能正确返回完整主机名就对了。3.2 静态 IP 与 DNS 的配置方式选择Debian 12 默认用/etc/network/interfacesifupdown但云镜像通常已经装好了 cloud-init由它接管网络配置。这里有个容易踩的坑手动改/etc/network/interfaces可能被 cloud-init 覆盖。判断方法cloud-init status ls /etc/cloud/cloud.cfg.d/如果是云环境我一般不动底层配置而是通过云控制台改安全组和弹性 IP。如果是自建机房或者虚拟机就老老实实写 interfaces 文件auto ens3 iface ens3 inet static address 192.168.1.10/24 gateway 192.168.1.1 dns-nameservers 223.5.5.5 119.29.29.29DNS 这块Debian 12 默认没有装 systemd-resolved/etc/resolv.conf是实际生效的文件。但如果你装了 cloud-init 或者 NetworkManager它们可能会接管。检查方式ls -l /etc/resolv.conf如果它指向/run/systemd/resolve/...说明有东西在管如果是普通文件就可以直接编辑。3.3 时间同步chrony 的配置与选择理由Debian 12 默认带的是systemd-timesyncd它能满足基本需求但功能比较薄。我一般换成chrony原因有三个一是它支持更精细的源选择和统计二是默认源连不上时能更快切换到备用源三是它能作为时间源对外服务方便内网其他机器同步。apt install -y chrony systemctl enable --now chrony安装 chrony 时会自动与 timesyncd 冲突从而把它停掉。配置国内可用的时间服务器# /etc/chrony/chrony.conf 追加 server ntp.aliyun.com iburst server ntp1.aliyun.com iburst server cn.pool.ntp.org iburst server time1.cloud.tencent.com iburstiburst的含义是首次同步时快速发 4 个包把同步时间从几十秒缩短到几秒。配置完重启并验证systemctl restart chrony chronyc sources -v chronyc trackingsources -v里每个源前面有个符号^*表示当前选中的同步源^表示备用候选。tracking里的System time偏差值如果在毫秒级就正常。3.4 时区设置的连带影响时区要和时间同步一起处理。Debian 12 改时区timedatectl set-timezone Asia/Shanghai timedatectl status输出的三行关键信息要都确认Local time是本地时间、Universal time是 UTC、System clock synchronized: yes、NTP service: active。一个实际教训我曾经在一台机器上先改了时区但没管 NTP结果容器里跑的应用日志时间和宿主机差了 8 小时定位了大半天。改时区之后一定要跑一遍timedatectl确认同步状态不要只看时间对不对。4. 软件源与基础工具链把弹药备齐4.1 sources.list 的结构与 Debian 12 的变化Debian 12 引入了新的 deb822 格式文件放在/etc/apt/sources.list.d/下扩展名是.sources。相比传统的一行式写法它的可读性和可维护性更好Types: deb URIs: http://deb.debian.org/debian Suites: bookworm bookworm-updates Components: main contrib non-free non-free-firmware Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg Types: deb URIs: http://security.debian.org/debian-security Suites: bookworm-security Components: main contrib non-free non-free-firmware Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg这里要注意non-free-firmware这个组件它是 Debian 12 新增的固件包从non-free里独立出来了。如果你用的是默认的云镜像通常已经配好了。上游地址如果访问速度不理想可以按自己所在网络的实际情况换成响应更快的地址改完用apt update验证连通性即可。4.2 系统更新策略全自动还是手动apt update apt full-upgrade -y是标准动作但是否让系统自动装安全更新是个需要思考的决策策略适合场景风险全手动生产核心业务容易漏掉安全补丁只自动装安全更新大多数生产环境极小概率引入兼容问题全自动升级个人测试机内核升级后可能需重启我通常选第二种装unattended-upgrades并只启用安全源apt install -y unattended-upgrades dpkg-reconfigure --prioritylow unattended-upgrades配置文件在/etc/apt/apt.conf.d/50unattended-upgrades默认已经只勾选了安全更新。如果你的业务对重启敏感记得在同一目录下的20auto-upgrades里确认自动清理旧内核的行为避免/boot被塞满。4.3 必装工具清单与理由工具不在多在于出事的时候手边有家伙。我的基础清单是这些apt install -y curl wget vim htop git tmux unzip rsync jq \ mtr-tiny dnsutils lsof iotop sysstat ca-certificates每一项都有明确用途curl和wget做连通性测试vim是因为 vi 在 Debian 上其实是 vim-tiny很多功能缺失tmux是长任务保命工具SSH 断了任务还在jq处理 JSON 输出mtr-tiny是 traceroute 加 ping 的合体排查网络问题比单独用两个命令高效dnsutils提供diglsof查端口占用iotop和sysstat用于磁盘和系统性能回溯尤其是sysstat会留下历史数据事后排查非常有用。提示Debian 12 默认不装net-tools也就是没有ifconfig和netstat。建议直接用ip addr和ss -tulnp替代这两个命令的信息量更大。如果实在习惯老命令装net-tools也无妨只是要注意它已经多年不更新了。5. 防火墙、内核参数与基础加固5.1 ufw 与 nftables 的选择逻辑Debian 12 底层是 nftablesufw只是它的一个易用前端。个人和小团队场景我推荐 ufw因为规则可读性好、不容易写错如果是复杂规则集多网段、NAT、限速直接写 nftables 更合适。基础规则这样写apt install -y ufw ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp ufw allow 80/tcp ufw allow 443/tcp ufw enable ufw status verbose顺序很重要先把要放行的端口写进去最后再 enable。如果先 enable 再想加规则而你恰好只放行了 22那自己还能连上但如果你连 22 都没放行就直接失联了。云环境下还要注意云平台安全组和本机 ufw 是两层过滤两边都要放行才能通。5.2 fail2ban 的正确配置位置SSH 加固之后爆破依然会来只是打不进来。fail2ban 的作用是把反复尝试的 IP 封掉减少日志噪音和连接资源占用。apt install -y fail2banDebian 12 的 fail2ban 有个变化默认配置里没有启用任何 jail而且日志后端需要用 systemd。所以自定义配置要写清楚# /etc/fail2ban/jail.d/sshd.local [sshd] enabled true backend systemd maxretry 5 findtime 10m bantime 1h重启后验证systemctl restart fail2ban fail2ban-client status sshd输出的Banned IP list如果已经有内容说明它开始工作了。bantime我通常设 1 小时太长容易误伤自己的动态 IP太短又没威慑力。5.3 内核安全参数与生效方式/etc/sysctl.d/下放自定义配置避免污染/etc/sysctl.conf# /etc/sysctl.d/99-hardening.conf net.ipv4.tcp_syncookies 1 net.ipv4.conf.all.rp_filter 1 net.ipv4.conf.all.accept_redirects 0 net.ipv4.conf.all.accept_source_route 0 net.ipv4.icmp_echo_ignore_broadcasts 1 kernel.dmesg_restrict 1 fs.protected_hardlinks 1 fs.protected_symlinks 1这几个参数的意义tcp_syncookies缓解 SYN 洪泛rp_filter做反向路径校验减少伪造源地址的包关闭 ICMP 重定向和源路由防止路由被篡改icmp_echo_ignore_broadcasts忽略广播 pingdmesg_restrict让非特权用户看不到内核日志减少信息泄露后两个是防止硬链接和符号链接被滥用提权。应用并验证sysctl --system sysctl net.ipv4.tcp_syncookies注意sysctl --system会按文件名顺序加载数字前缀决定优先级所以用99-能保证最后生效。6. 磁盘、Swap 与文件系统规划6.1 磁盘现状查看与在线扩容拿到机器先看磁盘布局这一步很多人跳过结果后面发现根分区只有 20Glsblk -f df -hT云盘扩容的典型场景是控制台上把云盘从 40G 调到 100G但系统里看到的还是 40G。这时候需要先扩展分区再扩文件系统growpart /dev/vda 1 resize2fs /dev/vda1 # ext4 # xfs_growfs / # xfs 用这个growpart来自cloud-guest-utils包有些镜像没预装。ext4 支持在线扩容不用卸载xfs 也支持但命令不同。这个差别如果搞混会得到一堆看不懂的报错。6.2 Swap 到底要配多大Swap 大小没有统一答案取决于内存容量和业务类型。下面这个表是我在实际项目中常用的经验值物理内存建议 Swap说明≤ 2 GB2 GB内存紧张Swap 是保命线2 - 8 GB等于内存兼顾突发流量8 - 64 GB4 - 8 GB主要防 OOM不做日常使用 64 GB4 GB 或不配更依赖监控和限流如果你用的是云盘延迟比本地盘高我建议把vm.swappiness调低到 10 左右让系统尽量用内存只有在实在不够时才动用 Swap。配置方式sysctl -w vm.swappiness10 echo vm.swappiness 10 /etc/sysctl.d/99-swap.conf6.3 用 Swap 文件代替 Swap 分区的完整步骤现在主流做法是用文件代替分区灵活且不用重新分区fallocate -l 4G /swapfile chmod 600 /swapfile mkswap /swapfile swapon /swapfile echo /swapfile none swap sw 0 0 /etc/fstab free -h swapon --show四个关键点fallocate比dd快很多权限必须是 600否则 swap 会拒绝启用mkswap生成的文件头不能少写进/etc/fstab才能开机自动挂载。常见坑如果你用的是 Btrfs 文件系统fallocate创建的 swap 文件会导致系统挂起。Btrfs 下需要用dd或者干脆单独划一个分区。这个坑我是在一次线上事故里学到的代价不小。6.4 数据盘挂载与 fstab 的正确写法如果有独立数据盘挂载时用 UUID 而不是设备名因为设备名在重启后可能变化blkid /dev/vdb1 mkdir -p /data echo UUIDxxxx-xxxx /data ext4 defaults,noatime 0 2 /etc/fstab mount -a df -h /datanoatime表示不记录文件访问时间能减少写入量对 SSD 寿命和性能都有好处。mount -a会按 fstab 全部挂载一遍如果这一步报错千万不要重启因为 fstab 写错会导致系统进不了正常启动流程得进救援模式修。7. 常见问题与排查速查7.1 五类高频故障的定位思路第一类SSH 连不上。按顺序排查云安全组是否放行、systemctl status ssh是否运行、ss -tlnp | grep 22端口是否监听、sshd -T配置是否正常、journalctl -u ssh -n 50看最近日志。这个顺序是从外到内能最快排除大范围问题。第二类改了配置导致服务起不来。sshd、chrony 这类服务都有语法检查命令sshd -t、chronyc相关检查改完先验证再重启是最省时间的好习惯。第三类时间对不上。先看timedatectl如果NTP service显示 inactive说明同步服务没跑。再看chronyc sources -v如果所有源都是^?说明都连不上多半是 UDP 123 被防火墙挡住了。第四类磁盘满了。df -h只能看到分区级别用du -sh /* 2/dev/null | sort -h逐层找。日志文件是最常见的原因/var/log/journal如果没限制大小能涨到几个 G。第五类更新之后行为变了。Debian 的更新相对保守但内核升级后需要重启才会生效uname -r和dpkg -l | grep linux-image对比一下就知道有没有待生效的内核。7.2 排查速查表现象最可能原因快速验证命令SSH 超时无响应安全组或防火墙拦截ss -tlnp、云控制台SSH 提示 Permission denied密钥权限或认证方式sshd -T、检查 authorized_keys 权限服务启动失败配置语法错误journalctl -u 服务名 -n 50时间偏移超过 1 秒NTP 未同步chronyc tracking磁盘写入慢Swap 频繁或磁盘满free -h、iostat -x 1apt 报 GPG 错误密钥过期或源不匹配apt update完整输出主机名解析慢/etc/hosts 缺本机映射time hostname -f8. 把流程固化成可复用的初始化脚本8.1 脚本设计要过的三道坎第一道坎是幂等性。脚本要能重复执行而不出错比如创建用户前先判断是否存在追加配置前先检查是否已存在。最简单的方式是用id -u deploy /dev/null || adduser ...这种短路写法。第二道坎是失败即停。脚本开头一定要加set -euo pipefail任何一个命令失败就立即退出避免在错误状态下继续改配置把小问题滚成大问题。第三道坎是不要自动重启 SSH 服务。脚本可以改配置文件但最后一步的重载应该留给人来确认或者至少把新旧配置对比打印出来。8.2 脚本骨架示例#!/usr/bin/env bash set -euo pipefail ADMIN_USER${ADMIN_USER:-deploy} PUBKEY${PUBKEY:-} log() { printf [%s] %s\n $(date %H:%M:%S) $*; } # 1. 时区与主机名 timedatectl set-timezone Asia/Shanghai # 2. 创建运维账号 if ! id -u $ADMIN_USER /dev/null; then adduser --disabled-password --gecos $ADMIN_USER usermod -aG sudo $ADMIN_USER fi # 3. 注入公钥 if [[ -n $PUBKEY ]]; then install -d -m 700 -o $ADMIN_USER -g $ADMIN_USER /home/$ADMIN_USER/.ssh echo $PUBKEY /home/$ADMIN_USER/.ssh/authorized_keys chmod 600 /home/$ADMIN_USER/.ssh/authorized_keys chown $ADMIN_USER:$ADMIN_USER /home/$ADMIN_USER/.ssh/authorized_keys fi # 4. 安装基础工具 export DEBIAN_FRONTENDnoninteractive apt update apt install -y chrony ufw fail2ban curl git tmux jq mtr-tiny # 5. SSH 加固只写配置不自动重启 cat /etc/ssh/sshd_config.d/99-custom.conf EOF PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes MaxAuthTries 3 LoginGraceTime 20 EOF sshd -t # 6. 防火墙 ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp ufw --force enable log 初始化完成请用新会话验证 SSH 后再重载 sshd用的时候把公钥通过环境变量传进去就行PUBKEY$(cat ~/.ssh/id_ed25519_debian12.pub) bash init.sh脚本执行完之后我会再手动跑一遍sshd -T确认配置然后开新终端登录成功之后再systemctl reload ssh。9. 一些个人经验与后续可扩展的方向这套流程我用在不同规模的环境里最后沉淀下来几条体会。第一配置文件的每一处修改都要能回滚我习惯在改之前cp xxx xxx.bak.$(date %F)看起来土但真出事的时候一分钟就能恢复。第二不要迷信一键脚本脚本能帮你省时间但不能替你理解每一步在做什么尤其是防火墙和 SSH 这两块出问题的代价是失联。第三初始化做完要留一份记录我用一个简单的 Markdown 文件记下主机名、IP、时区、开了哪些端口、装了哪些服务半年后再接手的时候能省掉大量考古时间。后续如果这套流程要扩展我会往两个方向走一是接上配置管理工具把脚本变成声明式的 playbook几十台机器一起管的时候差异才算真正可控二是加一层基础监控哪怕只是 node_exporter 加一个简单的 Grafana 面板也能让磁盘什么时候开始涨的内存什么时候开始抖这类问题从猜测变成查数据。不过在只有一两台机器的时候把上面这些做完其实已经能覆盖九成以上的日常需求了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进