bash
#!/bin/bash
# =============================================================================
# 脚本名称: nginx_triage.sh
# 适用环境:Linux原生安装Nginx(无Docker容器)
# 核心特性:分段独立执行、全局缓存PID/路径可复制、完整故障现场取证
# 哲学驱动:本体论->奥卡姆剃刀->辩证法->整体论
# 运行权限:必须root/sudo(读取/proc、系统日志、端口、limits)
# 缓存文件:/tmp/nginx_diag_xxxx/nginx_diag_cache.txt(存放所有可复制参数)
# 归档包:/tmp/nginx_diag_xxxx.tar.gz 完整故障取证包
# 使用方式:
# 1. 完整一键执行:sudo bash nginx_triage_enhance.sh
# 2. 分段排查:复制标记【单段独立执行】整块代码,直接粘贴终端单独运行
# 原则:仅采集取证,不自动执行恢复操作
# =============================================================================
set -uo pipefail
# ======================【单段独立执行】全局颜色+工具函数+缓存初始化 ======================
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
WHITE='\033[1;37m'
NC='\033[0m'
# 分割线打印
divider() {
echo -e "\n${BLUE}============================================${NC}"
echo -e "${BLUE} $1${NC}"
echo -e "${BLUE}============================================${NC}\n"
}
# 打印高亮可复制变量,写入全局缓存文件
print_copy() {
local label="$1"
local val="$2"
echo -e "${WHITE}【可复制 $label 】${RED}$val${NC}"
echo "$label=$val" >> "$CACHE_FILE"
}
# 输出预填充变量的一键排查命令
quick_cmd() {
echo -e "${CYAN}[一键复制执行命令]${NC} $1"
echo "快捷命令:$1" >> "$CACHE_FILE"
}
# 执行命令并保存输出到取证文件
collect_file() {
local cmd="$1"
local save_path="$2"
echo "执行采集:$cmd" >> "$DIAG_SUMMARY"
eval "$cmd" > "$save_path" 2>&1
}
# 初始化全局取证缓存目录(所有分段共用)
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DIAG_ROOT="/tmp/nginx_diag_${TIMESTAMP}"
CACHE_FILE="${DIAG_ROOT}/nginx_diag_cache.txt"
DIAG_SUMMARY="${DIAG_ROOT}/diagnose_summary.log"
mkdir -p "$DIAG_ROOT"
> "$CACHE_FILE"
> "$DIAG_SUMMARY"
echo "故障取证采集时间:$(date)" >> "$DIAG_SUMMARY"
# Nginx全局默认路径(兼容主流发行版)
NGINX_MASTER_PID_FILE="/var/run/nginx.pid"
NGINX_ERROR_LOG="/var/log/nginx/error.log"
NGINX_ACCESS_LOG="/var/log/nginx/access.log"
NGINX_CONF_DIR="/etc/nginx"
JOURNAL_SINCE="5 minutes ago"
# ==========================================================================================
# ======================【单段独立执行】全局基础变量预采集(核心可复制参数) ======================
divider "全局基础参数预采集(全部存入缓存可复制)"
# 1. 获取主进程PID
MASTER_PID=""
if [ -f "$NGINX_MASTER_PID_FILE" ]; then
MASTER_PID=$(cat "$NGINX_MASTER_PID_FILE" 2>/dev/null)
fi
# PID文件丢失时,ps抓取master进程
if [ -z "$MASTER_PID" ] || ! kill -0 "$MASTER_PID" 2>/dev/null; then
MASTER_PID=$(ps -ef | grep "nginx: master process" | grep -v grep | awk '{print $2}' | head -1)
fi
# 2. 获取worker进程PID列表
WORKER_PIDS=$(pgrep -P "$MASTER_PID" 2>/dev/null | tr '\n' ' ')
WORKER_COUNT=$(pgrep -P "$MASTER_PID" -c 2>/dev/null || echo 0)
# 3. Nginx版本与编译参数
NGINX_BIN=$(command -v nginx 2>/dev/null)
NGINX_VERSION=$($NGINX_BIN -v 2>&1 || echo "未检测到nginx二进制")
NGINX_BUILD_ARGS=$($NGINX_BIN -V 2>&1 || echo "无编译参数")
# 4. systemd服务状态
SYSTEMD_STATUS=$(systemctl status nginx 2>/dev/null || echo "非systemd系统")
# 5. 全部关键参数高亮打印+写入缓存
print_copy "Nginx Master主进程PID" "$MASTER_PID"
print_copy "Worker进程PID列表" "$WORKER_PIDS"
print_copy "Worker进程数量" "$WORKER_COUNT"
print_copy "Nginx二进制路径" "$NGINX_BIN"
print_copy "Nginx配置目录" "$NGINX_CONF_DIR"
print_copy "错误日志路径" "$NGINX_ERROR_LOG"
print_copy "访问日志路径" "$NGINX_ACCESS_LOG"
print_copy "PID文件路径" "$NGINX_MASTER_PID_FILE"
print_copy "取证缓存目录" "$DIAG_ROOT"
print_copy "全局缓存文件" "$CACHE_FILE"
# 快捷查看所有缓存参数
quick_cmd "cat ${CACHE_FILE}"
quick_cmd "tar -czf ${DIAG_ROOT}.tar.gz -C /tmp nginx_diag_${TIMESTAMP}"
# ==========================================================================================
# ======================【单段独立执行】阶段一:本体论-进程状态完整校验 ======================
divider "阶段一 本体论:识别Nginx中断现象实体"
echo -e "${GREEN}[1.1 进程存活完整校验]${NC}"
if [ -n "$MASTER_PID" ] && kill -0 "$MASTER_PID" 2>/dev/null; then
echo "✅ Master进程正常运行 PID: $MASTER_PID"
echo "Worker进程总数: $WORKER_COUNT,PID列表: $WORKER_PIDS"
if [ "$WORKER_COUNT" -eq 0 ]; then
echo -e " ${RED}>>> 故障归类:Master存活无Worker,重载配置失败/worker持续崩溃${NC}"
fi
else
echo -e " ${RED}>>> 故障归类:Master进程已完全消失(被OOM杀死/手动停止/启动失败)${NC}"
fi
# 打印全量nginx进程树
echo -e "\n${GREEN}[1.2 Nginx完整进程树]${NC}"
collect_file "ps -efH | grep -E 'nginx' | grep -v grep" "${DIAG_ROOT}/nginx_process_tree.log"
cat "${DIAG_ROOT}/nginx_process_tree.log"
# 端口监听、连接队列堆积校验
echo -e "\n${GREEN}[1.3 端口监听&TCP连接队列]${NC}"
collect_file "ss -tlnp | grep nginx" "${DIAG_ROOT}/nginx_listen_ports.log"
cat "${DIAG_ROOT}/nginx_listen_ports.log"
# 80/443端口队列
for port in 80 443; do
recvq=$(ss -tan "sport = :$port" 2>/dev/null | awk 'NR>1 {sum+=$2} END{print sum}')
sendq=$(ss -tan "sport = :$port" 2>/dev/null | awk 'NR>1 {sum+=$3} END{print sum}')
echo "端口$port Recv-Q总堆积:$recvq | Send-Q总堆积:$sendq(非零代表请求阻塞)"
done
# 防火墙端口拦截检查
echo -e "\n${GREEN}[1.4 防火墙端口放行检测]${NC}"
if command -v firewall-cmd >/dev/null; then
collect_file "firewall-cmd --list-ports" "${DIAG_ROOT}/firewalld_ports.log"
cat "${DIAG_ROOT}/firewalld_ports.log"
elif command -v ufw >/dev/null; then
collect_file "ufw status" "${DIAG_ROOT}/ufw_status.log"
cat "${DIAG_ROOT}/ufw_status.log"
else
collect_file "iptables -L -n" "${DIAG_ROOT}/iptables_rules.log"
grep -E '80|443' "${DIAG_ROOT}/iptables_rules.log" || echo "iptables无80/443放行规则"
fi
# 手工调试快捷命令
quick_cmd "strace -p ${MASTER_PID} -f -s 200 # 追踪主进程所有系统调用"
quick_cmd "strace -p \${任意WorkerPID} # 定位worker阻塞/卡死"
# ==========================================================================================
# ======================【单段独立执行】阶段二:奥卡姆剃刀-高优先级日志检索 ======================
divider "阶段二 奥卡姆剃刀:高价值日志优先检索"
# 2.1 Nginx错误日志(标红严重级别错误)
echo -e "${GREEN}[2.1 Nginx错误日志最近30行(紧急错误标红)]${NC}"
if [ -f "$NGINX_ERROR_LOG" ]; then
collect_file "tail -100 $NGINX_ERROR_LOG" "${DIAG_ROOT}/nginx_error_full.log"
tail -30 "$NGINX_ERROR_LOG" | while IFS= read -r line; do
if echo "$line" | grep -qE '\[emerg\]|\[alert\]|\[crit\]|\[error\]'; then
echo -e " ${RED}$line${NC}"
else
echo " $line"
fi
done
# 致命错误关键字筛查
grep -q "No space left on device" "${DIAG_ROOT}/nginx_error_full.log" && echo -e "${RED}检测到:磁盘空间耗尽${NC}"
grep -q "Too many open files" "${DIAG_ROOT}/nginx_error_full.log" && echo -e "${RED}检测到:文件句柄上限耗尽${NC}"
grep -q "cannot bind" "${DIAG_ROOT}/nginx_error_full.log" && echo -e "${RED}检测到:端口绑定失败${NC}"
grep -q "certificate expired" "${DIAG_ROOT}/nginx_error_full.log" && echo -e "${RED}检测到:SSL证书过期${NC}"
else
echo -e "${RED}错误日志文件不存在:$NGINX_ERROR_LOG${NC}"
fi
# 2.2 系统日志OOM、进程杀死、段错误
echo -e "\n${GREEN}[2.2 系统日志:OOM/segfault/systemd杀进程]${NC}"
collect_file "dmesg | grep -iE 'oom|killed process|nginx|segfault' | tail -30" "${DIAG_ROOT}/dmesg_oom.log"
cat "${DIAG_ROOT}/dmesg_oom.log"
if command -v journalctl >/dev/null 2>&1; then
collect_file "journalctl --since '$JOURNAL_SINCE' --no-pager | grep -iE 'nginx|oom|kill'" "${DIAG_ROOT}/journal_nginx.log"
cat "${DIAG_ROOT}/journal_nginx.log"
elif [ -f /var/log/messages ]; then
collect_file "grep -iE 'nginx|oom|segfault' /var/log/messages | tail -20" "${DIAG_ROOT}/syslog_nginx.log"
cat "${DIAG_ROOT}/syslog_nginx.log"
fi
# 2.3 coredump崩溃文件检查
echo -e "\n${GREEN}[2.3 CoreDump崩溃转储文件检索]${NC}"
collect_file "coredumpctl list nginx 2>/dev/null" "${DIAG_ROOT}/coredump_list.log"
cat "${DIAG_ROOT}/coredump_list.log"
quick_cmd "coredumpctl gdb ${MASTER_PID} -batch -ex 'thread apply all bt' # 崩溃堆栈分析"
# ==========================================================================================
# ======================【单段独立执行】阶段三:资源瓶颈快速筛查(磁盘/内存/句柄) ======================
divider "阶段三 资源剃刀检查:磁盘、inode、内存、文件句柄"
# 3.1 磁盘与inode使用率(仅展示高于80%风险分区)
echo -e "${GREEN}[3.1 磁盘分区使用率]${NC}"
collect_file "df -h" "${DIAG_ROOT}/disk_usage.log"
awk '{if(NR==1 || $5+0>80) print}' "${DIAG_ROOT}/disk_usage.log" | grep -v tmpfs
echo -e "\n${GREEN}[3.2 Inode索引使用率]${NC}"
collect_file "df -i" "${DIAG_ROOT}/inode_usage.log"
awk '{if(NR==1 || $5+0>80) print}' "${DIAG_ROOT}/inode_usage.log" | grep -v tmpfs
# 3.2 内存、Swap全局快照
echo -e "\n${GREEN}[3.3 宿主机内存&Swap状态]${NC}"
collect_file "free -h; swapon --show" "${DIAG_ROOT}/mem_swap.log"
cat "${DIAG_ROOT}/mem_swap.log"
echo "内存占用TOP10进程:"
ps aux --sort=-%mem | head -10
# 3.3 文件描述符限制与进程当前句柄
echo -e "\n${GREEN}[3.4 文件句柄限制校验]${NC}"
echo "系统全局最大文件句柄:$(cat /proc/sys/fs/file-max)"
if [ -n "$MASTER_PID" ] && [ -d "/proc/$MASTER_PID" ]; then
collect_file "grep 'Max open files' /proc/$MASTER_PID/limits" "${DIAG_ROOT}/nginx_limits.log"
cat "${DIAG_ROOT}/nginx_limits.log"
fd_count=$(ls /proc/$MASTER_PID/fd/ 2>/dev/null | wc -l)
echo "Master进程当前打开文件句柄数:$fd_count"
fi
quick_cmd "lsof -p ${MASTER_PID} | wc -l # 精确统计进程句柄总数"
# ==========================================================================================
# ======================【单段独立执行】阶段四:整体论-配置、SSL、上游后端、状态页 ======================
divider "阶段四 整体论:配置、SSL证书、上游后端、Nginx状态页"
# 4.1 Nginx配置语法校验 + 完整配置导出
echo -e "${GREEN}[4.1 Nginx配置语法校验]${NC}"
collect_file "$NGINX_BIN -t 2>&1" "${DIAG_ROOT}/nginx_config_check.log"
cat "${DIAG_ROOT}/nginx_config_check.log"
# 导出全部配置文件
collect_file "cp -r $NGINX_CONF_DIR ${DIAG_ROOT}/nginx_conf_backup" "/dev/null"
echo "完整配置已备份至:${DIAG_ROOT}/nginx_conf_backup"
# 4.2 SSL证书有效期校验
echo -e "\n${GREEN}[4.2 SSL证书过期校验]${NC}"
cert_paths=$(grep -r "ssl_certificate " $NGINX_CONF_DIR 2>/dev/null | grep -v "#" | awk '{print $NF}' | tr -d ';' | sort -u)
> "${DIAG_ROOT}/ssl_cert_check.log"
for cert in $cert_paths; do
if [ -f "$cert" ]; then
end_date=$(openssl x509 -enddate -noout -in "$cert" 2>/dev/null | cut -d= -f2)
expiry_epoch=$(date -d "$end_date" +%s 2>/dev/null)
now_epoch=$(date +%s)
if [ "$now_epoch" -gt "$expiry_epoch" ]; then
echo -e "${RED}证书已过期:$cert 到期时间:$end_date${NC}" >> "${DIAG_ROOT}/ssl_cert_check.log"
else
echo "证书有效:$cert 到期时间:$end_date" >> "${DIAG_ROOT}/ssl_cert_check.log"
fi
else
echo -e "${YELLOW}证书文件缺失:$cert${NC}" >> "${DIAG_ROOT}/ssl_cert_check.log"
fi
done
cat "${DIAG_ROOT}/ssl_cert_check.log"
# 4.3 上游后端连接状态
echo -e "\n${GREEN}[4.3 上游后端TCP连接状态]${NC}"
collect_file "ss -tan state established | grep -E ':8080|:9000|:3000'" "${DIAG_ROOT}/upstream_conn.log"
cat "${DIAG_ROOT}/upstream_conn.log"
# 4.4 Nginx status监控页探测
echo -e "\n${GREEN}[4.4 Nginx运行状态页探测]${NC}"
collect_file "curl -s http://127.0.0.1/nginx_status 2>/dev/null" "${DIAG_ROOT}/nginx_status_page.log"
cat "${DIAG_ROOT}/nginx_status_page.log"
# ==========================================================================================
# ======================【单段独立执行】阶段五:辩证法-故障汇总、取证打包、决策指引 ======================
divider "阶段五 辩证法:故障汇总、完整取证归档、恢复决策提示"
# 自动收集所有高风险故障标记
issues=()
[ -z "$MASTER_PID" ] && issues+=("Nginx Master进程已消失")
grep -q "No space left on device" "${DIAG_ROOT}/nginx_error_full.log" 2>/dev/null && issues+=("磁盘分区空间耗尽")
grep -q "Too many open files" "${DIAG_ROOT}/nginx_error_full.log" 2>/dev/null && issues+=("进程文件句柄上限不足")
grep -q "cannot bind" "${DIAG_ROOT}/nginx_error_full.log" 2>/dev/null && issues+=("80/443端口被占用,绑定失败")
grep -q "oom" "${DIAG_ROOT}/dmesg_oom.log" && issues+=("系统OOM Killer杀死Nginx进程")
grep -q "failed" "${DIAG_ROOT}/nginx_config_check.log" && issues+=("Nginx配置存在语法错误")
grep -q "过期" "${DIAG_ROOT}/ssl_cert_check.log" && issues+=("SSL证书过期,HTTPS服务中断")
# 故障摘要输出
if [ ${#issues[@]} -eq 0 ]; then
echo -e "${GREEN}未检测到明确高优先级故障,执行深度排查:${NC}"
echo "1. 分析worker段错误coredump堆栈"
echo "2. 查看访问日志定位异常流量/恶意请求"
echo "3. strace追踪worker进程卡死场景"
echo "4. 核对近期配置、系统包升级变更记录"
else
echo -e "${RED}检测到关键故障项:${NC}"
for item in "${issues[@]}"; do
echo -e " ${RED}* $item${NC}"
done
fi
# 完整打包取证文件
tar -czf "${DIAG_ROOT}.tar.gz" -C /tmp "nginx_diag_${TIMESTAMP}" 2>/dev/null
echo -e "\n${GREEN}✅ 全部故障取证打包完成${NC}"
print_copy "完整故障压缩包路径" "${DIAG_ROOT}.tar.gz"
# 故障恢复决策提示
echo -e "\n${YELLOW}【辩证法恢复决策提示】${NC}"
echo "1. 磁盘满:清理nginx日志/临时文件,扩容分区后执行 systemctl reload nginx"
echo "2. OOM杀死:调大系统内存、降低其他进程占用、调整nginx worker进程数量"
echo "3. 配置错误:执行 nginx -t 定位错误行,修复后再reload,禁止强制重启"
echo "4. 文件句柄不足:修改/etc/security/limits.conf提升nofile,重启nginx生效"
echo "5. 端口占用:ss -tlnp 查找占用进程,停止冲突程序或修改nginx listen端口"
# 所有手工深度排查命令汇总
divider "手工深度排查一键命令库(预填充缓存变量)"
cat << EOF
# 1. 崩溃堆栈分析(coredump)
coredumpctl gdb ${MASTER_PID} -batch -ex "bt full"
# 2. 实时追踪worker进程阻塞
strace -p ${WORKER_PIDS} -f -tt -s 300
# 3. 完整查看nginx所有打开文件
lsof -p ${MASTER_PID}
# 4. 测试本地nginx访问连通性
curl -v http://127.0.0.1
curl -v https://127.0.0.1 -k
# 5. 查看systemd资源限制
systemctl show nginx | grep -E "LimitNOFILE|MemoryMax"
# 6. 分析访问日志错误请求
grep -E ' 500|502|503|499' ${NGINX_ACCESS_LOG} | tail -50
# 7. 临时开启nginx debug日志(仅调试用,流量大会爆盘)
echo "error_log /var/log/nginx/debug.log debug;" >> ${NGINX_CONF_DIR}/conf.d/debug.conf && nginx -t && systemctl reload nginx
EOF
echo -e "\n${GREEN}Nginx故障诊断脚本全部执行完成,缓存参数文件:${CACHE_FILE}${NC}"