<?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: lexiforest</title>
    <description>The latest articles on DEV Community by lexiforest (@lexiforest).</description>
    <link>https://dev.to/lexiforest</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3850382%2Ff6312750-f01b-41ed-af13-2014197af44a.png</url>
      <title>DEV Community: lexiforest</title>
      <link>https://dev.to/lexiforest</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lexiforest"/>
    <language>en</language>
    <item>
      <title>HTTP/3 Fingerprints: Identifying Clients in the QUIC Era</title>
      <dc:creator>lexiforest</dc:creator>
      <pubDate>Fri, 10 Apr 2026 13:54:32 +0000</pubDate>
      <link>https://dev.to/lexiforest/http3-fingerprints-identifying-clients-in-the-quic-era-2k3d</link>
      <guid>https://dev.to/lexiforest/http3-fingerprints-identifying-clients-in-the-quic-era-2k3d</guid>
      <description>&lt;p&gt;By May 2025, HTTP/3 traffic accounted for nearly &lt;strong&gt;35% of all internet traffic&lt;/strong&gt;. With this rapid adoption, the question naturally arises: can HTTP/3 traffic be fingerprinted in the same way as HTTP/2?&lt;/p&gt;

&lt;p&gt;The short answer is &lt;strong&gt;yes&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Revisiting HTTP/2 Fingerprints
&lt;/h2&gt;

&lt;p&gt;Fingerprinting in HTTP/2 is well-established. For example, opening &lt;a href="https://tls.browserleaks.com/json" rel="noopener noreferrer"&gt;https://tls.browserleaks.com/json&lt;/a&gt; with Chrome 136 shows the following &lt;code&gt;akamai_text&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"akamai_text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This string is divided into four parts, each corresponding to an HTTP/2 feature:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Settings&lt;/strong&gt; – e.g., &lt;code&gt;1:65536;2:0;4:6291456;6:262144&lt;/code&gt;. Representing values from the HTTP/2 settings frame.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Window Update&lt;/strong&gt; – the value of the &lt;code&gt;window_update&lt;/code&gt; frame.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weight&lt;/strong&gt; – now deprecated, so browsers typically send &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pseudo Headers&lt;/strong&gt; – HTTP/2 defines pseudo-header fields such as &lt;code&gt;:method&lt;/code&gt;, &lt;code&gt;:path&lt;/code&gt;, etc., and their order varies across clients.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By combining these values, it is straightforward to distinguish a request from a legitimate browser versus an automated script, such as &lt;code&gt;curl&lt;/code&gt; or &lt;code&gt;requests&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Examples of browser fingerprints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chrome 136: 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p
Firefox 138: 1:65536;2:0;4:131072;5:16384|12517377|0|m,p,a,s
Safari 18.4: 2:0;3:100;4:2097152;9:1|10420225|0|m,s,a,p
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Defining HTTP/3 Fingerprints
&lt;/h2&gt;

&lt;p&gt;HTTP/3 adopts a similar design, with QUIC replacing TCP as the transport layer. This allows for comparable fingerprinting strategies.&lt;/p&gt;

&lt;p&gt;Using Wireshark, we can observe the &lt;strong&gt;settings frame&lt;/strong&gt; for different clients:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;curl&lt;/strong&gt; includes &lt;code&gt;MaxFieldSection&lt;/code&gt;, &lt;code&gt;MaxTableCapacity&lt;/code&gt;, and &lt;code&gt;BlockedStream&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdn0ce2c6jvuoy0ozldk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdn0ce2c6jvuoy0ozldk.png" alt="h3-settings-curl.png" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firefox&lt;/strong&gt; includes &lt;code&gt;MaxTableCapacity&lt;/code&gt;, &lt;code&gt;BlockedStream&lt;/code&gt;, and &lt;code&gt;EnableWebTransport&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx0nbtb20nnlzs2tq3kwo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx0nbtb20nnlzs2tq3kwo.png" alt="h3-settings-firefox.png" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chrome&lt;/strong&gt; follows its tradition of adding a &lt;strong&gt;GREASE&lt;/strong&gt; field (random, undefined values) to enforce forward compatibility.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu58khhofggnmezhw59r3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu58khhofggnmezhw59r3.png" alt="h3-settings-chrome.png" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is GREASE?
&lt;/h3&gt;

&lt;p&gt;GREASE (Generate Random Extensions And Sustain Extensibility) is a mechanism where clients insert undefined, random fields. This ensures servers remain compatible with potential future extensions.&lt;/p&gt;




&lt;h2&gt;
  
  
  The “Perk” Fingerprint
&lt;/h2&gt;

&lt;p&gt;Similar to HTTP/2, we can define an HTTP/3 fingerprint. We propose the &lt;strong&gt;perk fingerprint&lt;/strong&gt;, based on two key components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Settings&lt;/strong&gt; – same format as HTTP/2 but with HTTP/3-specific keys and values.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pseudo Headers&lt;/strong&gt; – identical rules to HTTP/2 for pseudo-header order.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;perk_text&lt;/span&gt; &lt;span class="err"&gt;:=&lt;/span&gt; &lt;span class="err"&gt;SETTINGS|PSEUDO_HEADERS&lt;/span&gt;
&lt;span class="err"&gt;perk_hash&lt;/span&gt; &lt;span class="err"&gt;:=&lt;/span&gt; &lt;span class="err"&gt;md5(perk_text)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Examples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;curl&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;6:4611686018427387903;1:0;7:0&lt;/span&gt;
&lt;span class="py"&gt;pseudo_headers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;mpsa&lt;/span&gt;

&lt;span class="py"&gt;perk_text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;6:4611686018427387903;1:0;7:0|mpsa&lt;/span&gt;
&lt;span class="py"&gt;perk_hash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;cb11122dd57d03bad5d061c9abe83ddd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;firefox&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1:65536;7:20;727725890:0&lt;/span&gt;
&lt;span class="py"&gt;pseudo_headers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;mpas&lt;/span&gt;

&lt;span class="py"&gt;perk_text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1:65536;7:20;727725890:0|mpas&lt;/span&gt;
&lt;span class="py"&gt;perk_hash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;2e09f470459efcf3c9354e402a54208d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;chrome&lt;/strong&gt; (with GREASE):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1:65536;6:262144;7:100;51:1;GREASE&lt;/span&gt;
&lt;span class="py"&gt;pseudo_headers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;masp&lt;/span&gt;

&lt;span class="py"&gt;perk_text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1:65536;6:262144;7:100;51:1;GREASE|masp&lt;/span&gt;
&lt;span class="py"&gt;perk_hash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;51da7e5a519bbb4b6943e603907816eb&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;HTTP/3 traffic can indeed be fingerprinted, just like HTTP/2.  &lt;/p&gt;

&lt;p&gt;For convenience, we created a simple API where you can check your client’s HTTP/3 fingerprint:  &lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://fp.impersonate.pro/api/http3" rel="noopener noreferrer"&gt;https://fp.impersonate.pro/api/http3&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Coming Next
&lt;/h3&gt;

&lt;p&gt;You might wonder:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can HTTP/3 traffic be proxied?
&lt;/li&gt;
&lt;li&gt;Can fingerprints be modified?
&lt;/li&gt;
&lt;li&gt;What do the TLS fingerprints look like in HTTP/3?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay tuned for the next article.&lt;/p&gt;

</description>
      <category>networking</category>
      <category>privacy</category>
      <category>security</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
