<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kalyan Gopalam</title>
    <description>The latest articles on DEV Community by Kalyan Gopalam (@scorpionxdev).</description>
    <link>https://dev.to/scorpionxdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1139554%2F2cbea57c-eb19-4c60-a2ac-cbf80d44318a.png</url>
      <title>DEV Community: Kalyan Gopalam</title>
      <link>https://dev.to/scorpionxdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/scorpionxdev"/>
    <language>en</language>
    <item>
      <title>Your browser speaks post-quantum. The server behind it probably doesn't.</title>
      <dc:creator>Kalyan Gopalam</dc:creator>
      <pubDate>Thu, 12 Nov 2026 17:51:48 +0000</pubDate>
      <link>https://dev.to/scorpionxdev/your-browser-speaks-post-quantum-the-server-behind-it-probably-doesnt-hhd</link>
      <guid>https://dev.to/scorpionxdev/your-browser-speaks-post-quantum-the-server-behind-it-probably-doesnt-hhd</guid>
      <description>&lt;p&gt;Chrome, Firefox and Safari all negotiate X25519MLKEM768 by default now. Cloudflare Radar puts the post-quantum share of HTTPS traffic at around 72%. So we're done?&lt;/p&gt;

&lt;p&gt;Not really. That number is the hop from your browser to the CDN edge. The hop from the edge to the origin, the server someone actually runs, is a separate TLS handshake with a separate TLS stack. Radar's own number for that hop is under 20%.&lt;/p&gt;

&lt;p&gt;I wanted to know &lt;em&gt;why&lt;/em&gt;, so I built a tool to work it out and pointed it at a lot of servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I measured
&lt;/h2&gt;

&lt;p&gt;Self-run origins (no big CDN in front), counted per (hostname, IP) pair, the same way Radar counts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tranco ranks 10k–1M (sample):&lt;/strong&gt; 22.2% post-quantum (95% CI 19.8–24.5)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tranco top 10k:&lt;/strong&gt; 23.3%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Top 10k with CDNs counted&lt;/strong&gt; (what a visitor actually lands on): 66.1%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 22% lined up almost exactly with Radar's 19.6% for customer origins, which was a nice sanity check that I wasn't measuring garbage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why they aren't post-quantum
&lt;/h2&gt;

&lt;p&gt;Of the 3,327 non-PQ origins in my top-10k run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;60%&lt;/strong&gt; speak TLS 1.3 but don't offer ML-KEM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;34%&lt;/strong&gt; don't speak TLS 1.3 at all&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3%&lt;/strong&gt; have a hand-written curve list pinned to P-256/P-384&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The part that surprised me came when I fingerprinted the TLS library behind each one. Only &lt;strong&gt;13&lt;/strong&gt; of those 3,327 were provably a config problem. The rest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Library (1,137):&lt;/strong&gt; OpenSSL 3.0–3.4 and 1.1.1 dominate, plus Go older than 1.24. None of these have ML-KEM, so no config line will help.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provider (759):&lt;/strong&gt; AWS load balancers (336), F5 BIG-IP (179), and a long tail. 545 of them sit behind a product that &lt;em&gt;already has&lt;/em&gt; a PQ setting that nobody turned on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unknown (1,381):&lt;/strong&gt; mostly TLS 1.2-only servers, which never send the reply that gives the library away.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(The 13 is a floor. A server only counts as "config" with positive proof its library has ML-KEM, and a hand-set group list hides that.)&lt;/p&gt;

&lt;p&gt;So "add the PQ group to your config" is the wrong advice for most servers. OpenSSL below 3.5 can't do it. You upgrade, or you put something in front that can: OpenSSL 3.5+, Go 1.24+, Caddy 2.10+, or cloudflared.&lt;/p&gt;

&lt;p&gt;If you &lt;em&gt;are&lt;/em&gt; on OpenSSL 3.5+ with nginx and still not PQ, look for an old curve line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="n"&gt;ssl_ecdh_curve&lt;/span&gt; &lt;span class="no"&gt;X25519MLKEM768&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="no"&gt;X25519&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;prime256v1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How you check without finishing a handshake
&lt;/h2&gt;

&lt;p&gt;TLS 1.3 negotiation happens in the clear, so you can learn a lot from the server's first reply alone.&lt;/p&gt;

&lt;p&gt;The tool builds every ClientHello by hand and sends seven probes, each changing one variable. The key shares are valid but inert: the X25519 base point, the P-256 generator, and an ML-KEM key made of zero coefficients plus a seed, which still passes FIPS 203's modulus check. The server replies with a ServerHello or a HelloRetryRequest, and that tells you what it accepts. The tool never sends Finished and never sends application data.&lt;/p&gt;

&lt;p&gt;One pair of probes is my favourite. A PQ ClientHello is about 1.4 KB versus about 250 bytes for a classical one, and some middleboxes choke on the big one. So one probe sends the PQ hello split at byte 200, and a control probe sends a classical hello padded to the same size and split the same way. If both fail, a middlebox is dropping big hellos. If only the PQ one fails, the server is rejecting the group.&lt;/p&gt;

&lt;h2&gt;
  
  
  A weird one: Google Cloud flapping
&lt;/h2&gt;

&lt;p&gt;Between two runs ten days apart, 15 origins appeared to &lt;em&gt;lose&lt;/em&gt; post-quantum. None of them actually regressed. I re-probed each one 11 times over 15 minutes, and 14 of them flapped: the same IP answered with ML-KEM on some probes and without it on others, minutes apart. Those 14 were all Google Cloud addresses, and Google Cloud's PQ share in my sample went from 15% to 43% across those same ten days. It looks a lot like a staged ML-KEM rollout on their load balancers.&lt;/p&gt;

&lt;p&gt;The lesson: confirm a regression with a second probe before you alert on it. The tool now reports a &lt;code&gt;mixed&lt;/code&gt; verdict for these instead of crying wolf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;It's called &lt;strong&gt;pqdoctor&lt;/strong&gt;, an MIT-licensed Rust CLI. It checks TLS, mail (STARTTLS) and SSH servers, tells you why they aren't post-quantum, and prints the fix for that server's software.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo install pqdoctor
pqdoctor yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's also on Homebrew and Scoop, and there's a GitHub Action that fails your build if an origin falls off PQ. Docs and an FAQ are at &lt;a href="https://pqdoctor.com" rel="noopener noreferrer"&gt;pqdoctor.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caveats:&lt;/strong&gt; one vantage point, IPv4 only, and these are servers &lt;em&gt;comparable&lt;/em&gt; to Cloudflare's origins, not Cloudflare's actual origins. What transfers is the mix of causes.&lt;/p&gt;

&lt;p&gt;If you run your own origin, what's in front of it, and is it post-quantum yet? I'm curious what people find.&lt;/p&gt;

</description>
      <category>security</category>
      <category>cryptography</category>
      <category>postquantum</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Your browser speaks post-quantum. The server behind it probably doesn't.</title>
      <dc:creator>Kalyan Gopalam</dc:creator>
      <pubDate>Thu, 12 Nov 2026 17:46:51 +0000</pubDate>
      <link>https://dev.to/flarebox/your-browser-speaks-post-quantum-the-server-behind-it-probably-doesnt-3i3a</link>
      <guid>https://dev.to/flarebox/your-browser-speaks-post-quantum-the-server-behind-it-probably-doesnt-3i3a</guid>
      <description>&lt;p&gt;Chrome, Firefox and Safari all negotiate X25519MLKEM768 by default now. Cloudflare Radar puts the post-quantum share of HTTPS traffic at around 72%. So we're done?&lt;/p&gt;

&lt;p&gt;Not really. That number is the hop from your browser to the CDN edge. The hop from the edge to the origin, the server someone actually runs, is a separate TLS handshake with a separate TLS stack. Radar's own number for that hop is under 20%.&lt;/p&gt;

&lt;p&gt;I wanted to know &lt;em&gt;why&lt;/em&gt;, so I built a tool to work it out and pointed it at a lot of servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I measured
&lt;/h2&gt;

&lt;p&gt;Self-run origins (no big CDN in front), counted per (hostname, IP) pair, the same way Radar counts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tranco ranks 10k–1M (sample):&lt;/strong&gt; 22.2% post-quantum (95% CI 19.8–24.5)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tranco top 10k:&lt;/strong&gt; 23.3%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Top 10k with CDNs counted&lt;/strong&gt; (what a visitor actually lands on): 66.1%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 22% lined up almost exactly with Radar's 19.6% for customer origins, which was a nice sanity check that I wasn't measuring garbage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why they aren't post-quantum
&lt;/h2&gt;

&lt;p&gt;Of the 3,327 non-PQ origins in my top-10k run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;60%&lt;/strong&gt; speak TLS 1.3 but don't offer ML-KEM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;34%&lt;/strong&gt; don't speak TLS 1.3 at all&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3%&lt;/strong&gt; have a hand-written curve list pinned to P-256/P-384&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The part that surprised me came when I fingerprinted the TLS library behind each one. Only &lt;strong&gt;13&lt;/strong&gt; of those 3,327 were provably a config problem. The rest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Library (1,137):&lt;/strong&gt; OpenSSL 3.0–3.4 and 1.1.1 dominate, plus Go older than 1.24. None of these have ML-KEM, so no config line will help.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provider (759):&lt;/strong&gt; AWS load balancers (336), F5 BIG-IP (179), and a long tail. 545 of them sit behind a product that &lt;em&gt;already has&lt;/em&gt; a PQ setting that nobody turned on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unknown (1,381):&lt;/strong&gt; mostly TLS 1.2-only servers, which never send the reply that gives the library away.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(The 13 is a floor. A server only counts as "config" with positive proof its library has ML-KEM, and a hand-set group list hides that.)&lt;/p&gt;

&lt;p&gt;So "add the PQ group to your config" is the wrong advice for most servers. OpenSSL below 3.5 can't do it. You upgrade, or you put something in front that can: OpenSSL 3.5+, Go 1.24+, Caddy 2.10+, or cloudflared.&lt;/p&gt;

&lt;p&gt;If you &lt;em&gt;are&lt;/em&gt; on OpenSSL 3.5+ with nginx and still not PQ, look for an old curve line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="n"&gt;ssl_ecdh_curve&lt;/span&gt; &lt;span class="no"&gt;X25519MLKEM768&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="no"&gt;X25519&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;prime256v1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How you check without finishing a handshake
&lt;/h2&gt;

&lt;p&gt;TLS 1.3 negotiation happens in the clear, so you can learn a lot from the server's first reply alone.&lt;/p&gt;

&lt;p&gt;The tool builds every ClientHello by hand and sends seven probes, each changing one variable. The key shares are valid but inert: the X25519 base point, the P-256 generator, and an ML-KEM key made of zero coefficients plus a seed, which still passes FIPS 203's modulus check. The server replies with a ServerHello or a HelloRetryRequest, and that tells you what it accepts. The tool never sends Finished and never sends application data.&lt;/p&gt;

&lt;p&gt;One pair of probes is my favourite. A PQ ClientHello is about 1.4 KB versus about 250 bytes for a classical one, and some middleboxes choke on the big one. So one probe sends the PQ hello split at byte 200, and a control probe sends a classical hello padded to the same size and split the same way. If both fail, a middlebox is dropping big hellos. If only the PQ one fails, the server is rejecting the group.&lt;/p&gt;

&lt;h2&gt;
  
  
  A weird one: Google Cloud flapping
&lt;/h2&gt;

&lt;p&gt;Between two runs ten days apart, 15 origins appeared to &lt;em&gt;lose&lt;/em&gt; post-quantum. None of them actually regressed. I re-probed each one 11 times over 15 minutes, and 14 of them flapped: the same IP answered with ML-KEM on some probes and without it on others, minutes apart. Those 14 were all Google Cloud addresses, and Google Cloud's PQ share in my sample went from 15% to 43% across those same ten days. It looks a lot like a staged ML-KEM rollout on their load balancers.&lt;/p&gt;

&lt;p&gt;The lesson: confirm a regression with a second probe before you alert on it. The tool now reports a &lt;code&gt;mixed&lt;/code&gt; verdict for these instead of crying wolf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;It's called &lt;strong&gt;pqdoctor&lt;/strong&gt;, an MIT-licensed Rust CLI. It checks TLS, mail (STARTTLS) and SSH servers, tells you why they aren't post-quantum, and prints the fix for that server's software.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo install pqdoctor
pqdoctor yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's also on Homebrew and Scoop, and there's a GitHub Action that fails your build if an origin falls off PQ. Docs and an FAQ are at &lt;a href="https://pqdoctor.com" rel="noopener noreferrer"&gt;pqdoctor.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caveats:&lt;/strong&gt; one vantage point, IPv4 only, and these are servers &lt;em&gt;comparable&lt;/em&gt; to Cloudflare's origins, not Cloudflare's actual origins. What transfers is the mix of causes.&lt;/p&gt;

&lt;p&gt;If you run your own origin, what's in front of it, and is it post-quantum yet? I'm curious what people find.&lt;/p&gt;

</description>
      <category>security</category>
      <category>cryptography</category>
      <category>postquantum</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
