增加自定义绑定域名
This commit is contained in:
+165
-12
@@ -461,7 +461,154 @@ server {
|
||||
|
||||
---
|
||||
|
||||
## 五、重要注意事项
|
||||
## 五、租户自有域名(扑底站点 + 自动 HTTPS)
|
||||
|
||||
> 前面两节的站点只匹配 `yunzer.com.cn` / `*.yunzer.com.cn` / `dh2.fun` / `*.dh2.fun`。租户绑定自己的域名(如 `www.客户公司.com`)后,解析过来会落到 nginx 的默认站点,命中不了上面的任何 `server_name`。本节的扑底站点解决两件事:**让未知域名也能进 Go**,以及**为这些域名提供 HTTPS 证书**。
|
||||
|
||||
### 5.1 工作流程
|
||||
|
||||
1. 租户在后台「我的域名」点「绑定自有域名」,填写完整域名;
|
||||
2. 系统返回 CNAME 目标 / A 记录 IP(来自 `app.conf` 的 `customdomain_cname_target`、`customdomain_a_ips`);
|
||||
3. 租户到域名服务商添加解析,回来点「检测解析」;
|
||||
4. Go 用 `net.LookupCNAME` / `net.LookupHost` 校验解析是否指向平台;
|
||||
5. 校验通过后 Go 异步向 Let's Encrypt 发起 HTTP-01 申请,CA 回访 `http://域名/.well-known/acme-challenge/<token>`;
|
||||
6. nginx 扑底站点把该路径**明文**反代给 Go,Go 应答 token;
|
||||
7. 签发成功后证书写入 `{ssl_cert_dir}/{域名}/fullchain.pem` 与 `privkey.pem`,记录置 `ssl_status=2`、`status=1`,域名生效;
|
||||
8. 每 12 小时扫一次,到期前 30 天自动续期。
|
||||
|
||||
### 5.2 app.conf 需要填的五项
|
||||
|
||||
```ini
|
||||
# 租户子域名 CNAME 到这个主机名(该主机名需已 A 记录指向本服务器)
|
||||
customdomain_cname_target = sites.yunzer.com.cn
|
||||
|
||||
# 根域名只能用 A 记录,这里写本服务器公网 IP(多个用英文逗号分隔)
|
||||
customdomain_a_ips = 1.2.3.4
|
||||
|
||||
# Let's Encrypt 账号邮箱
|
||||
acme_email = ops@yunzer.com.cn
|
||||
|
||||
# 留空使用正式环境;联调先填 staging,避免打满正式环境频控
|
||||
# acme_directory = https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_directory =
|
||||
|
||||
# 证书落盘目录,Go 进程必须可写,nginx 必须可读
|
||||
ssl_cert_dir = /www/wwwroot/ssl-certs
|
||||
```
|
||||
|
||||
`customdomain_cname_target` 建议单独用一个平台子域(如 `sites.yunzer.com.cn`),指向服务器 IP。以后换服务器只改这条 A 记录,所有租户的 CNAME 都跟着走,不用逐个通知租户改解析。
|
||||
|
||||
### 5.3 扑底站点配置
|
||||
|
||||
新建一个宝塔站点(域名可随便填一个不用的,例如 `custom-domains.local`),或直接在任一站点配置文件里追加下面内容。**注意 upstream 名字不能与 `go_backend_dh2` / `go_backend_yunzer` 重复。**
|
||||
|
||||
```nginx
|
||||
upstream go_backend_custom {
|
||||
server 127.0.0.1:8081;
|
||||
}
|
||||
|
||||
# SNI 决定用哪个域名的证书目录;无 SNI 的请求回落到 _default
|
||||
map $ssl_server_name $cert_host {
|
||||
"" _default;
|
||||
default $ssl_server_name;
|
||||
}
|
||||
|
||||
# ---------- HTTP:只负责 ACME 验证与明文访问,不做整站跳转 ----------
|
||||
server {
|
||||
listen 80 default_server;
|
||||
server_name _;
|
||||
|
||||
# 关键:ACME 验证不能 301 到 HTTPS。首次签发时还没有证书,
|
||||
# 跳过去 CA 会因为证书不可信而验证失败。
|
||||
location /.well-known/acme-challenge/ {
|
||||
proxy_pass http://go_backend_custom;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://go_backend_custom;
|
||||
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;
|
||||
}
|
||||
|
||||
access_log /www/wwwlogs/custom-domain.log;
|
||||
error_log /www/wwwlogs/custom-domain.error.log;
|
||||
}
|
||||
|
||||
# ---------- HTTPS:按 SNI 从证书目录取对应域名的证书 ----------
|
||||
server {
|
||||
listen 443 ssl default_server;
|
||||
http2 on;
|
||||
server_name _;
|
||||
|
||||
ssl_certificate /www/wwwroot/ssl-certs/$cert_host/fullchain.pem;
|
||||
ssl_certificate_key /www/wwwroot/ssl-certs/$cert_host/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
|
||||
# 这里不要加 HSTS:证书是自动签的,一旦某个域名签发失败又被浏览器记住 HSTS,
|
||||
# 租户连 HTTP 兜底访问都用不了。
|
||||
|
||||
location / {
|
||||
proxy_pass http://go_backend_custom;
|
||||
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;
|
||||
}
|
||||
|
||||
access_log /www/wwwlogs/custom-domain.ssl.log;
|
||||
error_log /www/wwwlogs/custom-domain.ssl.error.log;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 上线前必做的四件事
|
||||
|
||||
1. **放一份 `_default` 兜底证书**。`ssl_certificate` 用了变量,nginx 启动时不校验文件是否存在,但握手时找不到文件会直接失败。没有 SNI 的客户端(老设备、直接用 IP 访问)会落到 `_default`:
|
||||
|
||||
```bash
|
||||
mkdir -p /www/wwwroot/ssl-certs/_default
|
||||
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
|
||||
-keyout /www/wwwroot/ssl-certs/_default/privkey.pem \
|
||||
-out /www/wwwroot/ssl-certs/_default/fullchain.pem \
|
||||
-subj "/CN=invalid"
|
||||
```
|
||||
|
||||
2. **nginx 版本 ≥ 1.15.9**。`ssl_certificate` 支持变量是从这个版本开始的,宝塔默认的 1.2x 都满足。低版本会报 `invalid number of arguments` 或直接不生效。
|
||||
|
||||
3. **证书目录权限**。Go 进程要能创建子目录与写文件,nginx 要能读:
|
||||
|
||||
```bash
|
||||
mkdir -p /www/wwwroot/ssl-certs
|
||||
chown -R <go进程用户>:www /www/wwwroot/ssl-certs
|
||||
chmod 755 /www/wwwroot/ssl-certs
|
||||
```
|
||||
|
||||
4. **公网 80 与 443 都要可达**。HTTP-01 验证走 80 端口,被防火墙或安全组挡住会签发失败。
|
||||
|
||||
### 5.5 default_server 的影响范围
|
||||
|
||||
`listen 80 default_server` 会把「所有没匹配到具体 server_name 的请求」都吃掉。原来这类请求落到宝塔默认站点(通常是一个空白页或宝塔欢迎页)。如果服务器上还有其他靠默认站点兜底的业务,先确认它们都有精确的 `server_name`,否则会被这个扑底站点抢走。nginx 的匹配优先级是:精确 `server_name` > 前导通配 > 尾随通配 > 正则 > `default_server`,前面两节的配置都是精确匹配或正则,不受影响。
|
||||
|
||||
### 5.6 排查
|
||||
|
||||
| 现象 | 原因 | 处理 |
|
||||
|---|---|---|
|
||||
| 「解析检测」提示当前解析到别的 IP | 解析没生效或填错 | 等 TTL 过期;`dig +short 域名` 核对 |
|
||||
| 「解析检测」提示平台尚未配置解析目标 | `app.conf` 两项为空 | 填 `customdomain_cname_target` / `customdomain_a_ips` 后重启 Go |
|
||||
| 证书状态一直「签发中」 | CA 回访拿不到 token | `curl http://域名/.well-known/acme-challenge/test` 应到 Go 而非 301 |
|
||||
| 证书「签发失败」,原因含 403/404 | 80 端口不可达或被 HTTPS 跳转拦了 | 检查安全组、确认 challenge 的 location 在跳转规则之前 |
|
||||
| 浏览器提示证书名称不匹配 | 落到了 `_default` 自签证书 | 说明 SNI 对应目录没有证书,看该域名的 `ssl_status` 与 `ssl_error` |
|
||||
| 签发失败后短时间点不动 | 失败冷却 30 分钟 | 等冷却结束再点,或先用 staging 联调 |
|
||||
|
||||
> 签发失败原因会写进 `yz_system_tenant_domain.ssl_error`,前端「证书」列悬浮即可看到。Let's Encrypt 对同一账号每小时失败次数有限制,所以代码里加了 30 分钟冷却,避免租户反复点击把额度打满。
|
||||
|
||||
---
|
||||
|
||||
## 六、重要注意事项
|
||||
|
||||
1. **子域名必须用泛域名证书**。租户域名是二级域名(`ceshi.yunzer.com.cn`),单域名证书会导致 HTTPS 证书错误。需在宝塔为 `*.yunzer.com.cn`、`*.dh2.fun` 申请泛域名证书(DNS 验证),并替换配置中的证书路径。
|
||||
2. **upstream 不能重名**。两个站点配置都在同一 http 上下文,本文分别用 `go_backend_dh2`、`go_backend_yunzer`;重名会 `duplicate upstream` 启动失败。
|
||||
@@ -480,9 +627,9 @@ server {
|
||||
|
||||
---
|
||||
|
||||
## 六、验证与排查
|
||||
## 七、验证与排查
|
||||
|
||||
### 6.1 部署后验证顺序
|
||||
### 7.1 部署后验证顺序
|
||||
|
||||
```bash
|
||||
# 1. 绕过 nginx 直接验证 Go(在服务器上执行)
|
||||
@@ -494,10 +641,16 @@ curl -k -H "Host: ceshi.yunzer.com.cn" https://127.0.0.1/
|
||||
# 3. 验证七牛代理:应返回图片内容或 200/304,而不是 Go 的 404 页面
|
||||
curl -I https://yunzer.com.cn/qiniu/2026/06/02/1780371759517804300.png
|
||||
|
||||
# 4. 浏览器访问 https://ceshi.yunzer.com.cn
|
||||
# 4. 自有域名:先确认扑底站点能把 challenge 明文送到 Go(不应返回 301)
|
||||
curl -i -H "Host: 待绑域名" http://127.0.0.1/.well-known/acme-challenge/probe
|
||||
|
||||
# 5. 自有域名:确认 Go 能按 Host 命中租户
|
||||
curl -H "Host: 待绑域名" http://127.0.0.1:8081/
|
||||
|
||||
# 6. 浏览器访问 https://ceshi.yunzer.com.cn
|
||||
```
|
||||
|
||||
### 6.2 常见现象对照表
|
||||
### 7.2 常见现象对照表
|
||||
|
||||
| 现象 | 原因 | 处理 |
|
||||
|---|---|---|
|
||||
@@ -510,18 +663,18 @@ curl -I https://yunzer.com.cn/qiniu/2026/06/02/1780371759517804300.png
|
||||
|
||||
---
|
||||
|
||||
## 七、迁移服务器清单(以迁移到 192.168.1.101 为例)
|
||||
## 八、迁移服务器清单(以迁移到 192.168.1.101 为例)
|
||||
|
||||
1. **新机器部署 Go**:复制 `go/` 目录(含 `themes/`、`conf/app.conf`),修改 `app.conf` 数据库连接为新库地址,启动服务;
|
||||
2. **新库初始化**:导入业务数据,执行 `sql/` 下全部建表/变更 SQL(尤其 `yz_cms_frontend_tables.sql`);
|
||||
2. **新库初始化**:导入业务数据,执行 `sql/` 下全部建表/变更 SQL(尤其 `yz_cms_frontend_tables.sql`、`alter_tenant_domain_add_custom.sql`);
|
||||
3. **nginx**:
|
||||
- nginx 与 Go 同机 → upstream 用 `127.0.0.1:8081`,配置零改动;
|
||||
- 跨机 → 只改两个 upstream 块中的 `server` 一行;
|
||||
4. **证书**:新机器宝塔上为两个域名重新申请/导入证书(含泛域名),路径与配置保持一致;
|
||||
5. **DNS**:将 `dh2.fun`、`yunzer.com.cn` 及泛解析 `*` 的 A 记录改指向新服务器 IP;
|
||||
6. **验证**:按第六节顺序逐一验证;
|
||||
- 跨机 → 只改三个 upstream 块中的 `server` 一行(含扑底站点的 `go_backend_custom`);
|
||||
4. **证书**:新机器宝塔上为两个域名重新申请/导入证书(含泛域名),路径与配置保持一致;自有域名的证书目录 `/www/wwwroot/ssl-certs` 整个复制过去(含 `_account/key.pem`——换了账号私钥等于换 ACME 账号,历史签发记录与频控额度都会重置);
|
||||
5. **DNS**:将 `dh2.fun`、`yunzer.com.cn` 及泛解析 `*` 的 A 记录改指向新服务器 IP;`customdomain_cname_target` 对应子域的 A 记录也要改,租户侧的 CNAME 不用动;
|
||||
6. **验证**:按第七节顺序逐一验证;
|
||||
7. 确认无误后到域名服务商处调低旧解析 TTL、观察切换。
|
||||
|
||||
---
|
||||
|
||||
*本文档对应代码:`go/controllers/tenant_site.go`(Host 识别与渲染)、`go/pkg/tagengine/`(标签引擎)、`go/themes/default/`(内置默认模板)、`sql/yz_cms_frontend_tables.sql`(建站数据表)。*
|
||||
*本文档对应代码:`go/controllers/tenant_site.go`(Host 识别与渲染)、`go/services/acme_cert.go`(自动签发与续期)、`go/services/domain_verify.go`(解析检测)、`go/controllers/acme_challenge.go`(HTTP-01 应答)、`go/pkg/tagengine/`(标签引擎)、`go/themes/default/`(内置默认模板)、`sql/yz_cms_frontend_tables.sql`(建站数据表)、`sql/alter_tenant_domain_add_custom.sql`(自有域名字段)。*
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
# 绑定自有域名操作说明(给租户看的版本)
|
||||
|
||||
把你自己的域名(例如 `www.你的公司.com`)指向平台,访客访问时打开的还是你在后台配置的那套官网内容,与平台分配的二级域名完全一致。HTTPS 证书由系统自动申请与续期,你不需要买证书、不需要上传证书。
|
||||
|
||||
整个过程三步:**填域名 → 加解析 → 点检测**。
|
||||
|
||||
---
|
||||
|
||||
## 一、填域名
|
||||
|
||||
后台 →「我的域名」→ 右上角「绑定自有域名」。
|
||||
|
||||
填写规则:
|
||||
|
||||
- 填完整域名,例如 `www.example.com`;
|
||||
- 不要带 `http://`、`https://`,不要带路径和端口;
|
||||
- 根域名(`example.com`)和 `www.example.com` 是两个不同的域名。想两个都能访问,就绑两次;
|
||||
- 域名必须已完成实名认证与备案(国内服务器要求),否则解析能通但访问会被拦。
|
||||
|
||||
提交后会看到一条记录,状态是「待生效」,这是正常的——还没加解析。
|
||||
|
||||
---
|
||||
|
||||
## 二、加解析
|
||||
|
||||
弹窗里会显示平台给你的解析目标。到你买域名的地方(阿里云、腾讯云、DNSPod 等)的「域名解析」里添加记录:
|
||||
|
||||
### 子域名(如 www、cn、shop)
|
||||
|
||||
| 记录类型 | 主机记录 | 记录值 |
|
||||
|---|---|---|
|
||||
| CNAME | www | 弹窗里显示的 CNAME 目标 |
|
||||
|
||||
### 根域名(如 example.com 本身)
|
||||
|
||||
根域名在 DNS 标准里不能用 CNAME,只能用 A 记录:
|
||||
|
||||
| 记录类型 | 主机记录 | 记录值 |
|
||||
|---|---|---|
|
||||
| A | @ | 弹窗里显示的 IP |
|
||||
|
||||
几个容易踩的点:
|
||||
|
||||
- **主机记录填 `www`,不要填 `www.example.com`**。解析商会自动补上域名后缀,填全了会变成 `www.example.com.example.com`。
|
||||
- **同一个主机记录下不能既有 CNAME 又有 A/MX 记录**。如果 `www` 已经有 A 记录指向别处,先删掉再加 CNAME。
|
||||
- **有 CDN 的先关掉**。域名挂在 CDN 上时解析指向的是 CDN 节点,系统检测不到平台地址,证书也签不下来。
|
||||
- **TTL 用默认值就行**。修改解析后需要等 TTL 时间生效,通常 10 分钟内,最长可能要几小时。
|
||||
|
||||
---
|
||||
|
||||
## 三、点检测
|
||||
|
||||
回到「我的域名」,在这条自有域名记录上点「检测解析」。
|
||||
|
||||
- 解析已生效 → 提示「正在申请 HTTPS 证书」。等 1~2 分钟刷新页面,「证书」列变成「已签发」、状态变成「已生效」,域名就能用了;
|
||||
- 解析没生效 → 提示当前解析到了哪里,对照第二步核对记录值,等一会儿再点。
|
||||
|
||||
生效后用 `https://你的域名` 访问,看到的就是你的官网。
|
||||
|
||||
---
|
||||
|
||||
## 四、状态怎么看
|
||||
|
||||
「我的域名」列表里有三列状态:
|
||||
|
||||
| 列 | 取值 | 含义 |
|
||||
|---|---|---|
|
||||
| 状态 | 待生效 | 解析或证书还没就绪,暂时不能访问 |
|
||||
| 状态 | 已生效 | 可以正常访问 |
|
||||
| 状态 | 已禁用 | 被平台停用,联系平台管理员 |
|
||||
| 解析检测 | 未检测 / 已通过 / 未通过 | 鼠标悬浮看具体原因 |
|
||||
| 证书 | 待处理 / 签发中 / 已签发 / 签发失败 | 悬浮「已签发」看到期时间,悬浮「签发失败」看失败原因 |
|
||||
|
||||
证书到期前系统会自动续期,你不用管。
|
||||
|
||||
---
|
||||
|
||||
## 五、常见问题
|
||||
|
||||
**点了检测一直提示解析未生效**
|
||||
|
||||
用命令行确认解析实际指向哪里(Windows 用 `nslookup`,Mac/Linux 用 `dig`):
|
||||
|
||||
```bash
|
||||
nslookup www.example.com
|
||||
dig +short www.example.com
|
||||
```
|
||||
|
||||
看返回的结果是不是弹窗里给的目标。如果不是,说明记录没加对或者还没生效。
|
||||
|
||||
**证书显示「签发失败」**
|
||||
|
||||
悬浮在「签发失败」上看原因,常见两种:
|
||||
|
||||
- 提到 80 端口或验证失败:你的域名可能开了强制 HTTPS 跳转,或者解析被 CDN 拦了。证书颁发机构需要通过 80 端口访问 `http://你的域名/.well-known/acme-challenge/...` 来验证域名归属;
|
||||
- 提到频率限制:短时间内失败次数太多。系统有 30 分钟冷却,等半小时再试。
|
||||
|
||||
**能不能同时用平台二级域名和自己的域名**
|
||||
|
||||
可以,两个都能访问同一套官网内容,互不影响。
|
||||
|
||||
**换域名怎么办**
|
||||
|
||||
先删掉旧的那条记录,再绑新域名。删除后旧域名立即无法访问,已签发的证书也不再续期。
|
||||
|
||||
**一个租户能绑几个自有域名**
|
||||
|
||||
一个。需要多绑(比如根域名和 www 都要)请联系平台管理员。
|
||||
|
||||
---
|
||||
|
||||
## 六、需要联系平台的情况
|
||||
|
||||
- 弹窗提示「平台尚未配置解析目标」;
|
||||
- 提示「该域名已被绑定」但你确认没绑过;
|
||||
- 提示「该域名属于平台域名」——这种情况请改用「申请二级域名」;
|
||||
- 检测通过但证书连续失败超过一天。
|
||||
Reference in New Issue
Block a user