Ubuntu服务器默认安装后并不是为高强度生产环境准备的,很多安全策略和自动化机制都需要手动加固。如果你管理着多台Ubuntu服务器,还在逐台登录、手动更新、手动查看日志,不仅效率低,还容易遗漏关键风险。真正有效的做法是把日常运维中重复、高风险、易出错的操作脚本化,配合定时任务和监控告警,形成一套半自动甚至全自动的运维体系。下面直接讲具体怎么落地。

先做安全基线检查脚本

安全加固的第一步是知道服务器现状。写一个基线检查脚本,把Ubuntu系统里最容易被攻击的配置项全部检查一遍,输出明确结果。检查项包括:是否禁用了root的SSH密码登录、是否修改了默认SSH端口、防火墙是否开启、是否安装了fail2ban、是否有未授权的sudo权限账户、系统更新是否滞后、是否开启了不必要的服务等。

一个简化版的检查脚本可以这样写:

#!/bin/bash
echo "=== SSH配置检查 ==="
grep -E "^(PermitRootLogin|PasswordAuthentication|Port)" /etc/ssh/sshd_config 2>/dev/null
echo "=== 防火墙状态 ==="
sudo ufw status verbose 2>/dev/null || echo "UFW未安装或未启用"
echo "=== Fail2ban状态 ==="
systemctl is-active fail2ban 2>/dev/null || echo "fail2ban未运行"
echo "=== Sudo权限账户 ==="
grep -Po '^sudo:.*:\K.*$' /etc/group
echo "=== 可更新包数量 ==="
apt list --upgradable 2>/dev/null | grep -c upgradable
echo "=== 监听中的端口 ==="
ss -tulnp | grep LISTEN

把这个脚本保存为security_check.sh,加上执行权限后定期运行。更推荐的做法是把它接入到集中化运维平台或者定时任务里,每天跑一次,把结果追加到日志文件,再配合简单的关键词告警,比如发现“PermitRootLogin yes”就立刻通知你。

自动化安全更新与重启策略

Ubuntu的unattended-upgrades包可以自动安装安全更新,但很多人配置完就不管了,结果要么更新没生效,要么服务更新后需要重启但一直没重启,导致内核漏洞修了等于没修。正确做法是明确配置只自动安装安全更新,并且设置自动重启策略。

安装并启用:

sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades

然后编辑/etc/apt/apt.conf.d/50unattended-upgrades,重点确认以下几行:

Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
};
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";

这样系统只自动更新安全源里的包,避免更新普通软件包引入兼容性问题。自动重启放在凌晨三点,对业务影响最小。如果某些服务不能随便重启,可以在配置文件里用Unattended-Upgrade::Automatic-Reboot-WithUsers "false"来避免有用户登录时重启,或者把关键服务放到黑名单里。

另外建议配合一个更新后检查脚本,确认关键服务在重启后是否正常拉起。比如Nginx、MySQL、Redis这些,更新后自动执行systemctl is-active检查,异常就发告警。

SSH登录防护与动态封锁脚本

Ubuntu服务器最大的攻击面就是SSH。除了改端口、禁用密码登录、使用密钥认证这些基础操作,还需要动态识别暴力破解并自动封锁。fail2ban是标配,但默认配置对Ubuntu的日志格式适配有时不够精准,需要根据实际日志路径调整。

安装fail2ban后,创建一个本地配置/etc/fail2ban/jail.local:

[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600

这个配置表示10分钟内失败3次就封禁1小时。对于攻击密集的服务器,可以把bantime调大到86400甚至更长。同时建议把封禁日志单独输出,方便后续分析攻击来源。

如果不想依赖fail2ban,也可以用纯脚本实现轻量级封锁。思路是定时分析/var/log/auth.log,提取失败登录的IP,超过阈值就用ufw拒绝。脚本示例:

#!/bin/bash
LOG=/var/log/auth.log
THRESHOLD=5
BLOCKLIST=/tmp/ssh_blocked_ips.txt
tail -n 5000 $LOG | grep "Failed password" | grep -oP 'from \K[0-9.]+' | sort | uniq -c | sort -rn | while read count ip; do
    if [ $count -ge $THRESHOLD ]; then
        if ! grep -q $ip $BLOCKLIST 2>/dev/null; then
            sudo ufw deny from $ip to any
            echo $ip >> $BLOCKLIST
            echo "[$(date)] 已封锁 $ip,失败次数 $count" >> /var/log/ssh_auto_block.log
        fi
    fi
done

这个脚本可以每5分钟执行一次,对频繁攻击的IP自动封锁。优点是轻量、透明,缺点是功能不如fail2ban丰富,适合作为补充手段。

日志集中分析与异常检测

单台服务器的日志只能看到局部问题,多台服务器就需要把关键日志集中起来分析。Ubuntu上可以用rsyslog把auth日志、syslog、内核日志转发到一台日志服务器,再配合简单的脚本做异常模式匹配。

在日志服务器上配置rsyslog接收端:

# /etc/rsyslog.conf 取消注释
module(load="imudp")
input(type="imudp" port="514")

在客户端Ubuntu服务器上添加转发规则:

# /etc/rsyslog.d/50-forward.conf
auth,authpriv.* @日志服务器IP:514
*.* @日志服务器IP:514

日志集中后,写一个分析脚本,每天定时扫描关键事件:新增sudo用户、root登录成功、大量失败登录、异常服务停止、磁盘使用率超过阈值、内核报错等。把结果汇总成一份日报,通过邮件或企业微信机器人推送。

一个简单的异常扫描脚本片段:

#!/bin/bash
echo "=== 今日新增sudo用户 ==="
grep "useradd" /var/log/auth.log | grep sudo
echo "=== root登录记录 ==="
grep "Accepted" /var/log/auth.log | grep "root"
echo "=== 磁盘使用率超过80% ==="
df -h | awk 'NR>1 {gsub("%","",$5); if($5+0>80) print $1,$5"%"}'
echo "=== 服务异常停止 ==="
journalctl --since "today" -p err | grep -E "Failed|stopped|killed" | tail -20

这种脚本的价值在于把被动看日志变成主动发现异常,尤其是多台服务器时,能大幅减少人工巡检时间。

备份自动化与恢复验证

安全策略做得再好,数据丢失一样是灾难。Ubuntu服务器上最常见的备份需求是数据库、配置文件和网站目录。很多人用crontab定时tar打包,但从不验证备份是否可用,等到需要恢复时才发现备份文件损坏或者不完整。

一个可靠的备份脚本应该包含:备份、压缩、保留最近N份、校验、异地传输。以MySQL和网站目录为例:

#!/bin/bash
BACKUP_DIR=/backup
DATE=$(date +%F)
MYSQL_USER=backup
MYSQL_PASS=your_password
WEB_DIR=/var/www

mkdir -p $BACKUP_DIR/$DATE
mysqldump -u$MYSQL_USER -p$MYSQL_PASS --all-databases | gzip > $BACKUP_DIR/$DATE/db_all.sql.gz
tar -czf $BACKUP_DIR/$DATE/www.tar.gz $WEB_DIR
md5sum $BACKUP_DIR/$DATE/* > $BACKUP_DIR/$DATE/checksum.txt

# 保留最近7天
find $BACKUP_DIR -maxdepth 1 -type d -mtime +7 -exec rm -rf {} \;

# 异地传输示例(rsync到备份服务器)
rsync -avz --delete $BACKUP_DIR/ backup@备份服务器:/backup/ubuntu-server/

关键点是md5校验和异地传输。每周至少做一次恢复演练,把备份文件解压到测试目录,确认数据库能导入、网站文件完整。这个演练也可以脚本化,自动执行恢复并检查关键表是否存在。

资源监控与自动扩容告警

Ubuntu服务器在资源耗尽前往往会有征兆,比如内存使用率持续上升、磁盘IO等待变高、CPU负载异常。与其等服务器卡死再处理,不如用脚本定时采集指标,超过阈值就告警,必要时自动清理。

一个综合资源监控脚本:

#!/bin/bash
CPU_LOAD=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | tr -d ' ')
MEM_USED=$(free | awk '/Mem/{printf("%.0f"), $3/$2*100}')
DISK_USED=$(df / | awk 'NR==2 {gsub("%","",$5); print $5}')
SWAP_USED=$(free | awk '/Swap/{if($2>0) printf("%.0f"), $3/$2*100; else print 0}')

if [ $(echo "$CPU_LOAD > 4" | bc) -eq 1 ]; then
    echo "告警:CPU负载过高 $CPU_LOAD" >> /var/log/resource_alert.log
fi
if [ $MEM_USED -gt 90 ]; then
    echo "告警:内存使用率 $MEM_USED%" >> /var/log/resource_alert.log
    # 自动清理页缓存
    sync && echo 1 > /proc/sys/vm/drop_caches
fi
if [ $DISK_USED -gt 85 ]; then
    echo "告警:磁盘使用率 $DISK_USED%" >> /var/log/resource_alert.log
    # 清理旧日志
    find /var/log -type f -name "*.log" -mtime +30 -delete
fi

这个脚本不仅能告警,还能在内存和磁盘紧张时自动做无害清理。注意自动清理要保守,只清理确定无用的缓存和旧日志,避免误删业务数据。

定时任务统一管理与防冲突

脚本多了以后,crontab会变得混乱,执行时间冲突、重复执行、失败无感知是常见问题。建议把所有运维脚本统一放在/opt/scripts目录下,用统一的命名规范,比如backup_db.sh、security_check.sh、resource_monitor.sh。然后在crontab里集中管理,每个任务都加上日志输出和锁机制。

锁机制防止脚本未执行完又被触发:

#!/bin/bash
LOCK_FILE=/tmp/backup_db.lock
if [ -f $LOCK_FILE ]; then
    echo "备份脚本已在运行,跳过本次执行"
    exit 1
fi
touch $LOCK_FILE
trap "rm -f $LOCK_FILE" EXIT

# 实际备份逻辑

crontab配置示例:

# 每天凌晨2点备份
0 2 * * * /opt/scripts/backup_db.sh >> /var/log/backup_db.log 2>&1
# 每5分钟检查资源
*/5 * * * * /opt/scripts/resource_monitor.sh >> /var/log/resource_monitor.log 2>&1
# 每小时做安全基线检查
0 * * * * /opt/scripts/security_check.sh >> /var/log/security_check.log 2>&1

所有脚本的输出都重定向到独立日志,方便回溯。如果脚本执行失败,可以通过日志里的错误信息快速定位,而不是等到业务出问题才发现。

用systemd timer替代cron实现更精细调度

对于需要精确控制执行间隔、失败重试、依赖关系的任务,systemd timer比cron更合适。比如某个备份任务需要在网络服务启动后才能执行,或者某个清理任务需要在备份完成后才运行。

创建一个timer单元:

# /etc/systemd/system/backup.service
[Unit]
Description=Database backup service
After=network.target mysql.service

[Service]
Type=oneshot
ExecStart=/opt/scripts/backup_db.sh
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 2am

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target

启用timer:

sudo systemctl daemon-reload
sudo systemctl enable backup.timer
sudo systemctl start backup.timer

systemd timer的优势在于可以精确到秒、支持Persistent(错过执行时间后补跑)、可以设置Unit依赖、执行结果可以通过systemctl status直接查看。对于关键运维任务,强烈建议从cron迁移到systemd timer。

集中化配置管理与版本控制

当服务器数量超过5台,脚本和配置文件的同步就会成为新问题。每台机器手动拷贝脚本不仅低效,还容易出现版本不一致。把运维脚本和配置文件纳入Git管理,配合简单的拉取机制,可以做到一处更新、全集群生效。

在Git仓库中组织脚本:

ops-scripts/
├── common/
│   ├── security_check.sh
│   ├── resource_monitor.sh
│   └── backup_db.sh
├── web-server/
│   └── nginx_hardening.sh
└── db-server/
    └── mysql_backup.sh

每台服务器上配置一个同步脚本,定时从Git仓库拉取最新脚本:

#!/bin/bash
cd /opt/ops-scripts
git pull origin main --quiet
chmod +x common/*.sh

这样所有服务器的运维脚本版本一致,修改一次就能同步到所有机器。配合Git的提交记录,还能追溯每次变更内容和时间,大幅降低运维风险。

把这些脚本和机制组合起来,Ubuntu服务器的日常运维就能从手动操作转变成自动化流程。安全基线检查、自动更新、SSH防护、日志分析、备份验证、资源监控、定时调度、配置同步,每一块都有明确脚本支撑,并且互相配合。关键是先从小处开始,把最痛的那几个点脚本化,逐步扩展,最终形成一套适合自己业务规模的自动化运维体系。