一个站点常常有几个“别名”:旧域名、带不带 www 的写法、备用域名。它们应该用 301 永久重定向到唯一的规范域名,并且保留路径和查询参数。如果手里已经有一个 HTTPS 监听器,这件事用一张 ACM 证书加一条 ALB 监听器规则就能完成,请求不经过 WordPress,也不会唤醒数据库。本文按本站的实际做法,写清证书验证、SNI、规则写法和 curl 验证。示例里 example.org、www.example.org 是要跳走的名字,www.example.com 是规范域名。
结构
访客 ─TLS─> ALB 的 HTTPS 监听器(:443)
├─ 证书列表里多一张 example.org / www.example.org 的证书(SNI)
└─ 规则:Host ∈ {example.org, www.example.org}
└─> 301 到 https://www.example.com/原路径?原查询
这些名字的 DNS 直接指向 ALB,不经过 CloudFront。重定向在 ALB 上就完成了,请求到不了源站,所以这条规则不需要 CloudFront 回源时带的那个秘密头。本站把它放在一个独立的 CDK 栈里:WordPress 栈不用改,不需要时整栈删掉即可。它只做三件事:申请证书、把证书加到监听器、加一条规则。
证书:DNS 不在 Route 53 时的 DNS 验证
ALB 用的证书必须在 ALB 所在的区域申请,这一点和 CloudFront 不同,后者只认 us-east-1 的证书。用 CDK 申请一张覆盖多个名字的证书:
const [first, ...rest] = props.from; // ["example.org", "www.example.org"]
const cert = new acm.Certificate(this, "Certificate", {
domainName: first,
subjectAlternativeNames: rest,
validation: acm.CertificateValidation.fromDns(),
});
DNS 不在 Route 53 时,CDK 没法自动写验证记录。部署会停在证书这一步,等你把 ACM 给出的 CNAME 手工加到 DNS 服务商那里。记录名和值可以在栈事件、ACM 控制台,或 aws acm describe-certificate 的输出里找到。几点要注意:
- 每个名字各有一条验证 CNAME(通配符证书和它的根域名共用一条)。
- 各家 DNS 服务商对“记录名”一栏的处理不同:有的要填完整名字,有的会自动补上域名。填错会得到
_xxx.example.org.example.org这样的记录,验证就失败了。 - ACM 在 72 小时内验证不了,证书状态会变成 Validation timed out,只能重新申请。
- 验证记录要一直保留。只要证书在使用、记录还在,ACM 就会自动续期。
作为额外的 SNI 证书加到监听器
HTTPS 监听器有且只有一张默认证书,另外可以挂一个证书列表。客户端通过 SNI 报出主机名时,ALB 从列表里挑匹配的证书;没有匹配或客户端不支持 SNI 时,才用默认证书。所以加证书不会影响监听器上已有的其他站点:
new elbv2.CfnListenerCertificate(this, "ListenerCertificate", {
listenerArn: props.listenerArn,
certificates: [{ certificateArn: cert.certificateArn }],
});
规则:host-header 条件 + redirect 动作
new elbv2.CfnListenerRule(this, "Redirect", {
listenerArn: props.listenerArn,
priority: 110,
conditions: [{ field: "host-header", hostHeaderConfig: { values: ["example.org", "www.example.org"] } }],
actions: [{
type: "redirect",
redirectConfig: {
protocol: "HTTPS",
host: "www.example.com",
port: "443",
path: "/#{path}",
query: "#{query}",
statusCode: "HTTP_301",
},
}],
});
要点:
#{path}和#{query}是 ALB 的保留关键字,分别表示原请求的路径和查询参数。路径必须以/开头,所以写成/#{path};查询参数不带问号。- 重定向的协议、主机、端口、路径里至少要改一项,否则会形成循环;这里改的是主机。
- 优先级在监听器内必须唯一。本站站点本身的转发规则是 100,这条是 110。两条规则的 Host 集合没有交集,谁先谁后不影响结果。
- ALB 不允许从 HTTPS 重定向到 HTTP。
用 curl 验证
$ curl -sI "https://example.org/some/path?a=1&b=2" | grep -iE '^(HTTP|location|server)'
HTTP/2 301
server: awselb/2.0
location: https://www.example.com:443/some/path?a=1&b=2
响应来自 ALB(server: awselb/2.0),路径和查询参数都保留了。本站实测还确认了几种情况:
Location里带着:443。这是因为规则里显式写了端口。443 是 https 的默认端口,浏览器和 curl 跟随时都按同一个地址处理,不影响结果,只是看起来多了一段。- 没有查询参数时,
Location末尾不会多一个?,例如https://www.example.com:443/。 - 百分号编码的路径和参数原样保留,例如
/a%20b/c?x=%E4%B8%AD。 - 用 http 访问会跳两次:先由 ALB 的 80 端口监听器上原有的 HTTP→HTTPS 重定向跳到
https://example.org:443/…,再由这条规则跳到规范域名。可以用curl -sIL -o /dev/null -w '%{num_redirects} %{url_effective}\n' http://example.org/x看跳转次数和最终地址。
为什么放在 ALB 上做
- 不打扰应用:在 WordPress 里做重定向,每个请求都要进应用容器;用插件做时还要经过 PHP 和数据库,本站的 Aurora 会在空闲时暂停,一个爬旧域名的爬虫就可能把它反复唤醒。
- 不动 CDN:这些名字不需要加到 CloudFront 分发的备用域名里,也不用为它们在 us-east-1 申请证书。
- 边界清楚:独立的栈只加资源、不改已有资源,监听器的默认证书、默认动作和其他规则都保持原样。
局限 / 注意
- 根域名(如
example.org)按 DNS 规范不能设 CNAME。DNS 不在 Route 53 时,要确认服务商提供类似 CNAME 拉平的功能,才能把根域名指向 ALB 的 DNS 名。 - 301 会被浏览器长期缓存。目标写错了很难收回,可以先用
HTTP_302验证,确认无误再改成 301。 - 这些请求不经过 CloudFront,也就没有 CloudFront 那一层的缓存和防护,直接由 ALB 承接。



