Testing Page Speed With Cloudflare Automatic Signed Exchanges & Google Search Cache

During Cloudflare Speed Week 2021, Cloudflare announced Cloudflare Automatic Signed Exchanges (SXGs) which improve page load speed and Google Core Web Vital metrics like Largest Contentful Paint (LCP) by allowing Cloudflare to serve up a signed cached version of your web page to Google search engine crawler which can then prefetch the cached version to their own local cache and to the visitor’s web browser local cache.

The Cloudflare Signed Exchange cached content is verified against your website’s SSL certificate so that even if a third-party like Google serves up the SXG cached version, it can still have the content attributed to your site’s domain name which is displayed in the web browser’s URL bar.

  1. Enabling Cloudflare Automatic Signed Exchanges
  2. Inspecting Signed Exchanges
  3. What are the requirements for enabling SXGs?
  4. Signed Exchanges Limitations
  5. Cloudflare Automatic Signed Exchange FAQ, Bugs & Issues
  6. Testing Cloudflare Automatic Signed Exchanges
  7. Webpagetest Comparison
  8. Webpagetest Google Search Android Chrome Prefetch SXG Test
  9. Google Analytics Core Web Vitals
  10. Further reading

Enabling Cloudflare Automatic Signed Exchanges

To enable Cloudflare Automatic Signed Exchanges, go to your Cloudflare domain zone’s Speed tab and find it under the Optimization tab section – currently, the product is in beta phase. Currently, Signed Exchanges are supported by Chromium-based browsers starting with versions: Chrome 73, Edge 79, Opera 64, Android 94, Opera Mobile 94, and Samsung Internet browser 11.1.

Cloudflare’s description:

Improve your website’s performance by making cacheable resources available on Google’s Signed Exchanges. Enable Chromium based browsers to prefetch your website on Google’s search results page and make your website faster. Improve the Largest Contentful Paint (LCP) which is part of the Core Web Vitals and increase your SEO ranking.

Note: This functionality is available for the domains proxied through Cloudflare. Please ensure that the proxy status is set to Cloudflare (orange cloud).

Warning: This is a Beta product. To see the requirements and limitations, please click on Help. If you experience any problem, we kindly ask you to disable it and let us know.

Once Cloudflare Automatic Signed Exchanges (SXGs) is enabled and provided your web site meets all the below outlined requirements for SXG caching/usage, you should be able to check if it’s working by doing the same Chrome (or supported browser) tests or simply by using Google Search Console and do a Mobile Usability Live URL Inspection of a URL link on your site and inspect the MORE INFO -> HTTP Response (screenshot below) to check if content-encoding: mi-sha256-03 is displayed.

Google Search Console Mobile Usability URL Inspection of HTTP Response headers

There is also a digest header to look for in HTTP Response which indicates that Google Search Console’s mobile Android crawler with User Agent = Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) was served an SXG generated version of your page URL link by Cloudflare:

content-encoding: mi-sha256-03

digest: mi-sha256-03=mU9iQL8WEvT97RPbU6UQHoKMcn0nuE81A0iEg7Ei4zg=


Inspecting Signed Exchanges

We can also inspect the Cloudflare Sign Exchanges enabled site’s SXG generated information at both the Google cached and Cloudflare edge server end using Golang based dump-signedexchange tool. Below SSH commands will install the dump-signedexchange tool – the shell script and some commands are specific for Centmin Mod LEMP stack systems.

# install latest Go version for Centmin Mod LEMP stack systems
/usr/local/src/centminmod/addons/golang.sh install

# disable Centmin Mod default ccache compiler caching
export CC='gcc'

# install dump-signedexchange
go install github.com/WICG/webpackage/go/signedexchange/cmd/dump-signedexchange@latest

Then you can inspect the SXG generated request and response headers and signatures etc:

Updated: The latest dump-signedexchange tool now natively supports Signed Exchange verification

dump-signedexchange -requestHeader 'Accept: application/signed-exchange;v=b3' -uri https://blog.centminmod.com/ -verify -payload=false
format version: 1b3
request:
  method: GET
  uri: https://blog.centminmod.com/
  headers:
response:
  status: 200
  headers:
    Digest: mi-sha256-03=WjDNkrP5retqyEjxp61BYTOfNqzwE71AmBkjaLaoTrI=
    Expires: Wed, 05 Oct 2022 17:37:10 GMT
    Report-To: {"group":"default","max_age":31536000,"endpoints":[{"url":"https://centminmodcom.report-uri.com/a/d/g"}],"include_subdomains":true}
    Last-Modified: Tue, 04 Oct 2022 17:21:36 GMT
    Cf-Cache-Status: HIT
    X-Ua-Compatible: IE=edge
    Vary: Accept-Encoding
    Cf-Cachetime: 2592000
    Content-Type: text/html; charset=UTF-8
    Cache-Control: public, max-age=86400, s-maxage=86400, stale-while-revalidate=60
    X-Frame-Options: SAMEORIGIN
    Age: 813
    Server: cloudflare
    Cf-Req-Country: CA
    Referrer-Policy: strict-origin-when-cross-origin
    Content-Encoding: mi-sha256-03
    Permissions-Policy: interest-cohort=(), accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()
    Cf-Ray: 754faf1980af4bc5-YUL
    Date: Tue, 04 Oct 2022 17:37:10 GMT
    X-Powered-By: centminmod
    Cf-Index-Rule: 1
    X-Xss-Protection: 1; mode=block
    X-Content-Type-Options: nosniff
    Nel: {"report_to":"default","max_age":31536000,"include_subdomains":true}
signature: sig;sig=*MEQCIB+h+keBbUBw0mLxWpWCk04RXB80XQZHmsUkTXh6UoVGAiBkU8qFn2OLvkMsxDhDMiDQ5/Sb4B+jIK2bBCeGlupo7A==*;integrity="digest/mi-sha256-03";cert-url="https://blog.centminmod.com/cdn-fpw/sxg/cert.pem.msg.xroh1qa53ywIys_xWfDAi_da2PcAO1lbiQwvs9cc5Kg";cert-sha256=*xroh1qa53ywIys/xWfDAi/da2PcAO1lbiQwvs9cc5Kg=*;validity-url="https://blog.centminmod.com/cdn-fpw/sxg/valid.msg.validity";date=1664901431;expires=1664991431
header integrity: sha256-gbgTVTIThuWu8+9DNAkbniXwwr7HwW/ZCSUYd8RRkgM=

The exchange has a valid signature.

If Signed Exchanges isn’t working, you’d get the following error messages for native dump-signedexchange command tool and curl passthrough respectively.

dump-signedexchange -requestHeader 'Accept: application/signed-exchange;v=b3' -uri https://domain.com/ -verify -payload=false
2022/10/04 16:55:44 GET "https://domain.com/" responded with unexpected content type "text/html; charset=UTF-8"

and

curl -sH 'Accept: application/signed-exchange;v=b3' https://domain.com/ | dump-signedexchange -payload=false
2022/10/04 17:00:21 signedexchange: unknown magic bytes: [60 33 68 79 67 84 89 80]

At Google SXG cached address https://blog-centminmod-com.webpkgcache.com/doc/-/s/blog.centminmod.com/ . Notice the Date and Expires header values indicate Google set the SXG cached version to have a cache lifetime of 1 day or 86400 seconds as indicated by my original pages Cache-Control header max-age value of 86400 seconds. This is how you control the Google SXG cache lifetime for your pages.

curl -sH 'Accept: application/signed-exchange;v=b3' https://blog-centminmod-com.webpkgcache.com/doc/-/s/blog.centminmod.com/ | dump-signedexchange -payload=false
format version: 1b3
request:
  method: GET
  uri: https://blog.centminmod.com/
  headers:
response:
  status: 200
  headers:
    Nel: {"report_to":"default","max_age":31536000,"include_subdomains":true}
    Digest: mi-sha256-03=UNdooA20ctxN/v2Kcmwthij7F5HVQnAYbziOngDvLkI=
    Expect-Ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
    Cache-Control: public, max-age=86400, s-maxage=86400, stale-while-revalidate=60
    Cf-Req-Country: US
    X-Frame-Options: SAMEORIGIN
    Content-Encoding: mi-sha256-03
    Permissions-Policy: interest-cohort=(), accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()
    Content-Security-Policy-Report-Only: snipped;
    Cf-Ray: 69e1759f84e10f42-DFW
    Content-Type: text/html; charset=UTF-8
    Cf-Cache-Status: HIT
    Cf-Default-Rule: 1
    Expires: Fri, 15 Oct 2021 14:22:22 GMT
    Report-To: {"group":"default","max_age":31536000,"endpoints":[{"url":"https://centminmodcom.report-uri.com/a/d/g"}],"include_subdomains":true}
    X-Powered-By: centminmod
    Referrer-Policy: strict-origin-when-cross-origin
    X-Ua-Compatible: IE=edge
    X-Xss-Protection: 1; mode=block
    X-Content-Type-Options: nosniff
    Age: 36262
    Date: Thu, 14 Oct 2021 14:22:22 GMT
    Vary: Accept-Encoding
    Server: cloudflare
    Cf-Cachetime: 2592000
signature: sig;cert-sha256=*xgT7kwIC/EZrF36dMw4l/MtTiU13jhQju7Kh8tyo2MI=*;cert-url="https://blog-centminmod-com.webpkgcache.com/crt/xgT7kwIC_EZr/s/blog.centminmod.com/cdn-fpw/sxg/cert.pem.msg";date=1634217743;expires=1634307743;integrity="digest/mi-sha256-03";sig=*MEQCIFA8gxVhsq3f0zA8hHZq94V1kZeHl830i3dMlCdXm7ZfAiByGhomhDhBE5okCAnPYQJb3xSe+Ux4JkYsQlfhSpcayg==*;validity-url="https://blog.centminmod.com/cdn-fpw/sxg/valid.msg"
header integrity: sha256-yaQ6ieq4bE2aPPArm3Of/YN5TA7oGn3mUPKGMRDoliA=

Or at Cloudflare edge server address https://blog.centminmod.com/. Again notice the Date and Expires header values indicate Google set the SXG cached version to have a cache lifetime of 1 day or 86400 seconds as indicated by my original pages Cache-Control header max-age value of 86400 seconds. This is how you control the Google SXG cache lifetime for your pages.

curl -sH 'Accept: application/signed-exchange;v=b3' https://blog.centminmod.com/ | dump-signedexchange -payload=false
format version: 1b3
request:
  method: GET
  uri: https://blog.centminmod.com/
  headers:
response:
  status: 200
  headers:
    Cache-Control: public, max-age=86400, s-maxage=86400, stale-while-revalidate=60
    Cf-Cache-Status: HIT
    X-Frame-Options: SAMEORIGIN
    X-Xss-Protection: 1; mode=block
    Age: 38252
    Nel: {"report_to":"default","max_age":31536000,"include_subdomains":true}
    Expect-Ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
    X-Powered-By: centminmod
    Permissions-Policy: interest-cohort=(), accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()
    Content-Type: text/html; charset=UTF-8
    X-Ua-Compatible: IE=edge
    Content-Encoding: mi-sha256-03
    Date: Thu, 14 Oct 2021 15:03:48 GMT
    Vary: Accept-Encoding
    Cf-Ray: 69e1b250c60318b4-EWR
    Cf-Cachetime: 2592000
    Report-To: {"group":"default","max_age":31536000,"endpoints":[{"url":"https://centminmodcom.report-uri.com/a/d/g"}],"include_subdomains":true}
    Cf-Req-Country: CA
    X-Content-Type-Options: nosniff
    Referrer-Policy: strict-origin-when-cross-origin
    Content-Security-Policy-Report-Only: snipped;
    Digest: mi-sha256-03=UNdooA20ctxN/v2Kcmwthij7F5HVQnAYbziOngDvLkI=
    Server: cloudflare
    Expires: Fri, 15 Oct 2021 15:03:48 GMT
    Cf-Default-Rule: 1
signature: sig;sig=*MEQCIDSpy5StJC1+MVjK/PadLK3DxSp0zbjeIBhMAS05WoxQAiBfpjnj9JcicfvqGs83YeoCz49xgrX3zhjbb9LLxiUnvw==*;integrity="digest/mi-sha256-03";cert-url="https://blog.centminmod.com/cdn-fpw/sxg/cert.pem.msg";cert-sha256=*xgT7kwIC/EZrF36dMw4l/MtTiU13jhQju7Kh8tyo2MI=*;validity-url="https://blog.centminmod.com/cdn-fpw/sxg/valid.msg";date=1634220229;expires=1634310229
header integrity: sha256-dnuPaLudKG6HEAU+8qisfJEBQoKxtqMgS2u18X2BRBU=


Web site’s SXG certificate (cert-url) in Concise Binary Object Representation (CBOR) JSON binary format at
https://blog.centminmod.com/cdn-fpw/sxg/cert.pem.msg served via the required application/cert-chain+cbor mime content type. Has an Expires epoch time = 1634766124000 which converts to human readable format as Wednesday, October 20, 2021 9:42:04 PM which is approximately 6 days from now.

curl -s -i -H 'Accept: application/signed-exchange;v=b3' https://blog.centminmod.com/cdn-fpw/sxg/cert.pem.msg
HTTP/2 200 
date: Thu, 14 Oct 2021 15:29:26 GMT
content-type: application/cert-chain+cbor
content-length: 2622
cf-ray: 69e1d7dbb8140c95-EWR
accept-ranges: bytes
age: 562
expires: 1634766124000
cf-cache-status: HIT
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
set-cookie: __cf_bm=hyxG8.3eeXrG8kDuWXtK436tz705kExtmNLGaXYy.wo-1634225366-0-AZZSi3242xVgOW6w9iQByE4+iVRkZ5KnFmmCEII5cbB6rQh9vhk7Ck2gYqHf2uGqoqxK53L11UPz/n51yrsqY28a0JyvkPVRyIsFsgWrpp6U; path=/; expires=Thu, 14-Oct-21 15:59:26 GMT; domain=.centminmod.com; HttpOnly; Secure; SameSite=None
server: cloudflare

You can also inspect the Google cached SXG certificate’s cert-url file with the help of dump-signedexchange. First use dump-signedexchange command to output the info in JSON format using --json flag and pipe it through jq tool to get the location of Google’s cached SXG certificate’s cert-url. Here cert-url location is at https://blog-centminmod-com.webpkgcache.com/crt/xgT7kwIC_EZr/s/blog.centminmod.com/cdn-fpw/sxg/cert.pem.msg

curl -sH 'Accept: application/signed-exchange;v=b3' https://blog-centminmod-com.webpkgcache.com/doc/-/s/blog.centminmod.com/ | dump-signedexchange -payload=false --json | jq -r '.Signatures[] | .Params."cert-url"'
https://blog-centminmod-com.webpkgcache.com/crt/xgT7kwIC_EZr/s/blog.centminmod.com/cdn-fpw/sxg/cert.pem.msg

Then inspect the Google cached cert-url. Seems Google caches the SXG certificate for 1hr (3600 seconds):

curl -s -i -H 'Accept: application/signed-exchange;v=b3' https://blog-centminmod-com.webpkgcache.com/crt/xgT7kwIC_EZr/s/blog.centminmod.com/cdn-fpw/sxg/cert.pem.msg
HTTP/2 200 
nel: {"report_to":"nel","max_age":604800,"success_fraction":0.05}
report-to: {"group":"nel","max_age":604800,"endpoints":[{"url":"https://beacons.gcp.gvt2.com/nel/upload-nel"},{"url":"https://beacons.gvt2.com/nel/upload-nel"}]}
report-to: {"group":"webpkgcache-team","max_age":2592000,"endpoints":[{"url":"https://csp.withgoogle.com/csp/report-to/webpkgcache-team"}]}
accept-ranges: bytes
content-type: application/cert-chain+cbor
content-security-policy: require-trusted-types-for 'script'; report-uri https://csp.withgoogle.com/csp/webpkgcache-team
cross-origin-resource-policy: cross-origin
cross-origin-opener-policy-report-only: same-origin; report-to="webpkgcache-team"
content-length: 2622
date: Thu, 14 Oct 2021 15:42:24 GMT
expires: Thu, 14 Oct 2021 15:42:24 GMT
cache-control: private, max-age=3600
last-modified: Thu, 14 Oct 2021 15:32:09 GMT
x-content-type-options: nosniff
server: sffe
x-xss-protection: 0
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"

When Google Search crawler hits a SXG enabled web site, it will check the Cloudflare generated SXG certificate cert-url at https://blog.centminmod.com/cdn-fpw/sxg/cert.pem.msg and then save a local Google cached version for 1hr (3600 seconds).  You can check your Cloudflare dashboard’s Web Analytics filtering paths containing cert.pem.msg to see the SXG certificate is being served from Cloudflare’s end.

Cloudflare Web Analytics

If you are on Cloudflare Enterprise plans, you can also access your Cloudflare edge server side logs via Cloudflare Logpush to dig deeper and query those requests too. I wrote a shell script to be able to query and parse my Cloudflare edge server logs and filter on various parameters – for all IP requests with a Bot Management score <=100 and matching the Useragent = GoogleBot for domain + path = blog.centminmod.com/cdn-fpw/sxg/cert.pem.msg for just October 14, 2021. Seems like all the GoogleBot crawlers are using Android 6.0.1 Useragent and hitting the Cloudflare Dallas (DFW) datacenter.

./cflog-parser.sh parse blog.centminmod.com/cdn-fpw/sxg/cert.pem.msg allips 20211014 100 none GoogleBot

h=blog.centminmod.com
ip=allips
datedir=20211014
botscore=100
firewall=none
path=/cdn-fpw/sxg/cert.pem.msg
ua=GoogleBot
originstatus=
edgestatus=
/usr/bin/pzcat /home/cfcmm-logs/20211014/*.log.gz | jq -r --arg u $ua --arg host $h --arg bs $botscore --arg reqpath $path 'select(.BotScore <=($bs | tonumber) and .ClientRequestHost == $host and .ClientRequestPath == $reqpath and (.ClientRequestUserAgent | test($u; "i"))) | "\(.EdgeStartTimestamp) \(.ClientIP) \(.RayID) \(.ParentRayID) \(.ClientRequestURI) \(.ClientRequestMethod) \(.ClientRequestReferer) \(.EdgeResponseStatus) \(.OriginResponseStatus) \(.EdgeRequestHost) \(.EdgeColoCode)-coloid-\(.EdgeColoID) \(.ClientCountry) \(.ClientIPClass) [\(.WorkerStatus)-\(.WorkerSubrequest)-\(.WorkerSubrequestCount)-\(.WorkerCPUTime)] \(.EdgePathingOp)-\(.EdgePathingSrc)-\(.EdgePathingStatus)-\(.EdgeRateLimitAction) \(.FirewallMatchesActions):\(.FirewallMatchesRuleIDs):\(.FirewallMatchesSources) \(.WAFAction)-\(.WAFRuleID) \(.BotScore) x \(.BotScoreSrc) \(.ClientRequestUserAgent) ctf-\(.CacheTieredFill) cs-\(.CacheCacheStatus) crs-\(.CacheResponseStatus) d-\(.ClientDeviceType) edgeip-\(.EdgeServerIP)"'

2021-10-14T00:20:34Z 66.249.65.18 69dca485382e2893 00 /cdn-fpw/sxg/cert.pem.msg GET  200 0 blog.centminmod.com DFW-coloid-15 us searchEngine [ok-false-0-0] wl-macro-se- ["allow"]:["fwruleid"]:["firewallRules"] unknown- 1 x Verified Bot Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) ctf-false cs-miss crs-200 d-mobile edgeip-
2021-10-14T00:50:09Z 66.249.65.22 69dccfdbcbf92893 00 /cdn-fpw/sxg/cert.pem.msg GET  200 0 blog.centminmod.com DFW-coloid-15 us searchEngine [ok-false-0-0] wl-macro-se- ["allow"]:["fwruleid"]:["firewallRules"] unknown- 1 x Verified Bot Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) ctf-false cs-hit crs-200 d-desktop edgeip-
2021-10-14T03:22:45Z 66.249.65.18 69ddaf632ee80e5e 00 /cdn-fpw/sxg/cert.pem.msg GET  200 0 blog.centminmod.com DFW-coloid-15 us searchEngine [ok-false-0-0] wl-macro-se- ["allow"]:["fwruleid"]:["firewallRules"] unknown- 1 x Verified Bot Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) ctf-false cs-miss crs-200 d-mobile edgeip-
2021-10-14T07:41:12Z 66.249.65.8 69df29fa99ae287f 00 /cdn-fpw/sxg/cert.pem.msg GET  200 0 blog.centminmod.com DFW-coloid-15 us searchEngine [ok-false-0-0] wl-macro-se- ["allow"]:["fwruleid"]:["firewallRules"] unknown- 1 x Verified Bot Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) ctf-false cs-miss crs-200 d-desktop edgeip-
2021-10-14T07:54:06Z 66.249.65.22 69df3ce0a9170e4e 00 /cdn-fpw/sxg/cert.pem.msg GET  200 0 blog.centminmod.com DFW-coloid-15 us searchEngine [ok-false-0-0] wl-macro-se- ["allow"]:["fwruleid"]:["firewallRules"] unknown- 1 x Verified Bot Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) ctf-false cs-hit crs-200 d-desktop edgeip-
2021-10-14T10:02:38Z 66.249.65.20 69dff9245df86743 00 /cdn-fpw/sxg/cert.pem.msg GET  200 0 blog.centminmod.com DFW-coloid-15 us searchEngine [ok-false-0-0] wl-macro-se- ["allow"]:["fwruleid"]:["firewallRules"] unknown- 1 x Verified Bot Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) ctf-false cs-miss crs-200 d-mobile edgeip-
2021-10-14T11:37:31Z 66.249.65.8 69e0842438d40c13 00 /cdn-fpw/sxg/cert.pem.msg GET  200 0 blog.centminmod.com DFW-coloid-15 us searchEngine [ok-false-0-0] wl-macro-se- ["allow"]:["fwruleid"]:["firewallRules"] unknown- 1 x Verified Bot Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) ctf-false cs-hit crs-200 d-desktop edgeip-
2021-10-14T11:59:54Z 66.249.65.20 69e0a4ee3daf0f36 00 /cdn-fpw/sxg/cert.pem.msg GET  200 0 blog.centminmod.com DFW-coloid-15 us searchEngine [ok-false-0-0] wl-macro-se- ["allow"]:["fwruleid"]:["firewallRules"] unknown- 1 x Verified Bot Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) ctf-false cs-hit crs-200 d-desktop edgeip-
2021-10-14T13:27:36Z 66.249.65.20 69e125628cfc0bf7 00 /cdn-fpw/sxg/cert.pem.msg GET  200 0 blog.centminmod.com DFW-coloid-15 us searchEngine [ok-false-0-0] wl-macro-se- ["allow"]:["fwruleid"]:["firewallRules"] unknown- 1 x Verified Bot Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) ctf-false cs-miss crs-200 d-mobile edgeip-
2021-10-14T14:01:55Z 66.249.65.8 69e157abe8100f0a 00 /cdn-fpw/sxg/cert.pem.msg GET  200 0 blog.centminmod.com DFW-coloid-15 us searchEngine [ok-false-0-0] wl-macro-se- ["allow"]:["fwruleid"]:["firewallRules"] unknown- 1 x Verified Bot Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) ctf-false cs-hit crs-200 d-desktop edgeip-

What are the requirements for enabling SXGs?

There are certain requirements and limitations for Cloudflare Signed Exchanges to work – pay attention to the Browser cache TTL requirement that requires your content to have been cached for at least 2 minutes (120 seconds) which you can control either from Cloudflare dashboard or via your origin server’s Cache-Control header set max-age response headers:

  • Automatic Signed Exchanges (SXGs) is available for the zones with a Pro or Higher plan, or for the zones on a Free plan with the APO subscription.
  • SXGs only works with zones that are using Cloudflare Nameservers with orange clouded DNS records.
  • Note: To change your domain nameservers, please follow the instructions in this article. Then, turn on the Cloudflare proxy (orange cloud) for any relevant DNS record.
  • The certificates for the zone need to be managed by Cloudflare.
    • Note: If someone wants to obtain a SXG certificate, they need to add a certain CAA Record for issuance. Cloudflare does that on behalf of the Users upon the enablement of SXGs. These CAA DNS records that Cloudflare adds do not actually show up on your domain zone’s DNS tab though. Cloudflare adds these records to make sure issuance is not blocked. Cloudflare adds CAA DNS records for Digicert, Letsencrypts and Comodo Certificate Authority providers. If you obtain SSL certificates for your Cloudflare domain zone from other CA providers, you’ll need to add them to your CAA DNS records manually. For me I had to add them for ZeroSSL/Sectigo, Amazon AWS, SSL.com, Buypass SSL and Google Trust Services for their new Google Cloud Public Certificate Authority (CA) SSL certificate offerings:
      0 issue "sectigo.com"
      0 issuewild "sectigo.com"
      0 issue "buypass.com"
      0 issuewild "buypass.com"
      0 issue "buypass.no"
      0 issuewild "buypass.no"
      0 issue "ssl.com"
      0 issuewild "ssl.com"
      0 issue "amazon.com"
      0 issuewild "amazon.com"
      0 issue "amazontrust.com"
      0 issuewild "amazontrust.com"
      0 issue "awstrust.com"
      0 issuewild "awstrust.com"
      0 issue "amazonaws.com"
      0 issuewild "amazonaws.com"
      0 issue "digicert.com; cansignhttpexchanges=yes"
      0 issuewild "digicert.com; cansignhttpexchanges=yes"
      0 issue "comodoca.com"
      0 issue "letsencrypt.org"
      0 issuewild "comodoca.com"
      0 issuewild "letsencrypt.org"
      0 issue "pki.goog"
      0 issuewild "pki.goog"
      

      Cloudflare DNS dashboard: Cloudflare CAA DNS dashboard entries

  • Content needs to be cached every 120 seconds or longer.
    • Note: Google checks to make sure that content is cached over two minutes to generate an SXG. Please ensure that content is cached every two minutes or longer: Dashboard > Caching > Configuration > Browser Cache TTL. Or you can set your Cache-Control max-age headers at your origin web server level. There are other cache control headers Cloudflare is working on to support – see Limitations section.
  • AMP Real URL needs to be disabled if SXGs is enabled: Dashboard > Speed > Optimization > AMP Real URL

Limitations

  • Currently, signed exchanges are used by Google for Android search.
  • Google does not load signed exchanges for all search results on a page, only a selection (see the list of headers below). As of November 23, 2021, seems like only regularly Google search results are eligible for prefetching SXG versions. Google search’s Featured Snippets currently are not included SXG prefetch feature. But there are plans to include Google search’s Featured Snippets for Signed Exchanges caching and prefetching.
  • Signed exchanges can strip out cookies and certain headers, and create problems with dynamic content.
  • According to Cloudflare Automatic Signed Exchanges Product manager, Firat, the following uncached headers and cookies may be stripped out during the signed exchange generation:
    1. Hop-by-hop header fields listed in the Connection header field (Section 6.1 of [RFC7230]).
    2. Header fields listed in the no-cache response directive in the Cache-Control header field (Section 5.2.2.2 of [RFC7234]).
    3. Header fields defined as hop-by-hop: Connection, Keep-Alive, Proxy-Connection Trailer, Transfer-Encoding, Upgrade
  • Since Cloudflare cannot be sure whether a signed exchange does or does not include private information, a signed exchange will not be generated in the presence of the following headers:
    1. Authentication-Control, Authentication-Info, Clear-Site-Data, Optional-WWW-Authenticate, Proxy-Authenticate, Proxy-Authentication-Info, Public-Key-Pins, Sec-WebSocket-Accept, Set-Cookie, Set-Cookie2, SetProfile, Strict-Transport-Security, Vary, WWW-Authenticate
  • Google SXG cache requirements outline a freshness lifetime of at least 120 seconds and rfc7234 (4.2.1) outlines how you can configure that cache time. Google will look at the following headers in this order:
    • If the cache is shared and the s-maxage response directive (Section 5.2.2.9) is present, use its value, or
    • If the max-age response directive (Section 5.2.2.8) is present, use its value, or
    • If the Expires response header field (Section 5.3) is present, use its value minus the value of the Date response header field, or
    • Otherwise, no explicit expiration time is present in the response. A heuristic freshness lifetime might be applicable; see Section 4.2.2.

    However, according to Firat, Cloudflare Automatic Signed Exchanges only support using max-age right now. Cloudflare does intend to add support for s-maxage, expires and cdn-cache-control headers.

Cloudflare Automatic Signed Exchange FAQ, Bugs & Issues

  • In the web browser developer console, I see errors for Content type of cert-url must be application/cert-chain+cbor? This is fine, it’s a poorly worded message to indicate a cache miss for the SXG cert-url when the website gets an HTTP 200 status with a Location header and without a Warning header; it should be temporary until the URL is cached.
  • What are these requests for /.well-known/sxg-map? According to Cloudflare, this is for an unreleased/in-development feature. It’s safe to block those requests, and the Users can ignore them.
  • Google Search Console AMP pages report the error: The dates for the signed exchange are invalid. It’s ok to ignore this as Google can still parse the signed exchange and treat it like normal HTML, so it falls back to earlier behaviour. In this case, it’s an AMP page, so it should fall back to AMP behaviour.
  • Google side bug with the incorrect interpretation of Cache-Control: must-revalidate as equivalent to max-age=0 causing Signed Exchanges to not work with a SXG warning header message when validating against the Google cached SXG version: Warning: 199 - "debug: content has ingestion error: Error fetching resource: Content is not cache-able or cache-able lifetime is too short". Such warning headers usually indicate the SXG did not meet the Google caching requirements. For now, you can workaround this by removing must-revalidate from the Cache-Control header.  Example verification against Google cached SXG version of yourcfdomain.com at https://yourcfdomain.com.webpkgcache.com/doc/-/s/yourcfdomain.com/ showing the error and doing a silent redirect to the non-SXG version of the site as fallback:
    curl -s -i -H 'Accept: application/signed-exchange;v=b3' https://yourcfdomain.com.webpkgcache.com/doc/-/s/yourcfdomain.com/
    HTTP/1.1 200 OK
    Location: https://yourcfdomain.com/
    Cache-Control: private
    X-Silent-Redirect: true
    Warning: 199 - "debug: content has ingestion error: Error fetching resource: Content is not cache-able or cache-able lifetime is too short"
    Content-Type: text/html; charset=UTF-8
    X-Content-Type-Options: nosniff
    Date: Tue, 12 Oct 2021 22:26:22 GMT
    Server: sffe
    Content-Length: 287
    X-XSS-Protection: 0
    Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
    

Testing Cloudflare Automatic Signed Exchanges

I decided to enable and test Cloudflare Automatic Signed Exchanges on this WordPress blog’s HTML index page document itself using a real Android 11 Chrome session via Android developer tools USB debugging. The Chrome Canary dev tool network screenshot below shows 6 URL requests in my mobile Chrome for just the HTML index document itself and the resulting response times.

WordPress Blog Server Configuration Notes:

request 1. On my Android 11 mobile device open up Google Chrome and do a Google search for a term I know that will list my site at the top of the search results and click the Google search link to my blog site – this resulted in Google returning 2x prefetch cached SXGs requests – 1st a 303 redirect to cached document which took 24ms and 2nd prefetched request took 3ms. Total time = 27ms

request 2-5. which starts at 3 through 5th request line on below screenshot – are just normal non-SXG Chrome F5 refresh page requests to compare which took between 65-69ms total time (upper value) or 37-49ms network component time (lower value)

request 6. I copy and pasted the Google served https://blog-centminmod-com.webpkgcache.com/doc/-/s/blog.centminmod.com/ SXG request URL from 1st time and visited that URL directly showing up as the last request in below screenshot with 12ms total response time

Android 11 Chrome testing Cloudflare Automatic Signed Exchanges

I’m a server and page speed performance addict and already optimized this WordPress blog for some fast page load speed response times. However, utilising Cloudflare Automatic Signed Exchanges cached/prefetch cached responses from Google Search cache and Chrome browser local cache are on another level – reducing response times for the HTML document itself from 60-69ms to 3-24ms! As you will read further on with Webpagetests, it’s this local Chrome browser cached SXG version serving that makes all the difference it seems. Outside of that local Chrome browser cache served requests, it seems Google cached version over the network may not be the fastest compared to an optimally configured Cloudflare CDN cached HTML page.

Seeing as Google Search crawler would pickup or prefetch a signed exchange cached version of your HTML pages, it would logical to check Google Search Console’s crawl request statistics for the average response time (milliseconds). Though the current limitations mean not all Google Search results will serve a signed exchange version and only Android searches would benefit.

This WordPress blog’s mobile and desktop Google Search crawler’s average response times. Cloudflare Automatic Signed Exchanges was enabled around October 8, 2021. Though this blog only gets around 10-15% mobile traffic while 85-90% is desktop based traffic.

Google Search Console crawler average response times on mobile

Google Search Console crawler average response times on desktop

Webpagetest Comparison

Next is doing a 7x run Webpagetest comparison on emulated Motorola G4 gen4 Android Chrome device on a 4G throttled connection using Virginia, USA EC2 test location. The results are interesting here as non-SXG page load speed was faster than SXG i.e. LCP times. This suggests above mentioned gains are only when Google Search prefetches the SXG cached version and populates it in Chrome web browser local cache first so that there is no network overhead in serving the page.

Webpagetest SXG vs non-SXG page load filmstrip

SXG enabled Webpagetest result’s HTML document page loading metrics

SXG enabled actually shows a faster Time To First Byte (TTFB) at 200ms vs 328ms and faster SSL negotiation time 188ms vs 228ms than non-SXG Cloudflare CDN cached result But content download time was much slower at 227ms vs 39ms . However, testing the SXG directly from Google Search cache seems to be over HTTP/1.1 while non-SXG Cloudflare CDN result was over HTTP/2. However, that doesn’t exactly explain we saw a better LCP time with non-SXG Cloudflare CDN than SXG version (see Webpagetest waterfalls further below) as all the rest of the CSS/Fonts/JS/image files were served by Cloudflare over HTTP/2. But if you check the Webpagetest request details, you can see for non-SXG Cloudflare test more same origin assets were able to load before start render stage compared to SXG request details.

URL: https://blog-centminmod-com.webpkgcache.com/doc/-/s/blog.centminmod.com/
Document: https://blog-centminmod-com.webpkgcache.com/doc/-/s/blog.centminmod.com/
Host: blog-centminmod-com.webpkgcache.com
IP: 142.251.45.1
Error/Status Code: 200
Priority: Highest
Protocol: http/1.1
HTTP/2 Stream: 1, weight 256, depends on 0, EXCLUSIVE
Request ID: 27EBFB0F2731A0EFFBC1C6C6A9B3A7F9
Discovered: 0.005 s
Request Start: 0.564 s
DNS Lookup: 195 ms
Initial Connection: 171 ms
SSL Negotiation: 188 ms
Time to First Byte: 200 ms
Content Download: 227 ms
Bytes In (downloaded): 37.0 KB
Uncompressed Size: 218.1 KB
Bytes Out (uploaded): 1.8 KB

and request details

Webpagetest SXG request details

non-SXG enabled Webpagetest result’s HTML document page loading metrics

URL: https://blog.centminmod.com/
Document: https://blog.centminmod.com/
Host: blog.centminmod.com
IP: 104.18.11.170
Error/Status Code: 200
Priority: Highest
Protocol: HTTP/2
HTTP/2 Stream: 1, weight 256, depends on 0, EXCLUSIVE
Request ID: D1BAAB714461A57E3AE26AEB25122B5D
Discovered: 0.004 s
Request Start: 0.577 s
DNS Lookup: 171 ms
Initial Connection: 172 ms
SSL Negotiation: 228 ms
Time to First Byte: 326 ms
Content Download: 39 ms
Bytes In (downloaded): 34.6 KB
Uncompressed Size: 214.7 KB
Bytes Out (uploaded): 1.6 KB
CPU Time: 38 ms

and request details

Webpagetest non-SXG request details

The SXG enabled Webpagetest Waterfall

Webpagetest SXG waterfall

The non-SXG enabled Webpagetest

Webpagetest non-SXG waterfall

Now lets plot and compare both SXG and non-SXG 7x runs and their median results for Web Vital metrics

Speedindex

non-SXG Speedindex – visual perceived render time was faster than SXG

Webpagetest SpeedIndex

Time To First Byte (TTFB)

SXG TTFB was faster than non-SXG

Webpagetest TTFB

SSL Negotiation Time

Average and mean SXG SSL negotiation time was faster than non-SXG – but seems that was pretty close and just that non-SXG had more variance for 1 of the 7x runs.

Webpagetest SSL negotiation times

Largest Contentful Paint (LCP)

non-SXG LCP was faster than SXG

Webpagetest Largest Contentful Paint times

DOM Content Load Time

non-SXG DOM Content Load time was faster than SXG

Webpagetest DOM Content Loaded times

Webpagetest Google Search Android Chrome Prefetch SXG Test

Google search also supports prefetching cached Signed Exchange files if available. I did a Webpagetest test for Google search for a known search term – https://www.google.com/search?q=cloudflare+automatic+signed+exchanges+centminmod that would show my WordPress blog’s Cloudflare generated Signed Exchange cached version to see if Google Android search would prefetch it and it did!

This was tested using Webpagetest Dulles, VA – Motorola G4 gen4 Android Chrome web browser. See the 8th and 9th requests are loading the Google search prefetched browser cached SXG version and the Google cached SXG certificate cert-url respectively. This was even before clicking through to the search result listing!

Google search prefetch SXG

Google search prefetch SXG

Google search prefetch SXG

Google search prefetch SXG

Here’s a Webpagetest generated video demo of Cloudflare Signed Exchange being prefetched when loading Google Searched term – https://www.google.com/search?q=cloudflare+automatic+signed+exchanges+centminmod – notice how after clicking on the Google search result listing’s link, the WordPress blog page loading instantly!

 

Here’s another example for Google Searched term for https://www.google.com/search?q=how+to+use+webpagetest+for+page+load+speed+testing+site%3Acommunity.centminmod.com for my official Centmin Mod Xenforo based community forum where Google prefetched and served a SXG cached version of a forum thread page.

Google search prefetched Xenforo forum SXG cached page

I can definitely see how Google Signed Exchange caching and prefetching of SXG generated version of web pages can improve Google Core Web Vital metrics like Largest Contentful Paint (LCP). Google even officially mentions using Signed Exchanges as one of the methods to optimize your LCP through improving your Time to First Byte (TTFB).

Before anything else, improve how and where your server handles your content. Use Time to First Byte (TTFB) to measure your server response times. You can improve your TTFB in a number of different ways:

  • Optimize your server
  • Route users to a nearby CDN
  • Cache assets
  • Serve HTML pages cache-first
  • Establish third-party connections early
  • Use signed exchanges

Google Analytics Core Web Vitals

I enabled Cloudflared Automatic Signed Exchanges around October 8, 2021 so enough time has passed to check my Google Analytic’s Core Web Vital metrics. I use Google’s Web Vitals library to allow me to send my real time visitor Core Web Vital metrics to Google Analytics. I only have a small proportion of mobile traffic to my Cloudflare zone domain site. So waiting for the day that Google Signed Exchanges supports desktop users and not just Android mobile users.

This WordPress blog doesn’t have that much traffic so I’ll provide an example of Centmin Mod community forum’s mobile numbers comparing Google Search referral traffic from Android Mobile non-SXG vs Android Mobile SXG cached sources for the period between October 8 to 26, 2021. I’ve also drilled down into Core Web Vital metrics comparing mobile vs tablet segments where tablet traffic is generally faster.

Google Android Search SXG cached referrals only account for 0.70% of my traffic and Google Android Search non-SXG referrals account for only 2.99% of my traffic. The Google Core Web Vital metrics of focus would be Largest Contentful Paint (LCP) and Time To First Byte (TTFB) and New Visitors who would most likely not already have any of my forum’s cached assets in the browser cache.

New Visitors – TTFB

  • Google Android Search SXG cached referrals: 623.63 ms (22% faster)
  • Google Android Search non-SXG referrals: 799.99 ms

New Visitors – LCP

  • Google Android Search SXG cached referrals: 1,193.81 ms (19.3% faster)
  • Google Android Search non-SXG referrals: 1,479.58 ms

Returning visitor metrics for Google Android Search non-SXG referrals may be higher as this includes traffic for my Centmin Mod community forums where returning visitors are more likely logged in forum members with repeat visits which bypass any Cloudflare CDN cache benefits usually applied to New visitors which are guest forum members. However, the Google Android Search SXG cached referrals are much faster and I believe this is where Google Search prefetch SXG cache comes into play as well compared to New visitor referrals where Google Search does not yet have a copy of the SXG cached version to serve.

Returning Visitors – TTFB

  • Google Android Search SXG cached referrals: 237.09 ms (76.85% faster)
  • Google Android Search non-SXG referrals: 1,024.23 ms

Returning Visitors – LCP

  • Google Android Search SXG cached referrals: 771.79 ms (55.98% faster)
  • Google Android Search non-SXG referrals: 1,753.44 ms

Google Analytics Core Web Vitals for SXG vs non-SXG Mobile traffic

Google Analytics Core Web Vitals for SXG vs non-SXG Mobile traffic

From my Google DataStudio custom Core Web Vital metric dashboard with Google Analytics data source tracking LCP and TTFB over time.

Google Datastudio Core Web Vital metrics

Google Datastudio Core Web Vital metrics

Google Datastudio comparison for Android Google Search for SXG cached source referrals vs non-SXG source referrals for LCP metric only. This is with both New and Return visitors combined.

Google Datastudio LCP metric comparison

My Centmin Mod community forum’s Chrome User Experience report (CRuX) origin Core Web Vital metrics.

Centmin Mod community forum CRuX origin Core Web Vitals

And CRuX LCP & TTFB metrics over time. Will be interesting to see how LCP & TTFB improve over time once Signed Exchanges extends to desktop and non-Android users.

CRuX TTFB CRuX LCP

If you liked this blog post or have comments or feedback, you can post the comments by clicking the Load Comments button at bottom of this page or follow me on Twitter.

Further reading