サイトには「別名」がいくつもあることがよくあります。旧ドメイン、www の有無による表記ゆれ、予備のドメインなどです。これらは 301 の恒久的リダイレクトで唯一の正規ドメインに転送し、パスとクエリパラメータも保持すべきです。すでに HTTPS リスナーがあれば、ACM 証明書 1 枚と ALB リスナールール 1 つでこれを実現できます。リクエストは WordPress を経由せず、データベースを起こすこともありません。本記事では本サイトでの実際の構成に沿って、証明書の検証、SNI、ルールの書き方、curl による確認を説明します。例では example.org と www.example.org がリダイレクト元の名前、www.example.com が正規ドメインです。
構成
訪問者 ─TLS─> ALB の HTTPS リスナー(:443)
├─ 証明書リストに example.org / www.example.org の証明書を 1 枚追加(SNI)
└─ ルール:Host ∈ {example.org, www.example.org}
└─> 301 で https://www.example.com/元のパス?元のクエリ へ
これらの名前の DNS は ALB を直接指しており、CloudFront を経由しません。リダイレクトは ALB 上で完結し、リクエストはオリジンに届かないので、このルールには CloudFront がオリジンへのリクエストに付けるシークレットヘッダーは必要ありません。本サイトではこれを独立した CDK スタックにしています。WordPress スタックは変更不要で、不要になればスタックごと削除できます。このスタックが行うのは、証明書の発行、証明書のリスナーへの追加、ルールの追加の 3 つだけです。
証明書:DNS が Route 53 にない場合の DNS 検証
ALB で使う証明書は、ALB と同じリージョンで発行する必要があります。この点は CloudFront と異なります。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 が 1 つずつあります(ワイルドカード証明書とそのルートドメインは 1 つを共用します)。
- 「レコード名」欄の扱いは DNS プロバイダーによって異なります。完全な名前を入力させるところもあれば、ドメイン名を自動で補うところもあります。入力を誤ると
_xxx.example.org.example.orgのようなレコードになり、検証は失敗します。 - ACM が 72 時間以内に検証できないと、証明書のステータスは Validation timed out になり、発行し直すしかありません。
- 検証用レコードは削除せずに残してください。証明書が使用中でレコードが残っていれば、ACM が自動で更新します。
追加の SNI 証明書としてリスナーに追加する
HTTPS リスナーにはデフォルト証明書がちょうど 1 枚あり、それとは別に証明書リストを付けられます。クライアントが 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}と書きます。クエリパラメータには疑問符を含めません。- リダイレクト先のプロトコル、ホスト、ポート、パスのうち少なくとも 1 つを変えないとループになります。ここで変えているのはホストです。
- 優先度はリスナー内で一意でなければなりません。本サイトのサイト本体の転送ルールは 100、このルールは 110 です。2 つのルールの 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 でアクセスすると 2 回リダイレクトされます。まず ALB のポート 80 のリスナーにもともとある HTTP→HTTPS リダイレクトで
https://example.org:443/…に移り、次にこのルールで正規ドメインに移ります。リダイレクト回数と最終的な URL はcurl -sIL -o /dev/null -w '%{num_redirects} %{url_effective}\n' http://example.org/xで確認できます。
なぜ ALB で行うのか
- アプリケーションに負荷をかけない:WordPress 側でリダイレクトすると、すべてのリクエストがアプリケーションのコンテナに入ります。プラグインで行う場合は、さらに PHP とデータベースも通ります。本サイトの Aurora はアイドル時に一時停止するため、旧ドメインをクロールするボットが 1 つあるだけで何度も起こされかねません。
- CDN に手を入れない:これらの名前を CloudFront ディストリビューションの代替ドメイン名に追加する必要も、そのために us-east-1 で証明書を発行する必要もありません。
- 境界が明確:独立したスタックはリソースを追加するだけで、既存のリソースは変更しません。リスナーのデフォルト証明書、デフォルトアクション、他のルールはすべてそのまま残ります。
制約と注意点
- ルートドメイン(
example.orgなど)には DNS の仕様上 CNAME を設定できません。DNS が Route 53 にない場合、ルートドメインを ALB の DNS 名に向けるには、プロバイダーが CNAME フラットニングに類する機能を提供しているか確認する必要があります。 - 301 はブラウザに長期間キャッシュされます。転送先を誤ると取り消すのが難しいため、まず
HTTP_302で確認し、問題がないことを確かめてから 301 に変えるとよいでしょう。 - これらのリクエストは CloudFront を経由しないため、CloudFront 層のキャッシュや保護はなく、ALB が直接受けます。



