追梦学习库

记一次 Emlog Docker 部署后全站 404 的排查与解决

追梦君 3 阅读
文章摘要

最近用 Docker 在服务器上部署了一个 Emlog 博客,配合 1Panel 面板和 OpenResty 反向代理。容器跑起来之后,访问首页直接返回一个 Apache 的 404 页面:

记一次 Emlog Docker 部署后全站 404 的排查与解决

分类:建站运维 · 标签:Emlog、Docker、1Panel、伪静态、Apache

最近用 Docker 在服务器上部署了一个 Emlog 博客,配合 1Panel 面板和 OpenResty 反向代理。容器跑起来之后,访问首页直接返回一个 Apache 的 404 页面:

Not Found
The requested URL was not found on this server.
Apache/2.4.54 (Debian) Server at www.你的域名 Port 80

奇怪的是,容器明明是运行状态,数据库也连上了,但就是打不开。折腾了一番才找到根因,这里把完整的排查思路和解决方法记录下来,给遇到同样问题的朋友一个参考。

一、我的部署环境

先交代一下环境,方便对照:

  • 博客程序:Emlog Pro(Docker 容器部署)
  • 镜像:emlog/emlog:pro-latest-php7.4-apache
  • 面板:1Panel
  • Web 服务:OpenResty(反向代理到容器)
  • 数据库:MySQL 容器
  • 端口映射:宿主机 8080 → 容器 80
  • 数据挂载:宿主机数据目录 → 容器 /app

请求链路是这样的:用户 → CDN → OpenResty → Docker Emlog 容器(Apache + PHP)→ MySQL。

二、排查过程

1. 先确认容器在不在

容器名可能因为重建而变化,所以第一步先看实际容器名:

docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}"

确认容器确实在运行,名字也记下来(下文用 emlog容器 代指)。

2. 检查伪静态模块

Emlog 的伪静态依赖 Apache 的 mod_rewrite,先确认它启用了:

docker exec emlog容器 apache2ctl -M | grep rewrite

输出 rewrite_module (shared),说明模块没问题。

3. 关键发现:DocumentRoot 指向了空目录

接着看 Apache 的站点配置:

docker exec emlog容器 apache2ctl -S 2>&1 | head -20

输出里有一行:

Main DocumentRoot: "/var/www/html"

再去看这个目录里有什么:

docker exec emlog容器 ls /var/www/html/

结果是空的,什么都没有。

那 Emlog 的文件到底在哪?全局搜一下 index.php:

docker exec emlog容器 find / -name "index.php" -not -path "*/usr/share/*" 2>/dev/null

输出里能看到 Emlog 的程序文件其实在 /app 目录下:

/app/index.php
/app/admin/index.php
/app/content/...

到这里问题就清楚了。

三、问题根因

这个 Emlog Docker 镜像有一个设计上的坑:

Apache 默认的站点配置 000-default.conf 里,DocumentRoot 指向的是 /var/www/html,但 Emlog 的实际程序文件放在了 /app 目录。/var/www/html 是一个空目录,所以无论访问什么路径,Apache 都找不到文件,统统返回 404。

雪上加霜的是,Apache 默认配置里所有 Directory 块的 AllowOverride 都是 None。这意味着即使你放了 .htaccess 伪静态文件,Apache 也根本不会去读它。

所以要让站点正常访问、伪静态生效,必须同时解决两个问题:

  1. 把 DocumentRoot 指向正确的 /app 目录
  2. 开启 AllowOverride All,让 .htaccess 生效

四、解决方案

第一步:准备 .htaccess 文件

Emlog 的数据目录是挂载到容器 /app 的,所以直接在宿主机的数据目录里创建 .htaccess 即可:

cat > /你的数据目录/.htaccess << "EOF"
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
EOF

第二步:准备自定义站点配置

在宿主机上写一个 Apache 站点配置文件,把 DocumentRoot 指向 /app 并开启 AllowOverride:

mkdir -p /opt/emlog/conf

cat > /opt/emlog/conf/emlog-site.conf << "EOF"
<VirtualHost *:80>
    DocumentRoot /app
    <Directory /app>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
EOF

第三步:用 docker run 重建容器

这里要特别注意:不要用 1Panel 的图形界面去改挂载,它会报一个莫名其妙的错误:

includes invalid characters for a local volume name, only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed

这是 1Panel 把挂载路径当成了 Docker 的命名卷(named volume)而不是 bind mount。直接用 docker run 手动建容器最稳妥:

docker run -d \
  --name emlog容器 \
  --restart always \
  -p 8080:80 \
  -v /你的数据目录:/app \
  -v /opt/emlog/conf/emlog-site.conf:/etc/apache2/sites-enabled/000-default.conf \
  emlog/emlog:pro-latest-php7.4-apache

注意:.htaccess 要放在 /app 目录下(也就是宿主机的数据目录里),不要挂载到 /var/www/html/.htaccess,那个目录是空的,挂了也没用。

第四步:配置 OpenResty 伪静态

在 1Panel 里找到对应站点 → 站点设置 → 伪静态,填入:

if (!-e $request_filename) {
    rewrite ^/(.*)$ /index.php last;
}

如果面板的伪静态入口对反向代理站点不生效,就手动编辑 OpenResty 配置,在 location / 之前加入 rewrite 规则:

server {
    listen 80;
    server_name 你的域名 www.你的域名;

    if (!-e $request_filename) {
        rewrite ^/(.*)$ /index.php last;
    }

    location / {
        proxy_pass http://你的服务器IP:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

改完重载 OpenResty:

docker exec openresty容器 nginx -s reload

第五步:Emlog 后台设置

  1. 登录 Emlog 后台
  2. 进入「设置 → 链接结构」
  3. 选择「文件格式」(形如 /post-1.html),SEO 友好
  4. 确认「站点地址」填的是你的正式域名(https://你的域名),而不是 http://服务器IP:8080,否则生成的链接会带 IP 和端口
  5. 保存

五、一个容易遇到的小插曲:.htaccess 不可写

后台保存链接结构时,可能会提示「根目录下的.htaccess不可写」。

原因是 .htaccess 是用 root 创建的,而容器里 Apache 的运行用户是 www-data(UID 33),没有写权限。改一下文件归属就行:

chown 33:33 /你的数据目录/.htaccess

还不行就直接放开写权限:

chmod 666 /你的数据目录/.htaccess

其实我们手动放的规则已经是通用的了,Emlog 保存时只是用自带规则覆盖一遍,效果一样。这一步主要是让后台保存不报错,同时把链接结构写进数据库。

六、让配置持久化

上面这套配置都是放在宿主机、通过挂载进容器的,所以容器重建也不会丢。需要持久化的就两个文件:

宿主机路径 容器路径 说明
/你的数据目录/ /app Emlog 全部数据(含 .htaccess)
/opt/emlog/conf/emlog-site.conf /etc/apache2/sites-enabled/000-default.conf Apache 站点配置

以后无论容器怎么重建,只要这两条挂载在,伪静态就一直是好的。

七、踩坑总结

把这次踩的坑集中列一下,方便快速对照:

  1. DocumentRoot 陷阱:Emlog 镜像的 Apache 默认指向空的 /var/www/html,真实文件在 /app,必须用自定义站点配置覆盖。
  2. AllowOverride 默认 None:即使放了 .htaccess 也不生效,必须显式开启 AllowOverride All
  3. 别用 1Panel UI 改挂载:会报 "invalid characters for a local volume name",直接用 docker run
  4. proxy_pass 别用 127.0.0.1:容器内的 127.0.0.1 指向容器自己,要用宿主机内网 IP。
  5. 站点地址要填正式域名:填 IP:端口会导致生成的链接全是 IP。
  6. .htaccess 写权限:root 创建的文件 www-data 写不了,记得 chown 33:33
  7. 改完 OpenResty 要重载:否则配置不生效。

八、常用排查命令备忘

最后留一份排查命令清单,下次遇到问题可以按顺序敲:

# 确认容器名
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}"

# 检查 mod_rewrite
docker exec emlog容器 apache2ctl -M | grep rewrite

# 查看 DocumentRoot 指向
docker exec emlog容器 apache2ctl -S 2>&1 | head -20

# 检查 .htaccess(注意是 /app 不是 /var/www/html)
docker exec emlog容器 cat /app/.htaccess

# 检查站点配置
docker exec emlog容器 cat /etc/apache2/sites-enabled/000-default.conf

# 确认 Emlog 文件存在
docker exec emlog容器 ls /app/index.php

# 查看挂载情况
docker inspect emlog容器 --format '{{json .Mounts}}' | python3 -m json.tool

# 看 Apache 错误日志
docker exec emlog容器 tail -20 /var/log/apache2/error.log

结语

这次问题的核心,就是 Emlog Docker 镜像的 DocumentRoot 指向和实际文件位置不一致。看似一个 404,背后其实是「DocumentRoot 指错 + AllowOverride 没开」两个问题叠加。把这个坑记下来,希望能帮到同样在用 Docker 部署 Emlog 的朋友。

如果你也遇到了类似的 404,不妨先 apache2ctl -S 看看 DocumentRoot 到底指向哪里,多半问题就出在那。

分享到:

发表评论

请先 登录 再评论