使用Cloudflare和Nginx配置子域名反向代理与HTTPS访问的一些心得记录
简介
我自己有一个域名,也就是本站.然后我还有一台有公网 IP 的服务器,上面运行了各种服务,有一些是可以通过网页访问的.但是我暂时都没有给这些网页服务绑定一个域名.为了方便使用,我可以用我自己的这个域名的子域名来绑定一下,方便我自己访问,就不用每次都使用 http://IP:port
的形式来访问了.此外,我还可以白嫖一下 Cloudflare 的服务,加上 HTTPS 访问.
前情提要
本站点是一个静态站点,实际上是 hugo 生成的静态站点,源码托管在 github pages 或者 gitlab pages 上,绑定了自定义的域名.同时用 Cloudflare 的服务来提供了 HTTPS 的访问.关于这一点如何操作,互联网上已经有很多教程和资料,这里不再赘述.
现在,我打算添加子域名解析到我要用的其他网页服务去,但是前提肯定是不能影响我这个主站的正常运行.
详细的步骤
为了方便描述,下面先列出一些假设:
- 我的公网服务器 IP 为:
1.1.1.1
. - 我在公网服务器上部署了一个网页服务,可以通过
http://1.1.1.1:8080
访问到. - 我的域名为
example.org
. - 我现在想要直接通过
https://web.example.org:8081
或者http://web.example.org:8081
访问到http://1.1.1.1:8080
的服务.
这里我们可以通过在服务器 1.1.1.1
上使用 Nginx 反向代理,监听 8081 端口,将来自 web.example.org
的请求转发给 http://1.1.1.1:8080
,即可完成任务.
具体的,Cloudflare 的设置需要:
- 在 Cloudflare 的 DNS 设置添加 A 记录,将
web.example.org
解析到1.1.1.1
,并勾选 proxy 选项.勾选 proxy 选项表示使用 Cloudflare 的 CDN 服务,不勾选则表示仅仅做 DNS 解析.我们想要白嫖 Cloudflare 的 CDN,且用上 HTTPS,所以这里要勾选. - 打开 Cloudflare 的 SSL/TLS->Origin Server,生成一个证书,颁发给
*.example.org
和example.org
.这个证书仅仅用于服务器 1.1.1.1 和 Cloudflare 之间的通信,有效期选择 15 年,免得更换.这里按照提示保存得到两个文件,分别记作example.org.key
和example.org.pem
. - 检查一下 cloudfalre SSL/TLS 设置 encryption mode 为 flexible 或者 full.这个一般都是 ok 的.
- 检查一下 Cloudflare 里 SSL/TLS 的 Edge Certificates 开启 Always Use HTTPS。这个选项是为了使用 Cloudflare 的 CDN 服务的时候,用户访问
http://web.example.org:8081
会自动跳转到https://web.example.org:8081
.这个选项一般也是已经开启了的.
1.1.1.1
服务器上的设置:
- 这里我们使用 docker compose 来启动 Nginx 服务,Nginx 配置文件
proxy.conf
内容如下:
map $host $proxy_target {
# 域名和代理目标服务器的映射
web.example.org http://1.1.1.1:8080;
# 默认设置一个空的代理目标服务器
default "";
}
server {
listen 443 ssl;
# 服务器名称,也就是域名
server_name web.example.org;
# HTTPS 证书,仅用于源服务器与 Cloudflare 之间的通信
# 由 Cloudflare 签发给 *.example.org,有效期15年
# 完全不用更新了
ssl_certificate /etc/nginx/cert/example.org.pem;
ssl_certificate_key /etc/nginx/cert/example.org.key;
location / {
# 检查代理目标服务器的这个变量,为空则直接返回 404
if ($proxy_target = "") {
return 404;
}
# 代理目标服务器
proxy_pass $proxy_target;
# 设置请求头
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;
proxy_read_timeout 600s;
proxy_connect_timeout 600s;
proxy_buffering off;
}
}
这里我们使用了 Nginx 配置里的 map
,方便后续根据需要添加更多的子域名.只需要添加子域名和对应的反向代理目标服务器即可.
docker-compose.yaml
文件内容如下:
name: nginx-proxy
services:
nginx:
image: nginx:latest
container_name: nginx_proxy
ports:
- "8081:443"
volumes:
- ./proxy.conf:/etc/nginx/conf.d/proxy.conf:ro
- ./cert/example.org.pem:/etc/nginx/cert/example.org.pem:ro
- ./cert/example.org.key:/etc/nginx/cert/example.org.key:ro
- 启动服务:
docker compose up -d
.
现在,用户访问 http://web.example.org:8081
会自动跳转到 https://web.example.org:8081
,最后实际上访问到 http://1.1.1.1:8080
提供的网页服务了.当然,用户访问 https://web.example.org:8081
也会是访问到 http://1.1.1.1:8080
提供的网页服务.
这样就实现了用户(浏览器)访问我们的站点的时候用上了 HTTPS 了,提高了安全性.比如说你的网页可以使用语音输入,浏览器会要求只能在 HTTPS 下使用麦克风设备,如果直接通过 IP 访问,就得自签名一个证书,还会看到浏览器的不安全警告.
进一步的改进
但是有的时候事情并不尽如人意.如果我们有一个新的网页服务部署在 http://1.1.1.1:8090
,但是他的前端代码里有一些目标为 HTTP 站点的请求,例如他可能发送请求给 http://2.2.2.2:8080/api/
获取某个响应结果.这个时候继续按照上面的方法去操作,对于浏览器来说,就是要在 HTTPS 站点里发起了对 HTTP 目标服务器的请求了,也就是 HTTP over HTTPS,这个是不安全的.特别是这种从 HTTPS 站点主动请求 HTTP API 的情况,一般都会被浏览器直接禁止,从而导致这个网页服务无法使用.
最好的情况当然是我们对 http://1.1.1.1:8090
上的服务有完全的掌控力,可以去修改其中的代码.具体的修改可以是改成请求到 HTTPS 的接口,如果外部服务不提供 HTTPS 的接口请求,还可以自己再做一个代理服务器来转发.如果无法修改,那我们只好继续使用 http 访问了.这个时候配置会略有不同.
具体的,假设我们想要使用 http://vip.example.org:8091
访问 http://1.1.1.1:8090
,Cloudflare 的配置:
- 在 Cloudflare 的 DNS 设置添加 A 记录,将
vip.example.org
解析到1.1.1.1
,并且取消勾选 proxy 选项.这里我们只需要做 DNS 解析.
1.1.1.1
服务器上的设置:
- Nginx 配置文件
proxy.conf
新增一个 server 块:
server {
listen 80;
# 服务器名称,也就是域名
server_name vip.example.org;
location / {
# 代理目标服务器
proxy_pass http://1.1.1.1:8090;
# 设置请求头
proxy_set_header Host $remote_addr;
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;
proxy_read_timeout 600s;
proxy_connect_timeout 600s;
proxy_buffering off;
}
}
然后 docker-compose.yaml
中新增一个端口映射,完整配置如下:
name: nginx-proxy
services:
nginx:
image: nginx:latest
container_name: nginx_proxy
ports:
- "8081:443"
- "8091:80"
volumes:
- ./proxy.conf:/etc/nginx/conf.d/proxy.conf:ro
- ./cert/example.org.pem:/etc/nginx/cert/example.org.pem:ro
- ./cert/example.org.key:/etc/nginx/cert/example.org.key:ro
启动这个 Nginx 反向代理服务之后,用户访问 http://vip.example.org:8091
即可对应访问到 http://1.1.1.1:8090
的服务了.
补充
关于公网服务网 1.1.1.1
的端口,如果你的 80 和 443 端口可以使用,那就可以不用非标端口 8091
和 8081
.这样用户访问的域名后面也就不用加上端口了.不过如果你的服务器在国内的话,一般 80 和 443 端口都是被云服务商的防火墙挡住的,必须备案后才能使用.有的云服务上即使你使用了非标端口,如果域名没有备案,也是不允许通过域名访问的,但是并不限制通过 IP 加端口的方式来访问.此时,你只需要删掉 Nginx 配置文件中的 proxy_set_header Host $remote_addr;
即可.也就是不在请求头里设置 Host
为你的域名,云服务商就不知道你这个请求是从一个域名解析过来的,然后就放行了.