接手一台Ubuntu生产服务器,第一件事不是装软件,而是锁死SSH访问通道。默认的22端口和密码登录在公网上存活不超过半小时就会被扫描爆破。直接修改/etc/ssh/sshd_config,把Port改成四位以上的高位端口,比如22922,同时把PermitRootLogin设为no,PasswordAuthentication也设为no,只留密钥登录。改完执行systemctl restart sshd,千万别在当前窗口断开测试,另开一个终端用新端口连一下,确认通了再关旧会话。这一步做完,90%的自动化攻击流量就与你无关了。
密钥管理有个极易被忽视的细节:私钥文件权限必须是600,.ssh目录权限700,错一点都不行。很多运维把密钥从Windows传上来直接用,权限是644或者更糟,SSH直接拒绝连接还看不出明显报错。正确的做法是chmod 600 ~/.ssh/id_rsa && chmod 700 ~/.ssh,同时检查authorized_keys里有没有来路不明的公钥,定期清理离职人员的访问凭证。
系统层面的安全基线Ubuntu Server装完系统,默认的ufw防火墙其实处于inactive状态。ufw enable之前,先把规则写好:ufw default deny incoming,ufw default allow outgoing,然后只放行业务需要的端口。如果你改了SSH端口,一定要先ufw allow 22922/tcp,再ufw enable,顺序反了就会把自己锁在外面。生产环境建议再装个fail2ban,apt install fail2ban -y之后,在/etc/fail2ban/jail.local里针对SSH新建一个监控规则,bantime设成3600秒,maxretry设成3次,findtime设成600秒。这样任何IP在10分钟内输错3次密钥就直接封一小时,暴力破解基本废了。
还有一个常被忽略的攻击面是系统用户。用awk -F: '($3==0){print $1}' /etc/passwd查一下哪些账户UID是0,正常情况下只有root。再用lastlog看看哪些账户从来没登录过,把不需要的系统用户shell改成/usr/sbin/nologin。特别是games、sync这种默认账户,攻击者拿到低权限shell后经常利用这些账户提权。
内核参数调优不是玄学Ubuntu默认的内核参数偏保守,直接用于生产高并发场景会出现各种诡异问题。编辑/etc/sysctl.conf,加一组经过验证的参数:net.core.somaxconn = 65535控制TCP连接队列长度,net.ipv4.tcp_max_syn_backlog = 65535应对SYN洪水,net.ipv4.tcp_tw_reuse = 1允许TIME_WAIT端口复用,net.ipv4.tcp_fin_timeout = 10加速释放半连接。特别注意vm.swappiness默认是60,对于数据库服务器这就是灾难,改成10甚至1,让系统尽量用物理内存而不是急着往swap里写。执行sysctl -p生效后,用free -h看一眼swap使用量,如果已经用了不少,先swapoff -a再swapon -a清空,但前提是物理内存足够容纳swap里的数据。
文件句柄限制是另一个坑。ulimit -n默认1024,高并发下日志都打不开。在/etc/security/limits.conf里加两行:* soft nofile 65535和* hard nofile 65535,同时修改/etc/systemd/system.conf里的DefaultLimitNOFILE=65535,重启后生效。用cat /proc/sys/fs/file-max看系统全局上限,通常不需要改,但要知道这个值的存在。
无人值守的安全更新策略生产环境最怕两件事:不更新被漏洞打穿,更新把业务打挂。Ubuntu的unattended-upgrades就是解决这个矛盾的。apt install unattended-upgrades -y装好后,编辑/etc/apt/apt.conf.d/50unattended-upgrades,核心配置就几行:只勾选安全更新源"${distro_id}:${distro_codename}-security",其他更新源全部注释掉。Unattended-Upgrade::AutoFixInterruptedDpkg设为true,Unattended-Upgrade::MinimalSteps设为true,这样更新过程即使中断也能自动修复。最关键的是Unattended-Upgrade::Mail配置项,填上自己的邮箱,每次自动更新后系统会发邮件报告更新了哪些包,出了事能溯源。
自动重启策略需要根据业务场景定。Unattended-Upgrade::Automatic-Reboot默认是false,如果某些安全更新涉及内核或关键库,不重启等于没更新。建议设为true,配合Unattended-Upgrade::Automatic-Reboot-Time "03:00",选在业务低谷时段。但数据库服务器要慎重,重启可能导致数据不一致,这种情况应该把数据库相关包加入黑名单,手动维护。
日志管理决定排障效率服务器出问题时的第一反应是看日志,但很多人发现日志要么被轮转删掉了,要么大到打不开。systemd-journald默认日志上限是所在文件系统的10%,很容易撑满磁盘。修改/etc/systemd/journald.conf,SystemMaxUse=2G,SystemKeepFree=5G,RuntimeMaxUse=1G,这样日志总量可控且给系统预留空间。然后装logrotate,在/etc/logrotate.d/下给每个重要应用单独写规则,核心参数是daily、rotate 7、compress、missingok、notifempty,再加个postrotate脚本给应用发HUP信号让它重新打开日志文件。很多应用日志中断就是因为logrotate后没发信号,进程还往旧inode里写,新日志文件一直是空的。
集中式日志方案推荐用rsyslog转发到中心服务器,或者用vector这类轻量采集器。但单机场景下,至少保证journalctl -u能看到所有systemd服务的日志,grep能找到关键错误。一个实用技巧:journalctl -p err -b只看当前启动以来的error级别日志,排障时比翻几十万行日志快得多。
备份策略的三层防线数据库备份很多人只做全量,恢复时发现要回滚到某个时间点根本做不到。MySQL或PostgreSQL必须开启binlog或WAL归档,配合全量备份实现时间点恢复。全量备份脚本用mysqldump或pg_dump,每天一次,保留7天。二进制日志保留14天以上,因为有些故障可能要一周后才被发现。备份文件不能只存在本机,用rsync或rclone同步到对象存储或者另一台物理机,本地、同机房异地、跨地域三层才算完整。
文件系统的快照能力不要浪费。如果用的是LVM或ZFS,全量备份前先打快照,备份从快照读,不影响正在运行的业务。ZFS的send/receive做增量备份效率极高,十几TB的数据增量可能只有几百MB,几分钟传完。没有ZFS条件的,至少用rsync的--link-dest参数做硬链接增量,每天一个目录但重复文件不占空间。
备份验证是最后一道防线。每周至少做一次恢复演练,从备份文件里随机抽几张表恢复到一个测试库,跑一遍业务校验脚本。不验证的备份等于没有备份,这句话每个运维都该刻在脑子里。
资源监控与告警的实战配置Ubuntu自带的监控工具其实够用,关键是要把告警通道打通。htop看实时负载,iotop看磁盘IO瓶颈,nethogs看哪个进程在疯狂占用带宽。但这些是事后排查用的,事前预警需要持续采集。node_exporter+Prometheus+Grafana是标配,但单机不想搭整套的话,用netdata五分钟装好就能用,默认开19999端口,浏览器直接看几百个指标,CPU、内存、磁盘、网络、各进程资源占用一目了然,还自带告警功能,支持推送各种即时通讯工具。
磁盘空间告警最容易实现也最容易被忽略。一条crontab就能救命:0 * * * * df -h / | awk 'NR==2{if(substr($5,1,length($5)-1)>85) print "磁盘使用率超过85%"}',配合mail命令发邮件。更完善的做法是监控inode使用率,df -i查,因为小文件太多把inode耗尽时磁盘空间可能还剩很多,但系统已经写不进任何文件了。
OOM Killer的行为也要监控。dmesg -T | grep -i oom能看到历史OOM记录,如果经常触发,说明内存配置不合理或者有内存泄漏。调整进程的oom_score_adj值可以保护关键服务不被杀,比如数据库进程设为-1000,echo -1000 > /proc/$(pgrep mysqld)/oom_score_adj,系统内存耗尽时会优先杀其他进程。
服务编排与进程守护systemd是Ubuntu的默认初始化系统,但很多人还在用nohup和screen跑服务,这在生产环境是绝对禁止的。每个长期运行的服务必须写systemd unit文件,放在/etc/systemd/system/下。一个规范的service文件至少包含这些配置:Type=simple或notify,ExecStart指定启动命令和参数,Restart=on-failure,RestartSec=5s,StandardOutput和StandardError重定向到journald。最关键的是User和Group字段,绝不能用root跑业务进程,每个服务建专用账户,权限最小化。
依赖关系要写清楚。数据库服务启动后应用才能启动,用After=postgresql.service和Requires=postgresql.service声明。网络就绪后才启动需要网络的服务,加After=network-online.target和Wants=network-online.target。systemctl list-dependencies可以反向查看依赖树,确认启动顺序符合预期。
定时任务统一用systemd timer替代cron。timer的优势是有日志、可精确控制执行时间、支持随机延迟避免惊群效应。写一个service文件定义要执行的任务,再写一个同名timer文件定义OnCalendar=*-*-* 02:00:00和RandomizedDelaySec=300,这样所有机器不会在同一秒同时执行,对后端存储的压力分散开来。
软件包管理的版本锁定策略apt upgrade无脑全升是生产环境大忌。用apt-mark hold锁定关键软件包版本,比如数据库版本、内核版本、特定语言运行时。hold状态的包即使apt upgrade也不会更新,直到你手动apt-mark unhold。查看已锁定的包用apt-mark showhold,这个列表应该纳入配置管理,新机器部署时直接应用。
内核更新需要特殊对待。Ubuntu默认会安装新内核但保留旧内核,/boot分区可能被旧内核塞满。定期执行apt autoremove --purge清理旧内核,但至少保留两个已知可用的版本。如果某次内核更新后系统异常,重启时在GRUB菜单选旧内核进入,确认旧内核正常工作后,把问题内核版本hold住,等上游修复再解封。
第三方软件源是安全隐患的重灾区。添加PPA或外部源之前,检查源是否还在活跃维护,很多源最后更新是三五年前,依赖链早已断裂。安装完需要的软件后,立即在/etc/apt/sources.list.d/里把对应源文件的后缀改成.disabled,防止后续apt update时拉取不可信的索引。
把这些要点落地到日常运维中,Ubuntu服务器的稳定性会有一个质的提升。生产环境的运维不是炫技,是把每一个可能出问题的点提前堵上,让系统在无人值守的情况下也能持续稳定运行。上面每一条都是踩过坑之后总结出来的,照做能避开绝大多数常见故障。
