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.
- Enabling Cloudflare Automatic Signed Exchanges
- Inspecting Signed Exchanges
- What are the requirements for enabling SXGs?
- Signed Exchanges Limitations
- Cloudflare Automatic Signed Exchange FAQ, Bugs & Issues
- Testing Cloudflare Automatic Signed Exchanges
- Webpagetest Comparison
- Webpagetest Google Search Android Chrome Prefetch SXG Test
- Google Analytics Core Web Vitals
- 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.
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.
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:
- 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:
- 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.
- 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:
- 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:
- Hop-by-hop header fields listed in the Connection header field (Section 6.1 of [RFC7230]).
- Header fields listed in the no-cache response directive in the Cache-Control header field (Section 5.2.2.2 of [RFC7234]).
- 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:
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 SXGcert-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 tomax-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 removingmust-revalidate
from theCache-Control
header. Example verification against Google cached SXG version ofyourcfdomain.com
athttps://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:
- My WordPress blog is hosted in US West Coast, San Jose on a US$5/month KVM VPS server at Upcloud.com and I am testing from Brisbane, Australia on the Android 11 Chrome client end.
- This WordPress blog also is on a Cloudflare Enterprise plan utilising Cloudflare Argo Smart Routing, Argo Tiered caching and Cloudflare Load Balancer to VPS backend using Cloudflare Argo Tunnel with failover origin set as a Cloudflare Worker serverless HTML maintenance offline page and Cloudflare Worker based full HTML page caching combined with a fully optimized full HTML page caching at Centmin Mod Nginx level and origin server PHP-FPM is compiled with Profile Guide Optimization training for specific WordPress PHP usage loads to further boost PHP performance (PHP 8 vs 7 with/without PGO).
- Essentially, if you could fully utilise and optimize Cloudflare’s products and services + fully optimize your origin backend stack, then this would be the result. Disclaimer: I am an official Cloudflare Community MVP since 2018 which is a non-paid position similar to the Microsoft MVP program. However, I’ve been a Cloudflare customer and user for over 10yrs! So check out the Cloudflare Community forum 😉
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
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.
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.
- SXG enabled Webpagetest result with median run #6 https://www.webpagetest.org/result/211012_AiDc7R_88394ce92c87c9cb827fe47acdb2ded0/
- non-SXG Webpagetest result with medium run #7 https://www.webpagetest.org/result/211012_BiDcDC_6601d8a5dbbb8d40df0da3413a53481a/
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
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
The SXG enabled Webpagetest Waterfall
The non-SXG enabled Webpagetest
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
Time To First Byte (TTFB)
SXG TTFB was faster than non-SXG
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.
Largest Contentful Paint (LCP)
non-SXG LCP was faster than SXG
DOM Content Load Time
non-SXG DOM Content Load time was faster than SXG
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!
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.
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
From my Google DataStudio custom Core Web Vital metric dashboard with Google Analytics data source tracking LCP and TTFB over time.
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.
My Centmin Mod community forum’s Chrome User Experience report (CRuX) origin Core Web Vital metrics.
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.
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.
- https://web.dev/signed-exchanges/
- Cloudflare Community forum thread: Automatic Signed Exchanges (SXGs) Beta Launch