NATFRP 域名穿透 + SSL 部署 SOP 模板(v1.0)
域名切换部署 SOP:<你的域名>(NATFRP <节点所在城市> + Let’s Encrypt 证书)
版本:v1.0(2026-09-03 实测完成)
目的:把一套自建系统的公网入口从<旧域名>(Cloudflare 隧道)切换到<你的域名>(NATFRP <节点所在城市>线路 + HTTPS + 受信任证书)。
本文档基于本次实际执行与验证完整记录,可作为同类 <你的主域名> 子域名复用的步骤复用文档。
0. 结果摘要
| 项 | 值 |
|---|---|
| 对外域名 | https://<你的域名> |
| 穿透服务 | NATFRP(SakuraFrp) |
| 节点 | <节点名称> = <natfrp节点域名> → <节点IP>) |
| 隧道 | myapp,id <隧道ID>,类型 https,<你的域名> → 127.0.0.1:8081 |
| 证书 | Let’s Encrypt(YE2,ECDSA ec-256),有效期 2026-09-03 ~ 2026-12-02,浏览器信任 |
| 证书续期 | acme.sh dns_ali 全自动(阿里云自动增删 TXT) |
最终验证:curl https://<你的域名>/ 返回 HTTP 200,前端资源 / 登录 API 均正常。
1. 架构与原理(务必先读)
1.1 TLS 在哪里终结:本地 frpc
NATFRP 的「自动 HTTPS」由本地 frpc 进程实现,TLS 在你的服务器上终结,证书不会上传到 NATFRP 服务器:
用户浏览器
│ TCP 穿透(密文,边缘节点不碰 TLS)
▼
NATFRP 边缘节点(<你的域名> → CNAME <natfrp节点域名>)
│ 内网隧道(TLS 密文)
▼
本地 frpc(192.168.1.120)
│ 从工作目录加载证书:/etc/natfrp/FrpcWorkingDirectory/<域名>.crt|.key
│ 解密后纯 HTTP/HTTPS 反代回源
▼
本地服务(自建前端 nginx,127.0.0.1:8081)
1.2 证书加载规则(关键)
隧道开启 auto_https 后,frpc 加载证书的优先级:
auto_https |
行为 |
|---|---|
| 留空(默认) | 禁用 |
auto |
用节点域名生成自签证书;若配置了子域绑定(自定义域名),则从服务端自动加载该域名证书 |
| 逗号分隔的域名列表 | 加载工作目录 <域名>.crt / <域名>.key;不存在则生成自签兜底 |
⚠️ 若证书文件缺失/格式错,frpc 会静默生成自签证书(
CN=SakuraFrp Automatic TLS),浏览器报”不受信任”。看到自签证书 = 正式证书没加载上。
本次隧道配置用了 auto_https = auto + force_https = 301:自动 HTTPS 开启、http 自动跳转 https。
1.3 为什么用 acme.sh 的 dns_ali 插件签发证书
- HTTP-01 验证在穿透场景不可靠(80 端口/路径不可控),DNS-01 更稳。
- <你的主域名> 的 DNS 在阿里云,
dns_ali插件用阿里云 AccessKey 自动增删_acme-challengeTXT 记录,全程无需人工改 DNS,且续期时也无需人工介入。 - 相比 certbot manual DNS-01(每次续期 TXT 值会变、需人工改 DNS),acme.sh dns_ali 是全链路无人值守方案。
2. 前置条件 Checklist
- [ ] NATFRP(SakuraFrp)账号,拿到访问密钥(Access Key,形如
sg2m...) - [ ] 目标域名(<你的域名>)所在的阿里云账号 AccessKeyId / AccessKeySecret(有 Alidns 操作权限)
- [ ] 内网服务器(本次
192.168.1.120)可 SSH,有 sudo,网络可出公网 - [ ] 本地服务已在某端口监听(本次自建前端 nginx 在
127.0.0.1:8081) - [ ] 服务器装有 acme.sh、openssl、dig、python3(带
aliyun-python-sdk-alidns,本机已装)
3. 步骤总览
Step 1 创建 NATFRP 隧道(<节点所在城市> <nodeID>,HTTPS,<你的域名> → 127.0.0.1:8081)
Step 2 阿里云 DNS 添加 CNAME:<你的域名> → <natfrp节点域名>
Step 3 acme.sh dns_ali 签发 Let's Encrypt 证书
Step 4 部署证书到 frpc 工作目录 /etc/natfrp/FrpcWorkingDirectory
Step 5 新隧道加入 daemon 自动启动并重启 natfrp;配置自动续期闭环
Step 6 端到端验证
4. Step 1:创建 NATFRP 隧道
4.1 方案选择:用官方 API(推荐,可脚本化)
NATFRP 有官方 API,https://api.natfrp.com/docs/(OpenAPI,swagger)。认证方式:
Authorization: Bearer <访问密钥>
基础 URL:https://api.natfrp.com/v4
先确认访问密钥有效,并列出当前隧道:
AK="<你的NATFRP访问密钥>"
curl -s --max-time 15 "https://api.natfrp.com/v4/tunnels" \
-H "Authorization: Bearer $AK" | python3 -m json.tool
获取节点列表,找到<节点所在城市>节点(nodes 是 dict,key 为节点 id):
curl -s --max-time 20 "https://api.natfrp.com/v4/nodes" \
-H "Authorization: Bearer $AK" -o /tmp/nodes.json
python3 -c "
import json
d=json.load(open('/tmp/nodes.json'))
for nid,n in d.items():
if '<节点所在城市>' in n.get('name',''): print(nid, n.get('name'), n.get('host'))
"
本次结果:<节点名称> =
(host <natfrp节点域名>),与既有 <某服务子域名> / <某服务子域名> 隧道同节点,支持 HTTPS 建站。
4.2 创建 HTTPS 隧道
POST /v4/tunnels,请求体字段见 OpenAPI(name/type/node 必填;remote 为绑定域名;local_ip/local_port 为回源地址;extra 为自定义配置):
AK="<你的NATFRP访问密钥>"
curl -s --max-time 20 -X POST "https://api.natfrp.com/v4/tunnels" \
-H "Authorization: Bearer $AK" -H "Content-Type: application/json" \
-d '{
"name":"myapp",
"type":"https",
"node":<nodeID>,
"remote":"<你的域名>",
"local_ip":"127.0.0.1",
"local_port":8081,
"note":"内容系统(<节点所在城市>)",
"extra":"auto_https = auto\nforce_https = 301\n"
}'
响应(HTTP 201):
{"id": <隧道ID>, "name": "myapp", "remote": "<你的域名>"}
说明:类型
https隧道必须与协议一致;也可建http隧道(80)。HTTPS 隧道配force_https=301后,用户访问 http 会自动跳 https,无需再建 http 隧道。内地节点备案提醒:用内地(<节点所在城市>等)节点建
HTTP(S)隧道时,域名需有 ICP 备案。本系统之所有可复用此流程,正是因 <你的主域名> 已备案。
4.3 面板替代方案(不用 API 时)
也可在 NATFRP 面板 手动创建:隧道类型选「HTTPS」,节点选<节点所在城市>,绑定域名填 <你的域名>,本地端口填 8081,开启「自动 HTTPS」。参数与 API 等价。
5. Step 2:配置阿里云 DNS
5.1 方式 A:阿里云 API(SDK,推荐)
本机已装 aliyun-python-sdk-alidns。先列出当前记录确认账号可访问:
python3 - <<'EOF'
from aliyunsdkcore.client import AcsClient
from aliyunsdkalidns.request.v20150109.DescribeDomainRecordsRequest import DescribeDomainRecordsRequest
client = AcsClient('<AccessKeyId>', '<AccessKeySecret>', 'cn-hangzhou')
req = DescribeDomainRecordsRequest(); req.set_DomainName('<你的主域名>')
import json
d = json.loads(client.do_action_with_exception(req))
for r in d['DomainRecords']['Record']:
print(r.get('RR'), r.get('Type'), r.get('Value'), 'recordId='+r.get('RecordId'))
EOF
添加 CNAME 记录(content → <natfrp节点域名>,TTL 600):
python3 - <<'EOF'
from aliyunsdkcore.client import AcsClient
from aliyunsdkalidns.request.v20150109.AddDomainRecordRequest import AddDomainRecordRequest
client = AcsClient('<AccessKeyId>', '<AccessKeySecret>', 'cn-hangzhou')
req = AddDomainRecordRequest()
req.set_DomainName('<你的主域名>')
req.set_RR('content'); req.set_Type('CNAME'); req.set_Value('<natfrp节点域名>'); req.set_TTL(600)
import json; print(json.loads(client.do_action_with_exception(req)))
EOF
验证生效:
dig +short <你的域名>
# 期望输出:
# <natfrp节点域名>.
# <节点IP>
5.2 方式 B:阿里云控制台
DNS 控制台 → 解析设置 → 添加记录:记录类型 CNAME、主机记录 content、记录值 <natfrp节点域名>、TTL 600。
<你的主域名> 的 NS 是阿里云(dns31/32.hichina.com)。CNAME 目标指向节点域名
<natfrp节点域名>(不要用 IP,便于自动 HTTPS 识别与节点迁移)。
6. Step 3:签发 Let’s Encrypt 证书(acme.sh dns_ali 全自动)
6.1 确认 acme.sh 及 dns_ali 插件存在
ls ~/.acme.sh/acme.sh ~/.acme.sh/dnsapi/dns_ali.sh
~/.acme.sh/acme.sh --version # 本机 v3.1.5
6.2 签发(环境变量一次性传入阿里云凭证;签发后会写进配置)
export HOME=/home/ubuntu
cd ~/.acme.sh
export Ali_Key="<AccessKeyId>"
export Ali_Secret="<AccessKeySecret>"
./acme.sh --issue --dns dns_ali -d <你的域名> \
--keylength ec-256 --server letsencrypt --force
dns_ali 会自动调用阿里云 API 添加
_acme-challenge.<你的域名>TXT 记录并在校验证后删除,全程无需人工。
成功后在:
~/.acme.sh/<你的域名>_ecc/
├── <你的域名>.cer # cert
├── <你的域名>.key # private key
├── ca.cer # intermediate
└── fullchain.cer # 完整链(部署用这个)
验证证书信息:
openssl x509 -in ~/.acme.sh/<你的域名>_ecc/fullchain.cer -noout -issuer -subject -dates
# issuer=C = US, O = Let's Encrypt, CN = YE2
7. Step 4:部署证书到 frpc 工作目录
必须用 fullchain.cer(含中间链)作为 .crt,不能用 cert.cer,否则浏览器报证书链不完整。
DEST="/etc/natfrp/FrpcWorkingDirectory"
SRC="/home/ubuntu/.acme.sh/<你的域名>_ecc"
sudo cp "$SRC/fullchain.cer" "$DEST/<你的域名>.crt"
sudo cp "$SRC/<你的域名>.key" "$DEST/<你的域名>.key"
sudo chown natfrp:natfrp "$DEST/<你的域名>.crt" "$DEST/<你的域名>.key"
sudo chmod 644 "$DEST/<你的域名>.crt"
sudo chmod 600 "$DEST/<你的域名>.key"
校验公私钥匹配(注意用 sudo 读 600 权限的 key,普通用户读会报 Permission denied,属正常):
CERTPUB=$(sudo openssl x509 -in "$DEST/<你的域名>.crt" -noout -pubkey | openssl md5)
KEYPUB=$(sudo bash -c "openssl ec -in '$DEST/<你的域名>.key' -pubout 2>/dev/null" | openssl md5)
[ "$CERTPUB" = "$KEYPUB" ] && echo "✓ 公私钥匹配" || echo "✗ 不匹配"
8. Step 5:新隧道加入 daemon 自动启动 + 配置自动续期
8.1 让 natfrp daemon 自动启动新隧道
natfrp-service(/etc/natfrp/config.json)里的 auto_start_tunnels 控制哪些隧道随服务启动。把新隧道 id 加进去并重启:
sudo cp /etc/natfrp/config.json /etc/natfrp/config.json.bak.content
sudo python3 - <<'EOF'
import json
p='/etc/natfrp/config.json'; d=json.load(open(p))
ast=list(d.get('auto_start_tunnels',[]))
if <隧道ID> not in ast: ast.append(<隧道ID>); d['auto_start_tunnels']=ast
json.dump(d,open(p,'w'),ensure_ascii=False,indent=2)
print(d['auto_start_tunnels'])
EOF
sudo python3 -c "import json; json.load(open('/etc/natfrp/config.json')); print('JSON OK')"
sudo systemctl restart natfrp.service
重启后查看日志确认新隧道加载了正式证书(而非自签):
sudo grep -i content /etc/natfrp/Logs/natfrp-service.<日期>.log
# 期望看到:
# 已为 <你的域名> 加载证书 [CN = <你的域名>, 2026-09-03 - 2026-12-02]
# HTTPS 隧道启动成功, 绑定到域名 [<你的域名>]
# 隧道启动成功
同时会看到类似
dx.<natfrp节点域名>的自签提示,那个是节点域名的自签兜底,不影响自定义域名 <你的域名> 使用正式证书。
8.2 配置证书自动续期 + 自动部署
acme.sh 已通过 dns_ali 持久化阿里云凭证(~/.acme.sh/account.conf 的 SAVED_Ali_Key/SAVED_Ali_Secret),续期时会自动加载重签,无需再传。还需配置「续期后自动复制证书到 frpc 工作目录并重启 natfrp」。
创建部署脚本(用 sudo 写 natfrp 目录,ubuntu 免密 sudo):
sudo tee /usr/local/bin/natfrp-content-deploy.sh >/dev/null <<'EOF'
#!/bin/bash
DEST="/etc/natfrp/FrpcWorkingDirectory"
CRT_DIR="$HOME/.acme.sh/<你的域名>_ecc"
if [ -f "$CRT_DIR/fullchain.cer" ] && [ -f "$CRT_DIR/<你的域名>.key" ]; then
sudo cp -f "$CRT_DIR/fullchain.cer" "$DEST/<你的域名>.crt"
sudo cp -f "$CRT_DIR/<你的域名>.key" "$DEST/<你的域名>.key"
sudo chown natfrp:natfrp "$DEST/<你的域名>.crt" "$DEST/<你的域名>.key"
sudo chmod 644 "$DEST/<你的域名>.crt"
sudo chmod 600 "$DEST/<你的域名>.key"
sudo systemctl restart natfrp.service
echo "[deploy] <你的域名> cert updated & natfrp restarted"
fi
EOF
sudo chmod +x /usr/local/bin/natfrp-content-deploy.sh
把脚本注册为域名的续期 hook:
CONF=~/.acme.sh/<你的域名>_ecc/<你的域名>.conf
python3 - "$CONF" <<'EOF'
import sys, re
p=sys.argv[1]; s=open(p).read()
hook="/usr/local/bin/natfrp-content-deploy.sh"
s = re.sub(r"Le_RenewHook=.*", f"Le_RenewHook='{hook}'", s) if 'Le_RenewHook=' in s \
else s.rstrip('\n') + f"\nLe_RenewHook='{hook}'\n"
open(p,'w').write(s)
EOF
grep Le_RenewHook "$CONF"
确认 cron 已存在(acme.sh 首次运行会装):
crontab -l | grep acme
# 58 5,11,17,23 * * * "/home/ubuntu/.acme.sh"/acme.sh --cron --home "/home/ubuntu/.acme.sh" > /dev/null
证书有效期 90 天;acme.sh 按 Let’s Encrypt ARI 建议时间自动续期(本次
Next renewal= 2026-11-03)。cron 每天跑 4 次检查。
9. Step 6:端到端验证清单
# 1) 本机回源服务正常
curl -s -o /dev/null -w "local 8081 -> %{http_code}\n" http://127.0.0.1:8081/
# 2) 公网 HTTPS + 校验证书(不用 -k,必须能过证书校验)
curl -s -o /dev/null -w "https://<你的域名> -> %{http_code}\n" --max-time 12 https://<你的域名>/
# 3) 证书信息
echo | openssl s_client -connect <你的域名>:443 -servername <你的域名> 2>/dev/null \
| openssl x509 -noout -issuer -subject -dates
# 4) 前端资源 + 登录 API(验证是真实应用而非默认页)
curl -s https://<你的域名>/ | grep -o '<title>[^<]*</title>'
curl -s -o /dev/null -w "JS asset -> %{http_code}\n" https://<你的域名>/assets/index-*.js
curl -s -o /dev/null -w "login API (bad creds) -> %{http_code}\n" -X POST https://<你的域名>/api/auth/login -H 'Content-Type: application/json' -d '{"username":"x","password":"y"}'
# 5) 隧道在线状态
curl -s "https://api.natfrp.com/v4/tunnels" -H "Authorization: Bearer $AK" \
| python3 -c "import sys,json;[print(f\"{t['name']} online={t['online']}\") for t in json.load(sys.stdin) if t['id']==<隧道ID>]"
# 6) natfrp 服务存活 + 证书续期闭环
systemctl is-active natfrp.service
grep Le_RenewHook ~/.acme.sh/<你的域名>_ecc/<你的域名>.conf
本次全部通过:
| 检查 | 结果 |
|---|---|
| 本机 8081 | 200 |
| https://<你的域名> | 200 |
| 证书 | issuer=YE2,CN=<你的域名>,2026-12-02 前有效 |
| 页面标题 | 「<系统名称>」 |
| 前端 JS / 登录 API | 200 / 401 |
| 隧道 API | myapp online=True |
| natfrp | active |
10. 故障排查
| 现象 | 排查 |
|---|---|
证书显示 CN=SakuraFrp Automatic TLS(自签) |
正式证书没加载上:① 检查 /etc/natfrp/FrpcWorkingDirectory/<你的域名>.{crt,key} 存在且格式正确(PEM)② 替换后是否 systemctl restart natfrp.service ③ 看日志是否有自签提示 |
| 浏览器报证书链不完整 | .crt 必须用 fullchain.cer(含中间链),不能用 cert.cer |
| 域名访问不了 / SakuraFrp 错误页 | ① DNS 是否生效:dig <你的域名> +short ② 隧道是否启动、节点是否在线 ③ 内地节点是否备案 |
| 502 / 504 / 连接拒绝 | frpc 到本地回源不通:ss -tlnp | grep 8081、ps aux | grep frpc;回源服务是否在监听 |
openssl 读 key 报 Permission denied |
正常,key 是 600 权限且属 natfrp;用 sudo 读 |
| natfrp 面板建隧道提示需备案 | 内地(<节点所在城市>)节点的 HTTP(S) 隧道必须 ICP 备案域名;或改用 TCP 隧道(非内地节点) |
| 续期后又是自签 | Le_RenewHook 未注册或脚本无执行权限;检查 cron 是否在跑 |
11. 续期说明(重要)
| 环节 | 自动化程度 |
|---|---|
| certbot/acme.sh 触发续期(到期前约 30 天,ARI) | ✅ 自动(cron 每天 4 次检查) |
| DNS-01 验证(阿里云增删 TXT) | ✅ 自动(acme.sh dns_ali,凭证在 account.conf,TXT 每次不同但自动处理) |
| 部署证书到 frpc 工作目录 + 重启 natfrp | ✅ 自动(Le_RenewHook → /usr/local/bin/natfrp-content-deploy.sh) |
全链路无人值守,但建议在 2026-11-01 前后抽查一次续期是否成功:
~/.acme.sh/acme.sh --list
# 期望:Renew 时间往前推进,证书 notAfter 更新为新的 +90 天
如需手动续期:
export HOME=/home/ubuntu
~/.acme.sh/acme.sh --renew -d <你的域名> --ecc
12. 变更记录
| 日期 | 内容 |
|---|---|
| 2026-09-03 | v1.0:完成 <你的域名> 全流程部署——NATFRP <节点所在城市> |
13. 安全提示
- 文中的 NATFRP 访问密钥、阿里云 AccessKey 均为敏感凭证,请勿提交到代码仓库/公开文档;生产环境建议用变量注入而非硬编码。
- 阿里云 AccessKey 建议按需轮换,并通过 RAM 限制到 Alidns 最小权限范围。