
I Tried Receiving Amazon CloudFront's CloudFront-Viewer-ISP and CloudFront-Viewer-TLS-Details Headers at the Origin
This page has been translated by machine translation. View original
Introduction
In October 2026, Amazon CloudFront became able to forward two headers, CloudFront-Viewer-ISP and CloudFront-Viewer-TLS-Details, to the origin. From these headers, you can check the viewer's ISP name and TLS connection information such as the key exchange method, including PQC.
In this article, I attach an origin request policy with these two headers added to a distribution, and check the values that reached the origin in the access log.
Verification Method
Origin Request Policy
Save the following content as origin-request-policy.json. The two headers are specified in HeadersConfig. To keep the query parameter case in the origin's log, I set QueryStringBehavior to all.
{
"Name": "cfv-1031-viewer-isp-tls-details",
"Comment": "Add CloudFront-Viewer-ISP and CloudFront-Viewer-TLS-Details to origin requests",
"HeadersConfig": {
"HeaderBehavior": "whitelist",
"Headers": {
"Quantity": 2,
"Items": [
"CloudFront-Viewer-ISP",
"CloudFront-Viewer-TLS-Details"
]
}
},
"CookiesConfig": {
"CookieBehavior": "none"
},
"QueryStringsConfig": {
"QueryStringBehavior": "all"
}
}
aws cloudfront create-origin-request-policy \
--origin-request-policy-config file://origin-request-policy.json
If you specify the ID of the created policy in OriginRequestPolicyId in the distribution's cache behavior, the two headers will reach the origin.
Verification Environment
| Item | Value |
|---|---|
| Origin | EC2 running Amazon Linux 2023, nginx 1.30.6. The two headers are recorded in the access log |
| Connection to origin | Specify the public DNS name of an Elastic IP, using HTTP 80 |
| Distribution | Default domain (*.cloudfront.net) and default certificate. Viewer protocol is HTTPS only |
| Cache policy | Managed-CachingDisabled. Every request is forwarded to the origin |
| Client | Docker's alpine/openssl:latest (OpenSSL 3.5.9) with the key exchange group specified. The host's curl 8.18.0 (OpenSSL 3.5.9) connects without specifying a group |
| Connection source | One line in my working environment (one global IP) |
nginx log format (/etc/nginx/conf.d/viewer-log.conf)
log_format viewer escape=json
'{"time":"$time_iso8601",'
'"uri":"$request_uri",'
'"status":$status,'
'"viewer_isp":"$http_cloudfront_viewer_isp",'
'"viewer_tls_details":"$http_cloudfront_viewer_tls_details"}';
access_log /var/log/nginx/viewer.log viewer;
OpenSSL Client
I ran the following script inside a Docker container. The arguments are the CloudFront domain, the key exchange group, and the value of case used to tell requests apart in the log.
#!/bin/sh
set -eu
DOMAIN="$1"
GROUP="$2"
CASE="$3"
(
printf 'GET /?case=%s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n' \
"$CASE" "$DOMAIN"
sleep 3
) |
openssl s_client \
-connect "${DOMAIN}:443" \
-servername "$DOMAIN" \
-tls1_3 \
-groups "$GROUP" \
-brief 2>&1 |
grep -E 'Protocol version|Ciphersuite|Negotiated TLS1.3 group|Peer Temp Key|HTTP/1.1|x-cache|error|alert'
Here is an example run. The CloudFront domain is masked.
./openssl-request.sh dxxxxxxxxxxxxx.cloudfront.net X25519MLKEM768 pqc-x25519mlkem768
I keep standard input open with sleep 3. Without it, although the TLS connection information was displayed, the request was not recorded in the origin's log.
To also check a connection without a specified group, I sent one request from the host's curl.
Verification Results
CloudFront-Viewer-ISP
I used a KDDI-family ISP line for the verification. In the six requests sent from this one line, the value of CloudFront-Viewer-ISP was au one net in all cases. This value reached the origin as a string containing spaces, as is.
CloudFront-Viewer-ISP is added only when CloudFront can identify the ISP, so the origin side needs to take requests without the header into account.
CloudFront-Viewer-TLS-Details
This is the value that reached the origin for a connection using PQC (X25519MLKEM768). I formatted the access log record for presentation.
{
"tlsVersion": "TLSv1.3",
"cipherSuite": "TLS_AES_128_GCM_SHA256",
"handshakeType": "fullHandshake",
"keyExchange": "X25519MLKEM768",
"signatureScheme": "rsa_pss_rsae_sha256",
"signaturePublicKeyType": "rsa2048"
}
This is the value for a conventional connection (X25519 specified on the client).
{
"tlsVersion": "TLSv1.3",
"cipherSuite": "TLS_AES_128_GCM_SHA256",
"handshakeType": "fullHandshake",
"keyExchange": "x25519",
"signatureScheme": "rsa_pss_rsae_sha256",
"signaturePublicKeyType": "rsa2048"
}
In this distribution, which uses the default *.cloudfront.net certificate, the signature scheme and the certificate's public key type were the same values as in the conventional connection, even for the connection using PQC.
Values of keyExchange
The PQC key exchange methods supported by CloudFront are two: X25519MLKEM768 and SecP256r1MLKEM768, and they can be used only with TLS 1.3.
These are the results of five connections, each limited to a single key exchange group.
| Group specified by the client | Client-side display | keyExchange that reached the origin |
|---|---|---|
| X25519MLKEM768 | Negotiated TLS1.3 group: X25519MLKEM768 | X25519MLKEM768 |
| SecP256r1MLKEM768 | Negotiated TLS1.3 group: SecP256r1MLKEM768 | SecP256r1MLKEM768 |
| X25519 | Peer Temp Key: X25519, 253 bits | x25519 |
| prime256v1 | Peer Temp Key: ECDH, prime256v1, 256 bits | secp256r1 |
| secp384r1 | Peer Temp Key: ECDH, secp384r1, 384 bits | secp384r1 |
For reference, even with curl 8.18.0 (OpenSSL 3.5.9), which does not specify a key exchange group, X25519MLKEM768 was selected in this environment.
Summary
By adding CloudFront-Viewer-ISP and CloudFront-Viewer-TLS-Details to the origin request policy, I was able to check the viewer's ISP name and the key exchange method, including PQC, at the origin.
If you want to use the connection source's ISP or key exchange method for decisions or logging on the origin side, give this update a try.
