用 ALB 监听器规则把多余域名 301 到主域名

一张 ACM 证书作为额外 SNI 证书挂到 HTTPS 监听器,再加一条 host-header 规则,就能把别名域名 301 到规范域名并保留路径和查询参数,请求不进 WordPress。附 DNS 验证要点与 curl 实测。

一个站点常常有几个“别名”:旧域名、带不带 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 承接。

链接