NGINX反向代理https

架构说明

我的实践是构建 docs.cloud-atlas.io :

  • HTTPS反向代理部署在阿里云上: 因为阿里云有稳定的公网带宽

  • HTTP服务器部署在 Raspberry Pi Cluster : 部署在家庭内部的微型 Raspberry Pi 系统

    • 核心服务器完全是自己部署,可以自由扩展服务器集群规模而没有云计算的高昂费用

    • 所有软硬件都由自己打造,可以充分锻炼 一个人的数据中心 技术堆栈

    • 个人家庭宽带没有公网IP,通过 CTunnel 打通阿里云公网服务器到家庭内部集群的通道

安装Nginx

Debian 系安装Nginx
apt update
apt install nginx
Arch Linux 安装Nginx
# 需要安装certbot-nginx插件,以便能够让Certbot配置nginx
pacman -S nginx certbot-nginx

配置Nginx

备注

需要简单配置Nginx的服务域名 Nginx virtual host配置 这样后续执行 certbot 会自动修订配置完成TLS/SSL配置修订。

警告

如果服务器在墙内云上,如果没有备案, Cetbot 配置时反向访问HTTP 80端口会被云厂商拦截导致配置失败。请备案后执行,或者在海外服务器上配置后再迁移。

  • 配置 /etc/nginx/conf.d/cloud-atlas.io.conf ( 这个文件命名只需要以 .conf 结尾即可包含在 /etc/nginx/nginx.conf 中,具体根据nginx版本发行版提供的 /etc/nginx.conf 而定 ):

/etc/nginx/nginx.conf 包含的 Nginx virtual host配置 配置
server {
    listen 80;
    listen [::]:80;

    root /var/www/cloud-atlas.io;
    index index.html index.htm ;

    server_name cloud-atlas.io alias docs.cloud-atlas.io;

    location / {
        try_files $uri $uri/ =404;
    }
}
  • /var/www/cloud-atlas.io 目录下创建一个测试 index.html 内容如下:

测试 index.html
<html>
    <head>
        <title>Welcome to Cloud Atlas</title>
    </head>
    <body>
        <h1>Success!  The cloud-atlas.io server block is working!</h1>
    </body>
</html>

Let's Encrypt证书

备注

首先需要确保域名( docs.cloud-atlas.io )已经指向了反向代理服务器,也就是我的阿里云公网服务器IP

这个步骤非常重要: Let's Encrypt就是通过域名指向的服务器来确保分发证书是合法的

  • 安装 Certbot :

Debian 安装 Certbot
apt-get update
apt-get install software-properties-common
add-apt-repository ppa:certbot/certbot
apt-get update
apt-get install python3-certbot-nginx
CentOS / Rocky Linux 安装 Certbot
dnf install epel-release
dnf install certbot python3-certbot-nginx
# 如果是Apache,则使用
# dnf install certbot python3-certbot-apache
FreeBSD 安装 Certbot
pkg install security/py-certbot-nginx
  • 运行 Certboot :

运行 certbot 为Nginx生成证书
certbot --nginx

执行的输出信息

运行 certbot 为Nginx生成证书
...
Which names would you like to activate HTTPS for?
We recommend selecting either all domains, or all domains in a VirtualHost/server block.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: cloud-atlas.io
2: docs.cloud-atlas.io
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 1 2
Requesting a certificate for cloud-atlas.io and docs.cloud-atlas.io

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/cloud-atlas.io/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/cloud-atlas.io/privkey.pem
This certificate expires on 2025-03-24.
These files will be updated when the certificate renews.

Deploying certificate
Successfully deployed certificate for cloud-atlas.io to /etc/nginx/nginx.conf
Successfully deployed certificate for docs.cloud-atlas.io to /etc/nginx/nginx.conf
Congratulations! You have successfully enabled HTTPS on https://cloud-atlas.io and https://docs.cloud-atlas.io

NEXT STEPS:
- The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

检查 /etc/nginx/conf.d/cloud-atlas.io.conf 可以看到 certbot 修订了配置:

Certbot 自动修订了 /etc/nginx/nginx.conf 包含的 Nginx virtual host配置 配置
server {
    server_name cloud-atlas.io alias docs.cloud-atlas.io;

    root /var/www/cloud-atlas.io;
    index index.html index.htm ;

    location / {
        try_files $uri $uri/ =404;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/cloud-atlas.io/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/cloud-atlas.io/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {
    if ($host = docs.cloud-atlas.io) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = cloud-atlas.io) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen       80;
    server_name  cloud-atlas.io  alias  docs.cloud-atlas.io;
    return 404; # managed by Certbot
}

现在访问 https://cloud-atlas.io 或者 https://docs.cloud-atlas.io 就可以看到HTTPS的加密已经生效,并且证书是Let's Encrypt 签发的。

证书更新

certbot 提供了自动更新证书的能力,简单执行以下命令可以检查:

Let's Encrypt证书更新检查
# 测试证书更新
certbot renew --dry-run
  • 配置一个定时任务 sudo crontab -e :

配置自动更新Let's Encrypt证书
0 0,12 * * * python -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew --quiet

警告

我还没有实践,待补充完善

反向代理

上述已经完成HTTPS基本配置,接下来修订转发规则:

修订转发规则
server {
    server_name cloud-atlas.io alias docs.cloud-atlas.io;

    #root /var/www/cloud-atlas.io;
    index index.html index.htm ;


    location / {
        #try_files $uri $uri/ =404;
        proxy_pass http://127.0.0.1:24180;
        proxy_http_version  1.1;
        proxy_cache_bypass  $http_upgrade;
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-Host  $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port  $server_port;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/cloud-atlas.io/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/cloud-atlas.io/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {
    if ($host = docs.cloud-atlas.io) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = cloud-atlas.io) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen       80;
    server_name  cloud-atlas.io  alias  docs.cloud-atlas.io;
    return 404; # managed by Certbot
}

注意,这里必须要传递 header ,也就是 proxy_set_header 传递参数非常关键,没有这些参数,后端WEB服务器无法分辨访问域名,也就无法使用 Nginx virtual host配置 来提供合适的返回。

后端服务器

后端服务器运行在一个 Raspberry Pi 主机 192.168.7.241 上,简单的Nginx配置实现一个WEB服务器配置:

后端服务器 nginx 配置
server {
	listen 80;
	listen [::]:80;

	server_name cloud-atlas.io alias docs.cloud-atlas.io;

	root /var/www/cloud-atlas.io;
	index index.html;

	location / {
		try_files $uri $uri/ =404;
	}
}

备注

我在实践完成后发现一个非常困扰的问题,safari经常正常访问网站。我最初以为是自己的配置问题,反复排查,但是最后发现问题在于阿里云对没有"网站备案"的HTTP/HTTPS会TCP reset。这个问题困扰的是只有Safari会出现异常(chrome和firefox正常)。

这个问题的根源在于阿里云对HTTPS的握手 SNI(Server Name Indication) 进行检查,因为Safari目前(2025年中)不支持 ESNI(encrypted SNI)ECH (Encrypted Client Hello) ,导致明文的SNI泄露了客户端访问的域名。任何没有备案的域名访问都会被TCP RESET!

而Chrome和Firefox已经默认启用了 ECH (Encrypted Client Hello) 支持,所以访问网站时不会泄露SNI,所以始终正常。

目前(Safari不支持加密SNI),没有很好的解决方案:

  • 我的域名是 cloud-atlas.dev ,这个 .dev 顶级域名工信部不提供备案,也就是无法在大陆租用的云主机上使用

  • 海外的VM价格太贵而且反向访问速度太慢,不太可能作为homelab的网关

我最终决定将方案改为使用 Cloudflare Tunnel 输出 cloud-atlas.dev ,构建自己的互联网集群。

参考