搞机2:域名 HTTPS 接入指南:从 frp 隧道到全链路加密

0 阅读4分钟

先回顾一下现在的局面:

用户访问: http://xx.xx.xx.xx:4097
浏览器:   ⚠️ 不安全(大红叉)
你心里:   淦,这看起来像个盗版网站

没域名、没 HTTPS、地址带端口号——三样东西凑一起,自己用还好,若是以后要做其他功能出来见人,页面再好看也没人敢用。

这次的目标:

  1. https://xxx.bnvstudio.com 直接访问,不带端口号
  2. 浏览器地址栏给我一把 绿色的小锁
  3. 不能随便什么子域名都能访问到我的服务

一、先搞清楚流量长什么样

有了 frp 隧道之后,整条链路是这样的:

用户 → https://xxx.bnvstudio.com
       ↓ DNS 解析到 ECS 公网 IP
       ↓ ECS:443(nginx 监听 HTTPS)
       ↓ nginx 反代到 127.0.0.1:4097(frps 在监听的端口)
       ↓ frp 隧道
       ↓ Mac Mini:4096(opencode 服务)

要搞定这件事,三步走:

DNS 解析  →  SSL 证书  →  nginx 反代

一个不能少。


二、DNS 解析——等了五分钟以为自己配错了

第一步,去你的域名注册商(阿里云/腾讯云/DNSpod 之类的),加一条 A 记录:

记录类型主机记录记录值
Aopencode你的 ECS 公网 IP

然后等。

$ dig xxx.bnvstudio.com

; <<>> DiG 9.x <<>>
;; 过了五秒...
;; 过了十秒...
;; 怎么还没生效???

每次配 DNS 都是这个心理活动:"是不是我配错了?"

实际上没配错,TTL 还没过期而已。去泡杯茶回来看就好了。

确认生效:

dig xxx.bnvstudio.com +short
# 输出: 你的ECS公网IP   ← 可以了

三、SSL 证书——为什么选了 acme.sh

现在 xxx.bnvstudio.com 通过nginx反代能解析到 ECS ,但访问还是 HTTP。需要一张 SSL 证书。

3.1 选项对比

方案优点缺点
阿里云免费证书点几下就拿到一年一换,不支持泛域名,要手动部署
商业证书贵,看起来专业
acme.sh + Let's Encrypt免费,自动续期,一行命令需要稍微动动手

我选 acme.sh。理由很简单:这个工具是用 shell 写的,连 npm install 都不用。

3.2 DNS-01 vs HTTP-01

Let's Encrypt 验证域名所有权有两种方式:

  • HTTP-01:在 80 端口放一个文件,CA 来访问验证
    • 问题:我的 80 端口本来就没开,还得专门去配
  • DNS-01:在 DNS 记录里加一个 TXT 记录,CA 去查
    • 好处:不需要开任何端口,配一次以后全部自动

我选 DNS-01。不动现有服务,是运维的第一原则。

3.3 阿里云 RAM 子用户

DNS-01 需要你有一个 DNS API 的 AccessKey,让 acme.sh 可以自动创建和删除 TXT 记录。

去阿里云 RAM 访问控制创建一个子用户:

https://ram.console.aliyun.com/users

授权策略选择 AliyunDNSFullAccess不要多给

然后拿到:

AccessKey ID:       LTAI5t...
AccessKey Secret:   xxxxxxxxxxxxxxxxxxxxxxxxxx

心理活动:"我只是想申请个证书,为什么要经过『访问控制』?"

照做就是了。

3.4 安装 acme.sh

curl https://get.acme.sh | sh -s email="admin@你的域名.com"

装完之后,acme.sh 会自动做两件事:

  1. ~/.acme.sh/acme.sh 加到 PATH
  2. 装一个 cron job 每天检查证书是否需要续期

没有任何依赖,没有 node_modules,一个 shell 脚本全搞定

3.5 申请证书

export Ali_Key="你的AccessKey ID"
export Ali_Secret="你的AccessKey Secret"

~/.acme.sh/acme.sh --issue \
  --dns dns_ali \
  -d bnvstudio.com \
  -d "*.bnvstudio.com"

解释一下:

参数含义
--dns dns_ali用阿里云 DNS 的 API 做验证
-d bnvstudio.com主域名也要申请,不然根域名访问会警告
-d "*.bnvstudio.com"泛域名,所有子域名都能用这张证书

acme.sh 背后的流程:

  1. 调用阿里云 API 创建 _acme-challenge.bnvstudio.com 的 TXT 记录
  2. 等 DNS 生效(默认它会等 120 秒)
  3. Let's Encrypt 来验证 TXT 记录
  4. 验证通过后自动删除 TXT 记录
  5. 证书生成完毕

全程自动化,你不需要手动去 DNS 控制台点任何按钮。

3.6 安装证书到 nginx

~/.acme.sh/acme.sh --install-cert \
  -d bnvstudio.com \
  --key-file /etc/nginx/ssl/bnvstudio.com/key.pem \
  --fullchain-file /etc/nginx/ssl/bnvstudio.com/fullchain.pem \
  --reloadcmd "systemctl reload nginx"

证书文件放到了:

/etc/nginx/ssl/bnvstudio.com/
├── fullchain.pem    # 证书链
└── key.pem          # 私钥

私钥一定要保护好,泄露了别人可以冒充你的域名。


四、nginx 配置——你以为装完证书就完了?

4.1 写 server block

server {
    listen 443 ssl http2;
    server_name xxx.bnvstudio.com;

    ssl_certificate     /etc/nginx/ssl/bnvstudio.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/bnvstudio.com/key.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
        proxy_pass http://127.0.0.1:4097;

        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;
    }
}

然后:

nginx -t
# syntax is ok ← 基本不会出问题

systemctl reload nginx
# 走你

去浏览器访问 https://xxx.bnvstudio.com...

超时。

4.2 第一层防火墙:firewalld

ss -tlnp | grep 443
# 看到 nginx 在监听,没问题

# 从另一台机器测
echo > /dev/tcp/ECS公网IP/443 && echo "通" || echo "不通"
# 不通

ECS 上的 firewalld 默认没放行 443:

firewall-cmd --add-port=443/tcp --permanent
firewall-cmd --reload

再测:

echo > /dev/tcp/ECS公网IP/443 && echo "通" || echo "不通"
# 通  ✅

4.3 第二层防火墙:阿里云安全组

火急火燎去浏览器访问...

还是超时。

对哦,阿里云安全组还没放行。

去阿里云控制台 → 安全组 → 入方向规则 → 添加:

方向协议端口授权对象说明
入方向TCP4430.0.0.0/0HTTPS

保存之后再访问:

HTTPS 能打开了!小绿锁!

经验教训:ECS 有两层防火墙——firewalld 和安全组。你永远会忘记其中一层。


五、SELinux 补刀

HTTPS 能访问了,点一下页面...

502 Bad Gateway

查看 nginx 错误日志:

tail -f /var/log/nginx/error.log
# 2025/xx/xx ... permission denied while connecting to upstream

SELinux 拦了 nginx 的出站连接。nginx 在 127.0.0.1:4097 反代到 frps,但 SELinux 说:"nginx 只能提供静态文件,不允许连后端服务。"

解决方案:

setsebool -P httpd_can_network_connect 1

解释:

  • setsebool 设置 SELinux 的布尔值
  • httpd_can_network_connect 允许 nginx 发起网络出站连接
  • -P 永久生效,重启不丢失

SELinux 每次都会在你觉得"终于搞定了"的时候出现,补上一刀。

感想:你每次都想过把它关了,但每次都忍住了。不是因为你会配 SELinux,而是因为关了之后万一出安全问题你不好交代。


六、Basic Auth——给服务加个门卫

泛域名证书办下来的好处是 ``*.bnvstudio.com` 都能用 HTTPS 访问了。

但坏处也是:``*.bnvstudio.com` 都能用 HTTPS 访问了。

这意味着如果有人扫到你的子域名,可以直接访问你的服务。虽然不是什么重要数据,但总归不舒服。

加一层 HTTP Basic Auth:

yum install -y httpd-tools                     # 如果还没装
mkdir -p /etc/nginx/auth
htpasswd -bcs /etc/nginx/auth/.htpasswd admin $(openssl rand -base64 12)

在 nginx server block 里加上:

auth_basic           "Restricted Access";
auth_basic_user_file /etc/nginx/auth/.htpasswd;

nginx -t && systemctl reload nginx 之后,再访问就会弹一个登录框:

⚠️ 此网站需要登录
用户名: admin
密码:  (随机生成的那一串)

至少挡住扫端口的脚本小子。真·安全性不要靠 Basic Auth,它只是不想让路人随便进。


七、默认 server 陷阱——"怎么哪个子域名都能访问?"

配完之后心血来潮试了一下:

https://aabbcc.bnvstudio.com

竟然也能访问,而且内容和 xxx.bnvstudio.com 一模一样。

Snipaste_2026-06-18_17-29-50.png

nginx 的工作原理是这样的:当请求的 server_name 没匹配到任何配置块时,它会把请求交给监听同一个端口的第一个 server block(或者说默认 server)来处理。

当前配置里只有一个 server block 监听 443,它就成了默认 server。所以不管什么子域名,只要 DNS 能解析到 ECS IP,都会落到同一个后端。

修复:加一个拒绝所有未知域名的默认 server。

把之前的 server block 改成:

# 默认 server:拒绝一切未匹配的域名
server {
    listen 443 ssl http2 default_server;
    server_name _;

    ssl_certificate     /etc/nginx/ssl/bnvstudio.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/bnvstudio.com/key.pem;

    return 444;  # 直接断开连接,不给任何响应
}

# 真正的服务
server {
    listen 443 ssl http2;   # 注意:没有 default_server 了
    server_name xxx.bnvstudio.com;

    ssl_certificate     /etc/nginx/ssl/bnvstudio.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/bnvstudio.com/key.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    auth_basic           "Restricted Access";
    auth_basic_user_file /etc/nginx/auth/.htpasswd;

    location / {
        proxy_pass http://127.0.0.1:4097;
        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;
    }
}

return 444 是 nginx 专属的状态码,直接关闭连接,不给任何响应数据。比起 return 403return 404,444 更狠——连"这个服务器存在"的消息都不给你。

nginx -t && systemctl reload nginx

再试:

curl -I https://xxx.bnvstudio.com
# HTTP/2 401     ← 正常,Basic Auth 弹了登录框

curl -I https://aabbcc.bnvstudio.com
# curl: (52) Empty reply from server    ← 444 断开连接

舒服了。


八、最终链路

从浏览器输入 URL 到页面渲染,整条路长这样:

┌─ 用户 ──────────────────────────────────┐
│  https://xxx.bnvstudio.com          │
└────────────────┬─────────────────────────┘
                 ▼ DNS 解析
       xxx.bnvstudio.com → ECS 公网 IP
                 ▼
┌─ ECS ───────────────────────────────────┐
│  ① nginx:443                             │
│      SSL 证书验证                    ✅   │
│      server_name 匹配               ✅   │
│      Basic Auth 验证                ✅   │
│      → 反代到 127.0.0.1:4097            │
│  ② frps:4097                            │
│      → 通过 frp 隧道转发                 │
└────────────────┬─────────────────────────┘
                 ▼ frp 隧道
┌─ Mac Mini 2012 ─────────────────────────┐
│  frpc → 本机:4096                       │
│  opencode 服务响应请求                    │
└──────────────────────────────────────────┘

三层安全防护:

防护作用
1SSL/TLS传输加密,小绿锁
2Basic Auth挡住路人访问
3默认 server 444未知域名直接断开

九、改完收工

# 防火墙
firewall-cmd --add-port=443/tcp --permanent
firewall-cmd --reload

# SELinux 放行 nginx 出站
setsebool -P httpd_can_network_connect 1

# nginx 验证
nginx -t && systemctl reload nginx

# 验证访问
curl -I https://xxx.bnvstudio.com

访问 https://xxx.bnvstudio.com,浏览器地址栏出现小绿锁的那一刻,前面所有踩的坑都值了。

续期的事不用管——acme.sh 早就帮你装好了 cron,每天检查一次,到期前自动续。从今天起往后一年,证书的事情都不是你的事了。

一年后才是。


附录:涉及的文件和路径

ECS 侧:
  /etc/nginx/ssl/bnvstudio.com/fullchain.pem   # 证书链
  /etc/nginx/ssl/bnvstudio.com/key.pem         # 私钥
  /etc/nginx/auth/.htpasswd                     # Basic Auth 密码文件
  /etc/nginx/conf.d/(你的server block配置文件)

自动续期:
  ~/.acme.sh/acme.sh --cron                     # 每天检查,acme.sh 自动配置

日志:
  /var/log/nginx/access.log
  /var/log/nginx/error.log