保哥笔记

宝塔面板自动升级失败网站列表消失的紧急修复与事后复盘记录

那天我在宝塔后台"手贱"点了一下"立即更新",转圈圈半分钟没反应,刷新之后整个"网站"页签里一片空白——昨天还在那里的二十多个站点,全部不见了。说实话,那一瞬间我整个人是凉的,因为这台机器跑着客户的几个生产站,万一数据真的丢了,赔多少钱我自己心里没谱。

好在最后是有惊无险,宝塔本身只是面板的元数据被升级脚本搞坏了,站点的代码和数据库都还在磁盘上。这篇文章把我那天的处理过程、用到的命令、以及事后做的一系列预防措施全部记录下来,给以后可能踩同样坑的朋友做个参考。

故障表现:到底是什么坏了

先把症状描述清楚,避免误诊。我那次的现象是:

  1. 宝塔后台依然能正常登录,URL 和端口没变;
  2. 顶部菜单 "网站"、"数据库"、"FTP" 都能点进去,但列表完全空白
  3. 点击"添加站点"按钮没有反应,或者弹出 500;
  4. 服务器本身的 nginx、MySQL、PHP-FPM 依然在跑,已经存在的网站可以正常访问;
  5. SSH 进去 ls /www/wwwroot/ 还能看到所有站点的目录和文件。

这一组症状的关键判断是:站点没死,只是宝塔面板自己"看不见"它们了。这意味着我不需要恢复网站文件,只需要修复面板。明白这一点之后,整个心态都稳了下来。

紧急修复:一行命令救回面板

后来在宝塔的官方论坛里翻到了这个升级修复脚本:

curl https://download.bt.cn/install/update_panel.sh | bash

执行步骤是:

  1. 用 SSH 工具(我用的 Termius,Windows 用户也可以用 MobaXterm 或者 PuTTY)登录服务器;
  2. 切到 root 或者用 sudo -i 提权;
  3. 把上面那条命令粘贴进去执行;
  4. 等脚本跑完——通常 30 秒到 2 分钟之间,依赖你的网络速度;
  5. 再次刷新宝塔后台,网站列表全部回来了。

这个脚本的本质是把面板程序重新拉一份覆盖安装,但保留 /www/server/panel/data 目录里的数据库(也就是宝塔自己的 SQLite 数据库 default.db)。所以站点配置、用户、定时任务这些都不会丢。

如果你担心 curl | bash 不够安全,可以分两步执行,先把脚本下下来看一眼再跑:

wget https://download.bt.cn/install/update_panel.sh
less update_panel.sh
bash update_panel.sh

实际我看了一下脚本内容,主要逻辑就是停止 BT-Panel 服务、下载新版面板压缩包、覆盖部分目录、跳过 data 与 vhost 目录、最后重启服务。读懂之后再跑,心里就踏实多了。

修复之后必须立刻做的三件事

网站列表回来不代表万事大吉,我那天后面又花了大概一个小时做收尾,确保不会有隐患。

第一件:核对站点数量

打开"网站"页面,逐个核对站点数量、域名绑定、根目录路径。我有过一次类似经历,少了一个三级域名子站,是因为升级前我手动改过 vhost 配置,覆盖之后就没了。所以一定要拿之前的截图或备份对照。

第二件:检查 Nginx/Apache 配置

nginx -t
systemctl status nginx

看一下配置语法是否有效、服务是否在跑。我那次脚本没动 vhost,但有些更激进的修复脚本会重写 conf 模板。

第三件:备份 default.db

cp /www/server/panel/data/default.db \
   /root/bt_default.db.$(date +%Y%m%d).bak

宝塔所有的元数据(站点列表、数据库账号、定时任务、计划任务、防火墙规则)都在这个 SQLite 文件里。出问题第一时间备份它,将来再修复就有了精确还原点。

为什么会出现这种升级失败

复盘之后,我大致归纳了几种常见原因,每一种我都踩过至少一次。

原因 1:面板版本和操作系统不兼容。我那台机器是 CentOS 7,宝塔后来某个版本要求 Python 3.7+,但 CentOS 7 自带 Python 2.7,升级脚本在切换 Python 解释器的时候出现了竞态,导致面板自身重启失败。

原因 2:磁盘满了。升级会下载临时压缩包并解压。如果 /www 或者 /tmp 已经接近 100%,解压一半就失败,然后旧文件被替换、新文件没写完整,面板就处于半残状态。可以提前用 df -h 看一下:

df -h

原因 3:网络抖动。宝塔升级要从 download.bt.cn 拉文件,如果是国外服务器或者运营商出口不稳定,下载到一半被中断,结果同样是文件损坏。

原因 4:手工修改过面板源码。我之前为了去掉某个推送弹窗,手动改过 /www/server/panel/BTPanel/static/ 里的文件。升级覆盖时会和我的改动冲突。

搞清楚原因之后,再做预防就有方向了。

我现在的预防套路

这次之后我给所有自己维护的服务器都加了一套防御措施,运维成本不高,但能避免大部分意外。

关闭面板自动升级

登录宝塔 → 面板设置 → 面板自动升级 → 关闭。我宁愿手动选时间窗口去升,也不要在凌晨自动升级把自己惊醒。

升级前先快照

云服务器(阿里云、腾讯云、华为云)都支持磁盘快照。我现在的固定动作是:升级前 5 分钟做一次完整快照,升级成功 24 小时后再删掉。万一翻车,直接快照回滚,损失最多就是几块钱快照费。

用 rsync 备份关键目录

我写了一个简单的备份脚本,放在 crontab 里每天跑一次:

#!/bin/bash
DATE=$(date +%Y%m%d)
DEST=/data/backup/$DATE
mkdir -p $DEST
rsync -a /www/server/panel/data/ $DEST/panel_data/
rsync -a /www/server/panel/vhost/ $DEST/vhost/
find /data/backup -type d -mtime +7 -exec rm -rf {} +

保留最近 7 天的备份,旧的自动清理。这样就算面板彻底崩了,我手动用旧的 default.db 也能在 5 分钟内还原所有站点元数据。

监控面板可用性

我用 UptimeRobot 给宝塔后台地址也加了一个监控(只看是否能返回 200)。一旦升级失败导致面板挂了,手机会立刻收到推送,比第二天用户来报问题强一万倍。

如果一行命令也救不回来

极少数情况下,update_panel.sh 也修不好——比如 default.db 文件损坏、Python 环境彻底报废。这时候有两个升级路径。

路径 A:重装面板,导回 default.db

先把 default.db 备份出来:

cp /www/server/panel/data/default.db /root/bt_rescue.db

然后执行官方完全重装:

curl -sSO https://download.bt.cn/install/install_panel.sh
bash install_panel.sh

重装完之后,把刚才备份的 default.db 覆盖回去,重启面板:

cp /root/bt_rescue.db /www/server/panel/data/default.db
btpython -c 'import os; os.system("systemctl restart bt")'

大部分情况下站点列表会原样回来。

路径 B:脱离面板纯手工接管

这是最坏情况。如果 default.db 也救不回来,那就不靠面板了——你网站的代码在 /www/wwwroot/、配置在 /www/server/panel/vhost/nginx/,数据库在 MySQL 里。你完全可以用纯命令行接管:

ls /www/wwwroot/
cat /www/server/panel/vhost/nginx/your-site.conf
mysql -uroot -p

至少站点本身可以保持在线,等你慢慢重建面板。

FAQ

Q1:执行 update_panel.sh 会不会丢站点?

按官方设计不会。脚本会跳过 data、vhost、wwwroot 三个目录。但任何"理论上不会"都不能替代"实际有备份"。跑之前先做磁盘快照或者 rsync 备份是必须的。

Q2:脚本卡在某一步不动了,要不要 Ctrl+C?

看时间。如果是"Downloading..."卡住超过 5 分钟,大概率是网络问题,可以 Ctrl+C 后换时间再试,或者换成镜像源。如果是"Stopping panel..."或者"Restarting..."卡住,建议再等 2 分钟,强行中断容易让面板进入半启动状态,更难恢复。

Q3:升级失败之后宝塔后台干脆打不开了,怎么办?

SSH 登录服务器,先看面板进程:

ps aux | grep BT-Panel

如果没进程,手动启动:

systemctl start bt
# 或者老版本
/etc/init.d/bt start

再不行就跑前面提到的重装脚本。

Q4:能不能彻底关闭升级提醒?

面板设置里有一个"关闭面板自动升级"开关,关掉它就不会主动升级。但顶部偶尔还会弹"有新版本"提示,这个目前没办法完全去除(除非改源码,下次升级又会被覆盖)。我的建议是接受它的存在,看到提示只在你方便的时候手动升。

一些容易被忽视的细节

这次故障之后,我又陆续在几台不同环境的服务器上跑过 update_panel.sh,遇到一些边角情况,分享一下避免大家也踩。

细节 1:权限问题。如果你不是 root 直接登录,而是用普通用户 sudo 提权,要确认 umask 没有被修改成奇怪的值(比如 077)。我见过一台机器因为 umask 太严格,脚本下载下来的临时文件其他模块读不了,导致升级"成功"了但面板进程起不来。一行 umask 022 解决。

细节 2:python 版本。新版宝塔强制依赖 Python 3.7+,CentOS 7 默认 Python 2.7。脚本会自动安装 Python 3,但有时候系统已经被你装过 Python 3.6 了,会出现版本冲突。我建议在升级前先 which python3 && python3 --version 确认版本,太旧就先手动升 Python 再升面板。

细节 3:SELinux。CentOS/AlmaLinux 系列默认开启 SELinux,宝塔的某些覆盖操作会被 SELinux 拦截但不报错,最后表现为"文件已替换但服务起不来"。临时排查可以执行 setenforce 0 把 SELinux 切到 permissive,确认问题之后再决定是关闭还是写策略放行。

细节 4:宝塔企业版 vs 社区版。两个版本的更新源不同,社区版的 update_panel.sh 不能用来修复企业版面板,反之亦然。下载脚本之前看清楚自己装的是哪个版本:cat /www/server/panel/class/common.py | grep -i version

顺便记录我的服务器升级 SOP

经过这几次教训,我现在凡是涉及生产环境的升级,都会按照下面这个 SOP 走。把它贴出来,朋友也可以直接抄:

  1. 通知:在客户群提前 30 分钟发预警,约定升级窗口。
  2. 快照:到云服务商控制台手动触发一次磁盘快照,确认完成。
  3. 备份元数据:本地多备一份 default.db、vhost、nginx 配置、/etc/my.cnf
  4. 导出网站清单:用 SQL 把站点列表、域名、目录、PHP 版本导出成 CSV,方便核对。
  5. 执行升级:在 SSH 终端用 screentmux 包裹长任务,避免网络断开导致脚本中断。
  6. 冒烟测试:升级完成后立刻访问几个代表性站点,确认 200。
  7. 清理临时文件rm -f /tmp/update_panel.sh.* 等遗留产物。
  8. 记录变更日志:把升级前后版本号、命令、耗时写到自己的运维笔记里。
  9. 保留快照 24 小时:确认无问题再删除快照。
  10. 关闭防火墙临时白名单:升级期间如果开过 IP 白名单,记得关。

这套流程看起来繁琐,但实际跑下来一次最多 15 分钟,能避免 90% 的人为事故。

小结

宝塔升级失败这种事在我看来属于"低概率但高影响"故障。低概率指的是绝大多数升级都顺利完成;高影响指的是一旦失败,新人很容易手忙脚乱去乱删乱改,反而把站点真的弄丢。记住三件事就够了:第一,站点文件不在 default.db 里,只要磁盘还在数据就还在;第二,官方修复脚本是首选,能解决 90% 的情况;第三,永远先快照、再升级,把一次性的运气换成可重复的安全。