ALPN(Application-Layer Protocol Negotiation)
概要
ALPN(Application-Layer Protocol Negotiation) は、TLS ハンドシェイクの中でクライアントとサーバーがアプリケーション層のプロトコルを交渉する TLS 拡張です。RFC 7301(2014 年)で定義されています。
HTTP/2(RFC 9113)は TLS 上で動作する際に ALPN による交渉を必須としています。クライアントが h2(HTTP/2)をサポートしていることを TLS ハンドシェイクで通知し、サーバーが対応していれば HTTP/2 で通信が始まります。ALPN がなければ、TLS ハンドシェイクが完了した後に追加のラウンドトリップでプロトコルを決定する必要があり、接続確立が遅れます。
ALPN の前身として NPN(Next Protocol Negotiation)がありました。NPN は Google が Chrome と SPDY プロトコルのために開発した独自拡張でしたが、IETF は NPN を標準化せず、代わりに ALPN を RFC として発行しました。NPN との主な違いは、ALPN ではサーバーがプロトコルを最終決定する点です。NPN ではクライアントが決定していました。
ALPN は HTTP/2 以外にも、ACME の TLS-ALPN-01 チャレンジ(RFC 8737)、HTTP/3(RFC 9114)の h3 識別子など、複数の用途で使われています。
仕組み
TLS ハンドシェイクの ClientHello/ServerHello でプロトコルを交渉する流れを説明します。
TLS ハンドシェイクでの交渉
ALPN の交渉は TLS ハンドシェイクの ClientHello と ServerHello で行われます。
- クライアントが ClientHello に
application_layer_protocol_negotiation拡張を含める。対応するプロトコルの識別子をリストで送信する(例:h2,http/1.1) - サーバーがリストの中から自身がサポートするプロトコルを 1 つ選択する
- サーバーが ServerHello に選択したプロトコルを含めて返す
- TLS ハンドシェイク完了後、選択されたプロトコルでアプリケーション層の通信が始まる
サーバーがクライアントのリストに含まれるプロトコルをどれもサポートしていない場合、サーバーは no_application_protocol アラートを送信して接続を中断するか、ALPN 拡張なしで応答します。ALPN 拡張なしの場合、HTTP/1.1 にフォールバックするのが一般的です。
プロトコル識別子
IANA が ALPN プロトコル識別子のレジストリを管理しています。主な識別子は次のとおりです。
| 識別子 | プロトコル | RFC |
|---|---|---|
h2 | HTTP/2 over TLS | RFC 9113 |
h3 | HTTP/3 over QUIC | RFC 9114 |
http/1.1 | HTTP/1.1 | RFC 9112 |
acme-tls/1 | ACME TLS-ALPN-01 | RFC 8737 |
stun.turn | STUN/TURN over TLS | RFC 7443 |
ACME TLS-ALPN-01 チャレンジ
Let’s Encrypt の DV 証明書発行で使われる TLS-ALPN-01 チャレンジは、ALPN を利用した検証方式です。RFC 8737(2020 年)で標準化されています。
- ACME サーバーがチャレンジトークンを発行する
- ACME クライアントがポート 443 で一時的な TLS サーバーを起動し、ALPN 識別子
acme-tls/1を設定する - ACME サーバーがそのドメインのポート 443 に TLS 接続し、
acme-tls/1で ALPN 交渉を行う - ACME クライアントがチャレンジトークンを含む自己署名証明書を返す
- ACME サーバーがトークンを検証し、ドメインの管理権限を確認する
HTTP-01 チャレンジはポート 80 を使いますが、TLS-ALPN-01 はポート 443 のみで完結します。CDN の背後にあるサーバーでポート 80 が使えない場合に有用です。
設定例
Nginx での HTTP/2 有効化と certbot での TLS-ALPN-01 チャレンジの設定を示します。
Nginx で HTTP/2 を有効化
Nginx 1.25.1 以降では listen ディレクティブの http2 パラメーターで HTTP/2 を有効化します。ALPN の交渉は Nginx が自動的に処理します。
server {
listen 443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
}
Nginx 1.25.0 以前では listen 443 ssl http2; の形式で指定していましたが、この書式は非推奨です。
certbot で TLS-ALPN-01 チャレンジを使用
certbot certonly --standalone --preferred-challenges tls-alpn -d example.com
--preferred-challenges tls-alpn を指定すると、TLS-ALPN-01 チャレンジを使用します。ポート 443 が他のプロセスに使われている場合は、一時的に停止する必要があります。
確認方法
openssl でサーバーの ALPN 対応を確認するには次のコマンドを使います。
openssl s_client -connect example.com:443 -servername example.com -alpn h2,http/1.1 </dev/null 2>/dev/null | grep "ALPN"
ALPN protocol: h2
ALPN protocol: h2 が表示されれば、サーバーは HTTP/2 に対応しており、ALPN で h2 が選択されています。http/1.1 が表示された場合はサーバーが HTTP/2 に対応していないか、無効化されています。
curl でも ALPN の結果を確認できます。
curl -vso /dev/null https://example.com 2>&1 | grep "ALPN"
* ALPN: server accepted h2
外部の視点からも確認したい場合は、Labee Dev Toolbox の SSL Cert API を使うと、外部の視点から見た結果を取得できます。
curl "https://labee.dev/api/ssl-cert?hostname=example.com"
{
"success": true,
"data": {
"hostname": "example.com",
"port": 443,
"reachable": true,
"status": 200
},
"error": null,
"meta": { "responseTime": 123 }
}
data.reachable が true であれば TLS ハンドシェイクが成功しています。ALPN の対応状況はこの API では確認できませんが、HTTPS 接続が確立できているかの基本確認として使えます。
よくある問題
ALPN に関連する設定ミスと接続トラブルの原因・対処法を整理します。
HTTP/2 が有効にならない
Nginx で HTTP/2 を有効にしたつもりでも、ブラウザーが HTTP/1.1 で接続するケースがあります。原因として、TLS 1.2 以上が有効になっていない場合があります。HTTP/2 は ALPN を必須としており、ALPN は TLS 拡張であるため TLS 接続が前提です。ssl_protocols に TLSv1.2 以上が含まれていることを確認します。
OpenSSL のバージョンが古い場合も ALPN が機能しません。ALPN をサポートするには OpenSSL 1.0.2 以降が必要です。
TLS-ALPN-01 チャレンジがファイアウォールで失敗
TLS-ALPN-01 チャレンジは対象ドメインのポート 443 に Let’s Encrypt のサーバーが接続します。ファイアウォールでインバウンドのポート 443 が制限されている場合、チャレンジが失敗します。ファイアウォールを開放できない場合は DNS-01 チャレンジに切り替えます。
ロードバランサーが ALPN を透過しない
L7 ロードバランサーが TLS を終端する構成では、ロードバランサーとバックエンドサーバー間で別途 ALPN 交渉が必要です。AWS ALB は HTTP/2 をフロントエンドでサポートしていますが、バックエンドへの通信は HTTP/1.1 にフォールバックします(2024 年時点)。バックエンドまで HTTP/2 を維持するには NLB と自前の TLS 終端が必要です。