Docker 项目实战:Nginx 返回 403,浏览器无法显示 HTML 页面——完整排障过程
本章记录一次真实的 Docker Web 项目故障排查过程。
项目环境:
- Ubuntu
- Docker
- Docker Compose
- Nginx
- Tornado
- MySQL
故障现象:
浏览器访问
http://127.0.0.1无法显示自己的 HTML 页面,Nginx 最终返回 403 Forbidden。
一、项目架构
我们的项目大致是:
用户浏览器
│
用户浏览器
│
│ HTTP :80
↓
┌─────────────┐
│ Nginx │
│ :80 │
└──────┬──────┘
│
┌───────────┴──────────┐
│ │
↓ ↓
静态 HTML /api/
│ │
│ ↓
│ ┌───────────┐
│ │ Tornado │
│ │ :8888 │
│ └─────┬─────┘
│ │
│ ↓
│ ┌───────────┐
│ │ MySQL │
│ │ :3306 │
│ └───────────┘
我们希望最终实现:
http://127.0.0.1 ↓ Nginx ↓ index.html ↓ 显示自己的项目首页
但是
http://127.0.0.1/api/
↓
Nginx
↓
Tornado :8888
↓
返回 API 数据
http://127.0.0.1
↓
Nginx
↓
index.html
↓
显示自己的项目首页
二、最开始的问题
之前访问:
curl http://127.0.0.1
得到:
{"status": "success", "message": "Docker Tornado Running"}
一开始我们以为:
为什么不是 HTML?
于是开始排查。
三、第一步:确认端口是否正常
首先使用
ss -lntp
看到:
LISTEN 0 4096 0.0.0.0:80
LISTEN 0 4096 0.0.0.0:8888
LISTEN 0 4096 0.0.0.0:3306
说明:
80 → Nginx
8888 → Tornado
3306 → MySQL
端口都在监听。
所以暂时排除:
服务没有启动
端口没有监听
四、第二步:直接访问 Tornado
执行:
curl -i http://127.0.0.1:8888
得到:
HTTP/1.1 200 OK
Server: TornadoServer/6.5.7
Content-Type: application/json; charset=UTF-8
{"status": "success", "message": "Docker Tornado Running"}
这说明:
Tornado 本身正常。
而且返回的是:
application/json
不是:
text/html
所以我们进一步确认:
Tornado 是 API 服务,不负责直接给我们显示 HTML 首页。
五、第三步:访问 Nginx
执行:
curl -i http://127.0.0.1
当时得到:
HTTP/1.1 200 OK
Server: nginx/1.31.3
Content-Type: application/json
返回:
{"status": "success", "message": "Docker Tornado Running"}
这说明:
浏览器
↓
Nginx :80
↓
Tornado :8888
已经打通了。
也就是说:
Nginx → Tornado 的反向代理是工作的。
六、第四步:检查 Nginx 配置
执行:
docker exec nginx-web nginx -T
发现:
http {
server {
listen 80;
location / {
proxy_pass http://app:8888;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
这里就发现了一个非常重要的问题。
当前配置:
location / {
proxy_pass http://app:8888;
}
意味着:
所有
/请求都转发给 Tornado。
所以:
http://127.0.0.1/
并不会寻找:
index.html
而是:
Nginx
↓
Tornado
↓
JSON
因此浏览器看到 JSON 是完全正常的。
七、第五步:我们希望实现什么?
我们真正需要的是:
/ --- HTML
而:
/api/----Tornado
因此 Nginx 应该改成:
location / {
root /usr/share/nginx/html;
index index.html;
}
location /api/ {
proxy_pass http://app:8888;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
也就是说:/ → 静态 HTML /api/ → Tornado
八、第六步:发现 HTML 文件问题
修改之后,访问:
curl -I http://127.0.0.1
结果:
HTTP/1.1 403 Forbidden
Server: nginx/1.31.3
这时候非常关键。
因为现在:
Nginx 正常
配置正常
端口正常
但是:
403 Forbidden
说明:
Nginx 收到了请求,但是没有权限/没有合适的资源可以返回。
于是继续检查:
docker exec nginx-web cat /usr/share/nginx/html/index.html
一开始看到的是:
<title>Welcome to nginx!</title>
这是:
Nginx 官方镜像自带的默认 HTML。
九、第七步:检查 Docker Compose
继续查看:
grep -A15 -B5 "nginx:" docker-compose.yml
发现:
nginx:
image: nginx:latest
container_name: nginx-web
restart: always
ports:- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
这里出现了真正的问题。
我们只挂载了:
nginx.conf
但是没有挂载:
HTML
也就是说:
宿主机挂载./nginx/nginx.conf
容器挂载-----/etc/nginx/nginx.conf
但是:
宿主机挂载到了./nginx/html/index.html
容器和/usr/share/nginx/html/index.html
两者没有关系。
十、Docker Volume 到底是什么?
这里一定要理解。
例如:
volumes:
- ./nginx/html:/usr/share/nginx/html
意思是:
宿主机 容器
./nginx/html
│ volume
↓
/usr/share/nginx/html
也就是说:
宿主机目录直接映射到容器目录。
因此:
vim nginx/html/index.html
修改宿主机文件之后:
容器里的
/usr/share/nginx/html/index.html
也会立即看到这个文件。
十一、第八步:添加 HTML Volume
修改:
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/html:/usr/share/nginx/html
完整 Nginx:
nginx:
image: nginx:latest
container_name: nginx-web
restart: always
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/html:/usr/share/nginx/html
depends_on:
- app
十二、第九步:创建 HTML 目录
执行:
mkdir -p nginx/html
然后:
nano nginx/html/index.html
写入我们的网页。
例如:
location / {
root /usr/share/nginx/html;
index index.html;
}
location /api/ {
proxy_pass http://app:8888;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
十三、第十步:检查宿主机文件
执行:
ls -l nginx/html/
应该看到:
index.html
然后:
head nginx/html/index.html
确认:
<!DOCTYPE html>
<html lang="zh-CN">
十四、第十一步:重新创建 Nginx
因为修改了 Compose 的挂载关系,所以执行:
docker compose up -d --force-recreate nginx
然后:
docker ps
确认:
nginx-web Up
十五、第十二步:进入容器检查
这是非常重要的一步。
执行:
docker exec nginx-web ls -l /usr/share/nginx/html/
应该看到:
index.html
然后:
docker exec nginx-web head -10 /usr/share/nginx/html/index.html
应该看到:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
这就证明:
宿主机 HTML
↓
Docker Volume
↓
Nginx 容器
↓
/usr/share/nginx/html/index.html
整个链路已经打通。
十六、第十三步:检查 Nginx 配置
执行:
docker exec nginx-web nginx -t
正常:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
说明:
Nginx 配置文件语法没有问题。
十七、第十四步:测试 HTTP
执行:
curl -I http://127.0.0.1
应该得到:
HTTP/1.1 200 OK
Content-Type: text/html
注意这里:
之前是:
Content-Type: application/json
现在应该变成:
Content-Type: text/html
这说明:
Nginx 已经开始返回 HTML。
十八第十五步:浏览器访问
浏览器输入:
http://127.0.0.1
就应该看到我们自己的网页。
更多推荐
所有评论(0)