结论: 服务器重启后网站打不开,八成不是"网站坏了",而是"该起来的没起来"——服务没设开机自启、Docker 容器没配 restart policy、/etc/fstab 挂载失败、云主机 IP 变化,或 /tmp 被清空导致 session 与缓存丢失。按"查自启→查容器→查挂载→查网络→查缓存→查证书时间"六步排查,五分钟内即可定位。
引言:为什么"重启后打不开"要单独讨论
服务器重启后网站打不开,是一类与"平时打不开"完全不同的故障。后者通常是流量、攻击、程序 bug、磁盘写满;而前者的特征极其鲜明——重启前一切正常,重启后集体失效。这意味着业务代码大概率没变,出问题的是"启动过程本身":某个进程没有跟着系统一起起来、某个依赖比它起得慢、某个目录被清空了、某个 IP 换了。
对重启类故障,最有效的思路不是从浏览器往回查,而是从 systemd 的启动时序往下查。Linux 启动是一条串行链路,任何一个环节失败都会让最终对外服务不可用,而 journalctl -b 会把本次启动(boot)的全部错误原样保留下来。截至 2026 年,主流发行版(Ubuntu 24.04 LTS、Debian 12/13、Rocky/AlmaLinux 9、openEuler 24.03)均已默认使用 systemd,下文命令以 systemd 为准;极老的 CentOS 6 需替换为 service/chkconfig。
动手前先做一件事:在云厂商控制台给系统盘打一份快照,尤其是你准备改动 /etc/fstab 或 grub 配置时。重启类故障往往伴随"越改越坏",快照是唯一的后悔药。
第一步:先确认"重启"这件事本身
结论: 先用 last reboot、who -b、uptime -s 确认机器是否真的重启过、重启于何时,再用 journalctl -b 只看本次启动的日志,避免在几十天的历史日志里无效翻找。
很多"我以为它重启了"其实是进程崩溃或 OOM 被杀,而"我以为没重启"其实是云厂商做了宿主机热迁移或系统自动更新。确认事实后再动手,能省掉一半无用功。
# 1. 确认重启历史与本次启动时刻
last -n 10 reboot # 最近 10 次重启记录(含 reboot / shutdown / crash)
who -b # 本次系统启动的时刻
uptime -s # 启动时间戳,与 who -b 一致
uptime # 已运行时长,判断是否刚刚启动
# 2. 只看"本次启动"的日志:-b 就是 boot,这是重启排查的核心参数
journalctl -b -p err --no-pager | head -60
journalctl -b -u nginx.service --no-pager
journalctl -b -u docker.service --no-pager
# 3. 上一次启动的日志用 -b -1,用于对比"重启前是否正常"
journalctl -b -1 -p err --no-pager | head -40
# 4. 看启动耗时与最慢的服务,判断是否有服务卡住
systemd-analyze
systemd-analyze blame | head -20
systemd-analyze critical-chain nginx.servicelast reboot 的输出里如果某一行显示为 crash 而非 reboot,说明不是正常重启,而是内核崩溃或掉电,此时应转向宕机原因排查,而不是本文的自启问题。
第二步:服务没设开机自启——重启后挂掉的第一号原因
结论: 用 systemctl is-enabled <服务> 检查,返回 disabled 就是没设自启;用 systemctl enable --now <服务> 一次性完成"设置自启 + 立刻启动",无需再单独 start。
这是重启后网站打不开最常见的原因,没有之一。很多服务是手工 systemctl start 或 nohup ./xxx & 拉起来的,从未写入 systemd 的启动链,系统一重启自然就没了。
# 检查关键服务是否设置了开机自启
systemctl is-enabled nginx php-fpm mysqld redis docker
# 一次性补齐:--now 表示同时立刻启动
systemctl enable --now nginx php-fpm mysqld redis docker
# 扫雷:列出所有"应该是 enabled 但实际 disabled"的服务
systemctl list-unit-files --type=service --state=disabled \
| grep -E 'nginx|httpd|php|mysql|maria|redis|memcach|docker|tomcat|supervisor|cron'
# 若返回 masked(被屏蔽),先解屏蔽再启用
systemctl unmask nginx && systemctl enable --now nginx
# 检查服务当前真实状态(active/inactive/failed)
systemctl status nginx --no-pager -l
systemctl --failed --no-pager # 列出所有启动失败的服务systemctl is-enabled 有四种返回值,含义必须分清:
| 返回值 | 含义 | 重启后会自启吗 | 处置 |
|---|---|---|---|
enabled | 已在 multi-user.target 中建立符号链接 | 会 | 正常,无需处理 |
disabled | 未建立链接 | 不会 | systemctl enable --now |
static | 无 [Install] 段,只能被别的服务依赖拉起 | 视依赖方而定 | 通常无需手工启用;若需自启请确认依赖方已 enabled |
masked | 被屏蔽到 /dev/null | 不会,且连手工 start 都被拒 | systemctl unmask 后再 enable |
需要特别提醒:用 nohup、screen、& 启动的进程一定不会随系统自启。正确做法是给它写一个 systemd unit(见第四步的 unit 示例),或至少把启动命令写进 /etc/rc.local 并确保 rc-local.service 已启用。
第三步:Docker 容器没配 restart policy
结论: Docker 容器默认不会随系统重启自动拉起,必须显式配置 restart policy;已存在的容器用 docker update --restart=unless-stopped <容器> 补上,新建容器在 docker run 或 compose 中声明。
即便 docker.service 本身是自启的,容器也不一定会起来——这是两件事。Docker 守护进程启动后,只会拉起那些配置了 always 或 unless-stopped 策略的容器。
# 查看所有容器(含已退出的)与端口
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
# 逐个查看重启策略:no 表示不会自动拉起
docker inspect -f '{{.Name}} => {{.HostConfig.RestartPolicy.Name}}' $(docker ps -aq)
# 给已存在的容器补策略(无需重建容器)
docker update --restart=unless-stopped nginx mysql redis
# 新建时直接指定
docker run -d --name web --restart=unless-stopped -p 80:80 nginx:1.26
# 只看已退出容器的日志,快速定位启动失败原因
docker ps -a --filter 'status=exited' --format '{{.Names}}'
docker logs --tail 100 <容器名>在 compose 文件中推荐用 unless-stopped 而非 always:前者会保留"我手工把它停了"的意图,后者则会在 Docker 重启后把你故意停掉的容器也拉起来。
# docker-compose.yml 片段
services:
web:
image: nginx:1.26
restart: unless-stopped
ports:
- "80:80"
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1/"]
interval: 10s
retries: 3
app:
image: myapp:1.0
restart: unless-stopped
depends_on:
mysql:
condition: service_healthy # 等数据库真正就绪,而非仅仅"容器已启动"
mysql:
image: mysql:8.4
restart: unless-stopped
volumes:
- /data/mysql:/var/lib/mysql还要注意 docker-compose up -d 本身不是开机自启的:容器策略只解决"Docker 起来后拉起容器",若你用 compose 管理,仍需一个 systemd unit 在开机时执行 docker compose up -d,或直接改用 docker compose 的 restart: unless-stopped + 手工补一次启动。
第四步:/etc/fstab 挂载失败与启动顺序依赖
结论: 用 mount -a 验证 /etc/fstab 能否全部挂载,报错的那一行就是病根;给非系统盘加 nofail,x-systemd.device-timeout=10s,避免一块数据盘把整机拖进紧急模式。
/data 没挂上,网站程序读不到上传目录、数据库读不到数据目录,表现出来就是"网站打不开"或"能打开但报错"。而如果 fstab 条目硬失败,systemd 会直接进入 emergency mode,连 SSH 都进不去。
# 验证 fstab 全部条目(未挂载的会被挂上,报错行即定位)
mount -a
# 只做校验不真挂载,适合生产环境
findmnt --verify --tab-file /etc/fstab
# 查看某个挂载单元的失败原因
systemctl status data.mount --no-pager
systemctl --failed --type=mount --no-pager
journalctl -b -u data.mount --no-pager
# 核对 UUID 是否与实际一致(换盘/重装后最易出错)
blkid | grep -E 'ext4|xfs'
grep -v '^#' /etc/fstab | grep -v '^$'推荐的数据盘 fstab 写法(以 ext4 为例):
# /etc/fstab 推荐写法:nofail + 超时,避免拖垮整机启动
UUID=3f1a9c2e-xxxx-xxxx-xxxx-xxxxxxxxxxxx /data ext4 defaults,nofail,x-systemd.device-timeout=10s 0 2另一类隐蔽问题是启动顺序依赖:Nginx 先于 PHP-FPM 启动,导致 upstream 的 unix socket 还不存在;或应用先于数据库启动,连接池初始化失败后进程直接退出。systemd 默认的 After= 只保证"启动命令发出"的顺序,不保证"服务可用"。解决方式是显式声明依赖 + 应用层重试:
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network-online.target mysqld.service redis.service
Wants=network-online.target
Requires=mysqld.service
[Service]
Type=simple
ExecStart=/opt/myapp/bin/start.sh
Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=0
[Install]
WantedBy=multi-user.targetRestart=on-failure 配合 RestartSec=5s 能覆盖绝大多数"依赖还没就绪我就先起来了"的场景:失败后每 5 秒重试,等数据库就绪后自然成功。
第五步:IP 变了、端口没起来、安全组放通了吗
结论: 云主机重启后内网 IP 可能变化、弹性 IP 可能未自动回绑;先用 ip -br addr 对比 IP,再用 ss -lntp 确认 80/443 真的在监听,最后用 curl 在本机回环自检,可把问题范围从"整条链路"缩小到"本机 / 网络 / 上游"三者之一。
# 对比重启前后的网卡地址
ip -br addr show
ip route show
# 确认 Web 端口真的在监听(进程存在 ≠ 端口在听)
ss -lntp | grep -E ':80|:443'
ss -lntup '( sport = :80 or sport = :443 )'
# 本机自检:绕过 DNS 与外网,验证服务本身是否正常
curl -I -H 'Host: example.com' http://127.0.0.1/
curl -vk https://127.0.0.1/ --resolve example.com:443:127.0.0.1
# 本机防火墙是否放通
firewall-cmd --list-ports
iptables -S | grep -E '80|443'
# 应用配置里写死的内网 IP 是否已失效
grep -rn '10\.0\.\|172\.1[6-9]\.\|192\.168\.' /etc/nginx/conf.d/ /opt/myapp/config/ 2>/dev/null判断逻辑很清晰:curl 127.0.0.1 通但外网不通 → 问题在防火墙/安全组/弹性 IP/ DNS;curl 127.0.0.1 也不通 → 问题在服务本身,回到第二、三步。云服务器还要记得检查控制台的安全组规则,尤其是"重启后实例被更换了宿主机、内网 IP 段变化"而安全组按 IP 授权的情况。
第六步:/tmp 被清空、session 与缓存丢失
结论: /tmp 与 /var/run 在很多系统上是 tmpfs 或由 systemd-tmpfiles-clean.timer 定期清理,重启后必然为空;依赖它们存放 session、PID 文件、socket 或缓存的程序会集体失效。
这是最容易被忽略的一类。重启前网站跑得好好的,重启后用户全部掉登录、后台报"Session 目录不可写"、缓存穿透把数据库打挂,都源于此。
# /tmp 是否为 tmpfs(内存盘,重启即清空)
findmnt -T /tmp
df -h /tmp /var/run /dev/shm
# systemd 的临时目录清理规则与定时清理任务
systemctl status systemd-tmpfiles-clean.timer --no-pager
cat /usr/lib/tmpfiles.d/tmp.conf
# PHP session 目录丢失会导致登录态全掉
php -i 2>/dev/null | grep -i 'session.save_path'
ls -ld /var/lib/php/session
systemd-tmpfiles --create # 按 tmpfiles.d 规则重建缺失目录
# 确认 PID 文件/socket 目录存在(Nginx、PHP-FPM 常见)
ls -ld /run/nginx /run/php-fpm 2>/dev/null
systemctl status php-fpm --no-pager | tail -20缓存侧的风险更隐蔽:Redis/Memcached 重启后是空的,所有请求瞬间打到数据库,可能导致数据库被打爆而表现为"网站打不开"。应对方式是重启后先做缓存预热(把热点 key 批量写回),并在应用层加限流或熔断。
企业QQ咨询




