AWSのApplication Load Balancer (ALB) を使ってWebアプリケーションを公開する際、HTTPS接続でカスタムポートを使用しようとしたら、思わぬエラーに直面しました。そのトラブルシューティングの過程と解決までの道のりを共有します。もし同じようなエラーでお悩みの方がいれば、この記事が少しでもお役に立てれば幸いです。
発端:ERR_SSL_PROTOCOL_ERROR の発生
今回、ALB経由でHTTPSカスタムポート(例: https://your-domain.com:XXXXX
)でWebアプリケーションに接続しようとしたところ、ブラウザに ERR_SSL_PROTOCOL_ERROR
が表示され、接続ができませんでした。
「SSLプロトコルエラー」という表示から、まずSSL/TLS関連の設定を疑いました。
フェーズ1:SSL/TLSハンドシェイクの検証
1. ALBリスナー設定の確認
最初にALBのリスナー設定を確認しました。
- プロトコルが
HTTPS
、ポートがXXXXX
になっているか。 - ACM (AWS Certificate Manager) で発行したSSL証明書が正しくアタッチされているか。
- セキュリティポリシーが最新の推奨ポリシー (
ELBSecurityPolicy-TLS13-1-2-Res-2021-06
など) になっているか。
これらはすべて正しく設定されていることを確認しました。
2. openssl s_client によるハンドシェイクテスト
ALBのDNS名とカスタムポートを指定して、openssl s_client
コマンドでSSL/TLSハンドシェイクが成功するかどうかをテストしました。
openssl s_client -connect your-alb-dns-name:XXXXX -servername your-domain.com -debug -state -tls1_3
結果:Verify return code: 0 (ok)
この結果は、クライアントとALBの間でSSL/TLSハンドシェイクが正常に完了していることを示していました。つまり、ALBはHTTPSリクエストを受け付けて、正しく証明書を提示できていたのです。
3. telnet によるTCP接続テスト
TCP接続自体が可能なのかを確認するため、telnet
コマンドでALBのIPアドレスとポートに接続を試みました。
telnet your-alb-ip-address XXXXX
結果:Connected to ... Connection closed by foreign host.
TCP接続はできたものの、すぐに切断されました。これは、ALBがHTTPSポートで生のTCP接続を受けた際に、SSLハンドシェイクが行われないために接続を切断する正常な挙動であり、ALBが意図通りHTTPSポートで待機している証拠でした。
この時点では、ERR_SSL_PROTOCOL_ERROR
は、SSL/TLSハンドシェイクそのものではなく、その後の段階で発生している可能性が高いと推測しました。
フェーズ2:ALBアクセスログによる詳細調査とDNS設定の特定
問題の切り分けのため、ALBのアクセスログを有効化することを決意しました。ALBのアクセスログはS3バケットに出力されますが、これにはS3バケットポリシーの設定が必要です。
S3バケットポリシーでの苦戦
ALBアクセスログ有効化時に「Access Denied」エラーが発生し、S3バケットポリシーの設定に手こずりました。
ポイントとなったのは、公式ドキュメントにある最新のポリシーを適用することと、Resource
のパスがALBのプレフィックス設定と完全に一致しているか、そしてAWSアカウントIDが正確であるかの確認でした。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "delivery.logs.amazonaws.com" // または古いelb-account-id
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::your-s3-bucket-name/AWSLogs/your-account-id/*", // プレフィックスがあればここに含める
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control"
}
}
},
{
"Effect": "Allow",
"Principal": {
"Service": "delivery.logs.amazonaws.com"
},
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::your-s3-bucket-name"
}
]
}
このポリシーを正確に適用することで、無事にALBのアクセスログが出力されるようになりました。
アクセスログの解析とDNSの盲点
アクセスログが出力されるようになったことで、大きな発見がありました。
重要な発見:
curl
コマンドでALBのDNS名(例:alb-xxxxxxxx.elb.amazonaws.com
)を直接指定してアクセスした場合は、ALBアクセスログに記録され、elb_status_code: 400
が返されていました。- しかし、カスタムドメイン(例:
https://your-domain.com:XXXXX
)でブラウザからアクセスしても、ALBのアクセスログには何も記録されませんでした。
このことから、ブラウザからのカスタムドメイン経由のアクセスが、そもそもALBに到達していないことが判明しました。これはつまり、DNS解決に問題があることを強く示唆していました。
Route 53 DNS設定の修正
そこで、カスタムドメイン your-domain.com
のDNS設定を確認するためにRoute 53のホストゾーンを調べました。
判明した問題: 当初、your-domain.com
のAレコードが「エイリアス設定OFF」になっており、直接Webサーバー(EC2インスタンス)のIPアドレスを指していました。この設定では、トラフィックがALBを経由せず、直接EC2インスタンスに送られてしまっていたのです。
解決策: Aレコードのエイリアス設定を「ON」にし、ルーティング先としてALBを指定するように修正しました。
この修正により、your-domain.com
へのリクエストが正しくALBを経由するようになり、ブラウザでアクセスした際にALBのアクセスログにも記録されるようになりました。
フェーズ3:The plain HTTP request was sent to HTTPS port エラーの解決
Route 53のDNS設定を修正し、カスタムドメインがALBを指すようになったことで、ブラウザで https://your-domain.com:XXXXX
へアクセスすると、今度は ERR_SSL_PROTOCOL_ERROR
ではなく、ブラウザ画面に「BadRequest: The plain HTTP request was sent to HTTPS port
」というメッセージが表示されるようになりました。
このメッセージは、以下の状況を示しています。
- ALBのHTTPSリスナーは正常に機能している。
- SSL/TLSハンドシェイクは成功している。
- しかし、暗号化された通信路上で、クライアントが「HTTP」(暗号化されていないプレーンテキスト)のリクエストを送ってしまっている。
この「HTTPSポートにHTTPリクエストが送信された」という問題の主な原因は、ブラウザに残っていたHSTS (HTTP Strict Transport Security) キャッシュや、過去の接続履歴によるリダイレクトの誤動作でした。ブラウザが過去のHTTP接続や特定の挙動を記憶し、ALBとのSSL/TLS接続確立後に、誤った形式のリクエストを送ってしまっていたのです。
最終的な解決への道筋
この問題は、ブラウザのHSTSキャッシュをクリアすることで解決できる可能性が高いです。
- ブラウザのHSTSキャッシュをクリアする: (例: Chromeなら
chrome://net-internals/#hsts
でドメインを削除し、ブラウザを再起動)
さらに、もしWebサーバー側でHTTPからHTTPSへの強制リダイレクトを行っている場合、ALBがSSLオフロードしていることを考慮し、X-Forwarded-Protoヘッダー(ALBが送信するHTTPSリクエストを示すヘッダー)を確認してリダイレクトを制御するようにWebサーバーの設定(例: Apacheの.htaccess
やNginxの設定)を見直すことで、リダイレクトループやエラーを根本的に解消できます。
まとめ
今回のトラブルシューティングは、複数のレイヤー(DNS、ALBリスナー、S3権限、Webサーバー設定、クライアントブラウザの挙動)にまたがる複雑な問題でした。特に、openssl s_client
によるSSLハンドシェイクの確認、ALBアクセスログの詳細な分析、そしてカスタムドメインのDNS解決経路の正確な把握が、問題の核心に迫る上で決定的な役割を果たしました。
もし同様のERR_SSL_PROTOCOL_ERROR
や The plain HTTP request was sent to HTTPS port
に直面された方がいれば、これらのステップをぜひ参考にしてみてください。