《Nginx 反向代理 + HTTPS 入门实战:一个入口接管「网页 + 接口」》
关键词:Nginx 反向代理、proxy_pass、HTTPS、Certbot、Docker Compose
前两篇我们把服务跑起来了,也用数据卷保住了数据。但现在还有个别扭的地方:网页占 80 端口,接口占 8000 端口,浏览器访问接口要跨端口,地址栏里还挂着两个端口号。正式对外提供服务时,通常只留一个入口:Nginx 站在最前面,网页请求自己处理,接口请求悄悄转发给后端。这就是反向代理。
本文接着前面的 my-app 结构讲:web 是静态网页服务,api 是 8000 端口的接口服务,假设服务器上已经装好 Docker 和 Docker Compose。
一、目标架构:只暴露 80 和 443
改动前,浏览器要记两个地址:
- 网页:
http://服务器 IP/(web 的 80 端口) - 接口:
http://服务器 IP:8000/(api 的 8000 端口)
改动后,只留一个入口:
- 网页:
http://域名/→ 网关 Nginx → web 容器 - 接口:
http://域名/api/→ 网关 Nginx → api 容器
web 和 api 不再直接对外暴露端口,只在 Compose 内部网络里互相可见。端口少了,攻击面小了,前端也不用再处理跨域和端口问题。
二、加一个网关服务
在 docker-compose.yml 里加一个 gateway 服务,用官方 Nginx 镜像,挂载一份我们自己写的配置:
services:
gateway:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./gateway/nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- web
- api
web:
build: ./web
# 不再写 ports,改由网关转发
api:
build: ./api
# 同样不再对外暴露 8000
注意:web 和 api 去掉 ports 之后,宿主机上 curl localhost:8000 就不通了,这是正常的——以后统一走网关的 /api/。
三、写网关配置:两条 location 分流
./gateway/nginx.conf 的核心就两条规则:
server {
listen 80;
server_name example.com; # 换成你自己的域名
location / {
proxy_pass http://web:80;
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;
}
location /api/ {
proxy_pass http://api:8000/;
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;
}
}
两个细节最容易写错:
1. /api/ 后面那个斜杠不能省
proxy_pass http://api:8000/; 结尾带 /,表示把 /api/ 前缀剥掉再转发:浏览器请求 /api/users,后端收到的是 /users。如果写成 http://api:8000(不带斜杠),后端收到的还是完整的 /api/users,接口大概率 404。
2. 转发头要补齐
不写 X-Real-IP 和 X-Forwarded-For,后端看到的客户端 IP 永远是网关容器的内网 IP,日志和限流都会失真。X-Forwarded-Proto 则告诉后端原始请求是 http 还是 https,避免后端生成错误的跳转地址。
改完启动并验证:
docker compose up -d
curl http://localhost/ # 应返回网页
curl http://localhost/api/ # 应返回接口的响应
两条都通,代理就通了。
四、加上 HTTPS:先有域名,再谈证书
HTTPS 有一个硬性前提:需要一个指向这台服务器的域名。Let’s Encrypt 这类免费证书只发给域名,不给裸 IP 办法,所以如果现在手头只有 IP,先把上面反向代理的部分跑通,HTTPS 这一步等域名解析好再做,不用硬上。
域名就绪后,流程是三步:
1. 在宿主机上用 Certbot 申请证书
# 以 Debian/Ubuntu 为例
sudo apt install certbot
sudo mkdir -p /var/www/certbot
sudo certbot certonly --webroot -w /var/www/certbot \
-d example.com
证书会落在宿主机的 /etc/letsencrypt/,有效期 90 天,到期前记得续期。
2. 把证书挂载进网关,并加 443 配置
docker-compose.yml 里给 gateway 再加一行挂载(只读):
volumes:
- ./gateway/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
- /var/www/certbot:/var/www/certbot:ro # 供 80 端口的续期验证路径使用
配置里再加一个 443 的 server,并把 80 的请求跳转过去:
server {
listen 80;
server_name example.com;
# Certbot 续期要走 http 验证,先放行验证路径,其余跳转 https
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://web:80;
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;
}
location /api/ {
proxy_pass http://api:8000/;
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;
}
}
3. 续期后重载 Nginx,不要重启容器
证书续期只是换了文件,Nginx 要重新读一次配置才生效:
sudo certbot renew
docker compose exec gateway nginx -s reload
只重载、不重启,正在处理的请求不会被打断。
五、两个新手最容易踩的坑
1. 80 端口被旧服务占着,网关起不来
如果宿主机上之前还有一个直接跑的 Nginx(比如 10-24 那篇里手动装的那个),gateway 会因为 80 端口冲突启动失败。先停掉旧的,再起 Compose,或者把网关改到别的端口测试。
2. 浏览器缓存了旧的 http 跳转
从 http 切到 https 后,浏览器可能还缓存着旧地址或旧的 301 跳转,改配置后看着"没生效"。先用 curl -v 验证服务端行为,确认服务端没问题再清浏览器缓存排查。
结语
从单容器、Compose 编排、数据卷,到今天的反向代理和 HTTPS,这条线就真正像一个能对外提供服务的部署了:一个入口、内外分层、传输加密。下一篇可以往运维方向走一步:给这套服务加上日志查看和健康检查,出问题先知道去哪看,而不是凭感觉重启。
- 点赞
- 收藏
- 关注作者
评论(0)