在Ubuntu服务器上维护Nginx和PHP服务时,最让人头疼的不是初始配置,而是服务无声无息地挂掉后,流量还在继续涌入,返回的全是502或504错误。等你发现时,业务可能已经中断了十几分钟。靠人工盯着日志或者等报警系统触发,往往存在延迟。最直接的解决办法就是写一个驻留在本地的监控脚本,让它以秒级或分钟级频率检查服务状态,一旦发现异常立即自动修复并记录日志。下面我会给出一个完整的、可直接部署的Bash脚本方案,并拆解每个模块的设计思路,让你不仅能用,还能根据自己的环境灵活改造。
脚本核心设计思路:状态检测、自动修复、日志记录三位一体一个合格的监控脚本不能只做“看门狗”,它至少要完成三件事:准确判断Nginx和PHP-FPM是否真正在运行,而不是仅仅检查进程是否存在;在服务异常时尝试重启,并验证重启是否成功;将所有事件详细记录到日志文件,方便事后追溯。我们还要考虑脚本自身不能成为资源负担,所以要用轻量级的检测手段,避免频繁调用消耗大的命令。
检测Nginx状态,很多人习惯用pidof nginx或者ps aux | grep nginx,但这两种方式都有盲区。进程存在不代表Nginx能正常处理请求,比如配置错误导致启动失败但旧进程残留,或者工作进程僵死。更可靠的做法是直接请求本地HTTP端口,检查响应状态码和内容。检测PHP-FPM则要复杂一些,因为它是通过Unix Socket或TCP端口通信的,我们需要检查Socket文件是否存在且可写,或者检查PHP-FPM的主进程和子进程池状态。
完整的监控脚本代码下面这个脚本整合了上述所有逻辑,你可以直接保存为nginx_php_monitor.sh,放在/usr/local/bin/目录下,并配合crontab定时执行。
#!/bin/bash
# ============================================
# Nginx与PHP-FPM服务状态监控脚本
# 适用于Ubuntu 20.04/22.04环境
# 功能:检测服务状态,异常时自动重启并记录日志
# ============================================
# 配置变量
LOG_FILE="/var/log/service_monitor.log"
NGINX_CHECK_URL="http://127.0.0.1/nginx_status"
PHP_FPM_SOCKET="/run/php/php8.1-fpm.sock"
PHP_FPM_SERVICE="php8.1-fpm"
NGINX_SERVICE="nginx"
MAX_RETRY=3
RETRY_INTERVAL=5
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
# 确保日志目录存在
mkdir -p "$(dirname "$LOG_FILE")"
# 日志记录函数
log_message() {
echo "[$TIMESTAMP] $1" >> "$LOG_FILE"
}
# 检查Nginx是否正常响应
check_nginx() {
# 使用curl请求本地nginx状态页,设置超时避免卡死
local http_code
http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 --max-time 10 "$NGINX_CHECK_URL")
if [ "$http_code" = "200" ]; then
return 0
else
return 1
fi
}
# 检查PHP-FPM Socket是否可用
check_php_fpm() {
# 检查Socket文件是否存在
if [ ! -S "$PHP_FPM_SOCKET" ]; then
return 1
fi
# 使用cgi-fcgi或直接通过Socket发送状态请求
# 这里使用SCRIPT_FILENAME和QUERY_STRING来触发PHP-FPM响应
local response
response=$(echo -e "GET /status HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" | \
timeout 5 nc -U "$PHP_FPM_SOCKET" 2>/dev/null | head -n 1)
if echo "$response" | grep -q "200 OK"; then
return 0
else
return 1
fi
}
# 重启服务函数
restart_service() {
local service_name="$1"
local retry_count=0
while [ $retry_count -lt $MAX_RETRY ]; do
systemctl restart "$service_name" 2>/dev/null
sleep "$RETRY_INTERVAL"
# 重启后验证
if [ "$service_name" = "$NGINX_SERVICE" ]; then
if check_nginx; then
log_message "SUCCESS: $service_name 重启成功,当前状态正常"
return 0
fi
elif [ "$service_name" = "$PHP_FPM_SERVICE" ]; then
if check_php_fpm; then
log_message "SUCCESS: $service_name 重启成功,当前状态正常"
return 0
fi
fi
retry_count=$((retry_count + 1))
log_message "WARNING: $service_name 第${retry_count}次重启后验证失败,准备重试"
done
log_message "ERROR: $service_name 重启${MAX_RETRY}次后仍然异常,请人工介入检查"
return 1
}
# 主监控逻辑
main() {
local nginx_status="正常"
local php_status="正常"
local need_restart_nginx=false
local need_restart_php=false
# 检测Nginx
if ! check_nginx; then
nginx_status="异常"
need_restart_nginx=true
log_message "ALERT: Nginx检测失败,HTTP请求未返回200状态码"
fi
# 检测PHP-FPM
if ! check_php_fpm; then
php_status="异常"
need_restart_php=true
log_message "ALERT: PHP-FPM Socket检测失败,Socket文件或服务不可用"
fi
# 记录当前状态
log_message "INFO: 检测完成 - Nginx: $nginx_status, PHP-FPM: $php_status"
# 执行重启操作
if [ "$need_restart_nginx" = true ]; then
log_message "ACTION: 开始尝试重启Nginx服务"
restart_service "$NGINX_SERVICE"
fi
if [ "$need_restart_php" = true ]; then
log_message "ACTION: 开始尝试重启PHP-FPM服务"
restart_service "$PHP_FPM_SERVICE"
fi
}
# 执行主函数
main
exit 0
脚本关键模块深度解析
这个脚本不是简单地把检测和重启拼凑在一起,有几个细节决定了它在生产环境中的可靠性。首先是Nginx的检测方式,我用了curl直接请求本地状态页,而不是检查进程。你需要在Nginx配置中提前开启stub_status模块,在server块里加上如下配置:
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
这样做的好处是,只有当Nginx真正能处理HTTP请求时才会返回200,避免了进程假死的情况。curl的超时参数--connect-timeout 5和--max-time 10也很关键,防止脚本因为网络或服务卡死而无限等待,导致后续的crontab任务堆积。
PHP-FPM的检测部分,我选择了直接通过Unix Socket发送一个简化的HTTP请求。这里没有依赖php-fpm的status页面,因为默认情况下pm.status_path是关闭的,需要额外配置。用nc命令向Socket写入一个GET请求,只要PHP-FPM主进程能接收并返回HTTP响应头,就说明服务是存活的。如果你的PHP-FPM监听的是TCP端口(比如9000端口),可以把nc -U换成nc -w 3 127.0.0.1 9000,逻辑完全一样。
部署与自动化配置脚本写好后,需要赋予执行权限并配置定时任务。执行以下命令:
sudo chmod +x /usr/local/bin/nginx_php_monitor.sh
然后编辑crontab,让脚本每两分钟运行一次。这里不建议设置成一分钟一次,因为如果服务真的挂了,重启操作本身需要时间,太频繁的检测可能导致重复重启。
sudo crontab -e # 添加以下行 */2 * * * * /usr/local/bin/nginx_php_monitor.sh
日志文件会持续增长,建议配合logrotate进行日志轮转。在/etc/logrotate.d/目录下创建一个配置文件service_monitor:
/var/log/service_monitor.log {
weekly
rotate 4
compress
missingok
notifempty
create 640 root adm
}
扩展监控维度:从存活检测到性能预警
基础脚本只解决了“死没死”的问题,但很多线上故障是“半死不活”的状态,比如Nginx能响应但延迟飙升,PHP-FPM子进程全部被慢请求占满导致新请求排队。你可以在脚本里增加响应时间检测,用curl的-w参数提取time_total,当响应时间超过阈值(比如3秒)时记录警告日志,但不触发重启。这样运维人员就能从日志中发现性能劣化的趋势。
另一个实用的扩展是检查PHP-FPM的子进程使用率。可以通过解析PHP-FPM的status页面(需要开启pm.status_path)获取active processes和max children的比值,当使用率超过80%时发送告警。这比等服务彻底挂掉再重启要主动得多。
多版本PHP环境下的适配Ubuntu系统上可能同时安装多个PHP版本,比如php7.4-fpm和php8.1-fpm并存。脚本中的PHP_FPM_SOCKET和PHP_FPM_SERVICE变量需要根据实际运行的版本调整。你可以用以下命令查看当前系统中所有PHP-FPM的Socket文件:
ls -la /run/php/
如果一台服务器上运行着多个PHP版本,建议为每个版本创建独立的监控配置,或者修改脚本让它遍历所有Socket文件逐一检测。systemctl列表里也会显示所有已安装的php-fpm服务单元,用systemctl list-units | grep php-fpm可以快速确认服务名称。
避免误报和重复告警的策略任何监控系统最怕的就是误报,半夜被报警叫起来发现服务其实没问题,这对运维人员的折磨不亚于真的故障。脚本里设置了MAX_RETRY=3,每次重启间隔5秒,总共15秒的恢复窗口。在这期间如果服务恢复正常,日志只会记录到WARNING级别,不会上升到ERROR。你还可以在脚本开头加一个锁文件机制,防止上一个实例还没执行完,crontab又启动了新的实例,造成并发重启的混乱。
LOCK_FILE="/var/run/service_monitor.lock"
exec 200>"$LOCK_FILE"
flock -n 200 || { log_message "ERROR: 脚本已在运行中,退出"; exit 1; }
这段代码用文件描述符和flock命令实现了进程互斥,放在主逻辑之前即可。如果脚本执行时间超过crontab的间隔,后续任务会自动跳过,不会堆积。
整个方案的核心价值在于,它不需要引入任何第三方监控系统,完全靠服务器自身的能力实现了基础自治。对于中小型项目或者边缘节点服务器来说,这种轻量级的自愈机制比搭建一整套Prometheus加Alertmanager要务实得多。把脚本部署到位,再配合日志轮转和必要的性能阈值检测,Ubuntu下的Nginx与PHP服务稳定性会提升一个明显的台阶。
