Skip to main content

Early Hints - How to Enable on WordPress using Cloudflare

· 22 min read
Kashish Kumawat
CEO @ SpeedVitals

Early Hints Banner using WordPress and CloudFlare

It has been some years since Early Hints became available but its adoption hasn't been as high as preloading or prerendering.

During a page load, one of the most overlooked sources of wait time is not slow assets or unoptimized images, it is the gap between when the browser makes a request and when it receives enough information to start doing something useful, also known as server response time.

That is the exact problem Early Hints was designed to fix!

Today, we will be taking a deep look at Early Hints and explore enabling it on WordPress using Cloudflare and see if it is actually worth it.

And the best part?

You can enable Early Hints in a few minutes and start sending the browser critical resource signals before your server has even finished building the page. This guide covers what Early Hints is, why it matters for Core Web Vitals, and multiple ways to set it up correctly on a WordPress + Cloudflare stack.

Let us get started!

What Are Early Hints?

Early Hints is a web standard defined in RFC 8297. It introduces a new HTTP status code, 103 Early Hints, that sits between the browser's initial request and the server's final 200 OK response.

Here is what a typical Early Hints exchange looks like at the protocol level:

Browser Request:

GET / HTTP/1.1
Host: example.com

Early Hints Response (sent immediately while server is still processing your request):

HTTP/1.1 103 Early Hints
Link: </fonts/brand.woff2>; rel=preload; as=font; crossorigin
Link: </css/critical.css>; rel=preload; as=style
Link: <https://fonts.gstatic.com>; rel=preconnect; crossorigin

Final Response:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Link: </fonts/brand.woff2>; rel=preload; as=font; crossorigin
Link: </css/critical.css>; rel=preload; as=style

[Full HTML response]

The 103 response is a non-final response. The browser receives it, starts fetching the hinted resources, and by the time the full 200 OK arrives, those resources are already in-flight or even partially downloaded. The server's "think time", the time spent executing business logic, querying a database, or generating dynamic HTML, becomes productive browser time.

So this is how it works. While it feels very simple based on how it works, the impact it can make in Web Performance is significant!

How Early Hints Is Different than other methods

Before Early Hints, we had several solutions that tackled this exact problem. However, they weren't as effective as Early Hints.

Early Hints vs. HTTP/2 Server Push

HTTP/2 Server Push tried to solve a similar problem by having the server proactively push assets to the browser without waiting for a request. It had two fundamental issues:

  1. It sent data before the browser knew it needed it, often pushing resources that were already in the browser cache, wasting bandwidth.
  2. It was difficult to implement correctly, and Chrome removed support for it in 2022. Early Hints avoids these problems by only sending hints, not actual resource bytes. The browser decides whether to fetch, based on its own cache state. This makes it cache-friendly, less aggressive, and far more compatible.

Early Hints vs. Preloading in HTML

A <link rel="preload"> tag in your HTML also tells the browser to fetch a resource early, but the browser does not discover it until it has parsed the <head> of your HTML. That parsing cannot begin until the server sends the first bytes of the response.

Early Hints decouples this discovery from the HTML entirely. Resource hints are sent before a single byte of HTML is transmitted.

TechniqueWhen Browser Learns About ResourceCache-AwareServer Push Risk
PreloadingAfter HTML parsing startsYesNo
HTTP/2 Server PushImmediately, but assets are pushedNoYes
Early Hints (103)Before HTML is receivedYesNo

Early Hints is the more efficient solution among the three.

Why Early Hints Matter for WordPress Sites

WordPress sites are, by nature, dynamic. Even with full-page caching in place, many requests trigger PHP execution, looking up user sessions, generating nonces, processing shortcodes, or querying the database for custom post meta. This creates server processing time on every uncached or semi-dynamic page request.

During that processing time, the browser sits idle. It has sent a request and is waiting. No critical CSS is being fetched. No fonts are downloading. No preconnect to a third-party domain is being established.

Early Hints utilizes this idle time and helps browser download the critical resources such as LCP Image or Fonts.

The Impact on Core Web Vitals

The most direct metric affected is Largest Contentful Paint (LCP). LCP measures how long it takes for the largest visible element, typically a hero image, a heading font, or a banner, to render on screen. When the browser gets an Early Hint for the LCP image or the font used in your H1, it starts fetching that resource earlier, even before parsing the HTML.

Time to First Byte (TTFB) is not directly improved by Early Hints, the time your server takes to process the request does not change. But the browser-perceived latency shrinks, because the browser is doing useful work while waiting for the TTFB to complete.

First Contentful Paint (FCP) also benefits when Early Hints are used to preload critical CSS or fonts, reducing the chain of dependent resource requests that typically delays initial rendering.

How Cloudflare Implements Early Hints

Cloudflare acts as the intermediary between your browser and your origin server. This positioning makes it uniquely well-suited to serve Early Hints efficiently.

Here is the mechanism:

  1. A browser makes a request to your domain.
  2. Cloudflare's intercepts the request and checks its cache for stored Early Hints for that URL.
  3. If cached Early Hints exist, Cloudflare immediately sends the 103 response to the browser.
  4. In parallel, Cloudflare forwards the request to your WordPress origin.
  5. When the origin responds, Cloudflare streams the 200 OK to the browser. The Early Hints cache on Cloudflare is populated the first time a URL is requested and returns a 200, 301, or 302 response that includes Link headers with preload or preconnect rel types. Cloudflare caches those link headers and replays them as a 103 response on subsequent visits.

This means:

  • Your origin does not need to send the 103 status code directly (which most PHP-based servers cannot do reliably anyway).
  • Cloudflare handles the complexity on your behalf.
  • The only thing your WordPress site needs to do is send Link headers on its responses, which Cloudflare reads and caches.

Important: Cloudflare's Early Hints implementation only works over HTTP/2 or HTTP/3. HTTP/1.1 connections will not receive the 103 response.

Prerequisites Before You Start

Before enabling Early Hints, confirm the following:

  • Your domain is proxied through Cloudflare (orange cloud icon in DNS settings)
  • Your site is served over HTTPS with HTTP/2 or HTTP/3 enabled (Cloudflare enables HTTP/2 by default)
  • You have at least one meaningful Link header being sent from your WordPress origin (or you will configure one, this is covered below) Without Link headers, enabling Early Hints in Cloudflare will not cause any errors, but it will also not do anything useful. Cloudflare has nothing to cache and no hints to send.

Method 1: Enable Early Hints in the Cloudflare Dashboard

This is the first step regardless of which method you use for generating Link headers. Without this toggle, Cloudflare will not read or serve Early Hints at all.

Steps:

  1. Log in to the Cloudflare Dashboard and select your domain.
  2. Go to SpeedOptimization.
  3. Click the Content Optimization tab.
  4. Find Early Hints and toggle it to On. That is it, the feature is active on all Cloudflare plans, including the free tier. No upgrade is required.

Once enabled, Cloudflare will begin caching Link headers from your responses. The 103 Early Hints response will be sent automatically on subsequent requests to those URLs.

Note for Kinsta users: If you are on Kinsta, you can also enable Early Hints directly from MyKinsta under your site's Tools section. Kinsta's Cloudflare integration handles the toggle on your behalf. This applies to custom domains only, the .kinsta.cloud staging domain does not support Early Hints.

Enabling Early Hints in the Cloudflare dashboard is only half the equation. Cloudflare needs Link headers in your server's responses to know what to hint. The most practical way to generate these headers on a WordPress site is through a performance plugin.

Option A: Using Perfmatters

Perfmatters has native Early Hints integration. When you enable preloading for a resource in Perfmatters, it automatically outputs it as a Link HTTP response header alongside the usual <link rel="preload"> HTML tag.

Steps:

  1. Go to Perfmatters → Settings.
  2. Enable Show Advanced Options.
  3. Navigate to the Preloading section.
  4. Add the resources you want preloaded (fonts, CSS, scripts, images).
  5. Under Early Hint Types, select which resource types should be included in the Link header: Fonts, Images, Scripts, or Styles. Perfmatters will then include these as HTTP response headers, such as:
Link: </fonts/brand.woff2>; rel=preload; as=font; crossorigin
Link: </css/critical.css>; rel=preload; as=style

Cloudflare reads these headers, caches them, and sends them as 103 responses on subsequent requests.

Option B: Using WP-Rocket

WP Rocket does not yet have a dedicated Early Hints feature with native Link header output in the same way Perfmatters does. However, WP Rocket's Prefetch DNS Requests and Preload Fonts features do influence the <link> tags in your HTML <head>, which Cloudflare can learn from.

For WP Rocket to contribute to Cloudflare's Early Hints cache effectively, you should complement it with a small custom snippet (see Method 3 below) to output the resource hints as actual HTTP headers, not just HTML tags.

Option C: Using Other Plugins (Autoptimize, LiteSpeed Cache, etc.)

Most performance plugins add resource hints as <link> tags in HTML, not as HTTP response headers. Cloudflare's Early Hints implementation reads response headers, not HTML content. So if a plugin only inserts <link rel="preload"> into the <head>, it will not directly contribute to Cloudflare's Early Hints cache.

The workaround is to pair any plugin with a custom PHP snippet that converts the most important preloads into Link headers, which is what Method 3 covers.

If you prefer not to rely on a specific plugin, or if your current plugin setup does not output Link headers natively, you can use a small WordPress mu-plugin to add them manually.

This approach gives you precise control over which resources are hinted, without requiring any particular caching plugin.

Create a file at /wp-content/mu-plugins/early-hints-headers.php:

<?php
/**
* Early Hints: Output Link headers for Cloudflare 103 caching.
* Place this file in /wp-content/mu-plugins/
*/

add_action( 'send_headers', 'sv_add_early_hints_headers' );

function sv_add_early_hints_headers() {
// Only run on front-end page requests
if ( is_admin() || wp_doing_ajax() || is_feed() ) {
return;
}

// Define your critical resources here
$hints = [
// LCP hero image
'</wp-content/themes/your-theme/images/hero.webp>; rel=preload; as=image',
// Critical stylesheet
'</wp-content/themes/your-theme/css/critical.css>; rel=preload; as=style',
// Self-hosted font
'</wp-content/themes/your-theme/fonts/brand.woff2>; rel=preload; as=font; crossorigin',
// Third-party preconnect (e.g., Google Fonts, CDN)
'<https://fonts.gstatic.com>; rel=preconnect; crossorigin',
];

foreach ( $hints as $hint ) {
header( 'Link: ' . $hint, false );
}
}

What to put in the $hints array:

  • Your LCP element's image URL (check PageSpeed Insights or SpeedVitals to identify it)
  • Critical CSS files loaded in <head>
  • Self-hosted fonts referenced in your main stylesheet
  • Any preconnect targets for third-party domains used on every page (Google Fonts, your CDN, etc.) Avoid adding too many hints. A short list of 3–5 critical resources is more effective than a long list of 15+. The browser has a limited number of parallel connections, and excessive hints compete with each other for bandwidth on the initial connection.

Important: Using PHP's header() function with false as the second argument allows multiple Link headers to be sent without overwriting each other.

If you have control over your server configuration, for example on a VPS, Cloudways, or RunCloud, you can add Link headers at the server level without going through WordPress PHP at all. This is the most performant approach since it avoids PHP execution entirely.

Apache (.htaccess)

<IfModule mod_headers.c>
<FilesMatch "\.(html|htm|php)$">
Header add Link "</wp-content/themes/your-theme/fonts/brand.woff2>; rel=preload; as=font; crossorigin"
Header add Link "</wp-content/themes/your-theme/css/critical.css>; rel=preload; as=style"
Header add Link "<https://fonts.gstatic.com>; rel=preconnect; crossorigin"
</FilesMatch>
</IfModule>

Nginx

location ~* \.(html|htm|php)$ {
add_header Link "</wp-content/themes/your-theme/fonts/brand.woff2>; rel=preload; as=font; crossorigin";
add_header Link "</wp-content/themes/your-theme/css/critical.css>; rel=preload; as=style";
add_header Link "<https://fonts.gstatic.com>; rel=preconnect; crossorigin";
}

Nginx caveat: Nginx's add_header directive is not inherited in sub-contexts unless explicitly repeated. If you have caching or other add_header directives in child blocks, you may need to repeat these headers there as well, or use the always flag:

add_header Link "</css/critical.css>; rel=preload; as=style" always;

Method Comparison

MethodRequires PluginDynamic Per PageServer ConfigBest For
Cloudflare Dashboard (toggle)NoNoNoAll sites, required step
Perfmatters pluginYesYes (per resource type)NoWordPress-only, fine control
Custom PHP (mu-plugin)NoPartial (conditionals possible)NoDevelopers, no plugin overhead
Apache (.htaccess)NoNo (global)YesApache-based hosts
Nginx server blockNoNo (global)YesVPS/managed cloud hosts

How to Verify Early Hints Are Working

Once you have enabled the Cloudflare toggle and configured Link headers, verify the setup is actually working. Do not skip this step.

Method 1: cURL

Run this from your terminal. Replace https://yourdomain.com with your actual URL:

curl -sI --http2 https://yourdomain.com

If Early Hints are active, you should see a HTTP/2 103 response at the top of the output, followed by your Link headers, before the HTTP/2 200 response:

HTTP/2 103
link: </fonts/brand.woff2>; rel=preload; as=font; crossorigin
link: </css/critical.css>; rel=preload; as=style

HTTP/2 200
content-type: text/html; charset=UTF-8
...

Note: The --http2 flag is required. Without it, cURL defaults to HTTP/1.1 and you will not see the 103 response.

Method 2: Chrome DevTools

  1. Open Chrome DevTools (F12) and go to the Network tab.
  2. Check Disable cache to ensure you are not hitting the browser cache.
  3. Reload the page.
  4. Click on the main document request (the HTML file).
  5. In the Headers tab, scroll down to find Early Hints Headers, Chrome displays these separately from the final response headers.
  6. In the Type column of the waterfall, you will see early-hints listed alongside the document request.

Method 3: EarlyHints.Dev

Visit EarlyHints.dev which provides a excellent User Interface for the testing of Early Hints.

Benchmarking: Before and After Early Hints

Cloudflare's own internal testing has reported improvements of up to 30% in page load time when Early Hints are in use. That figure, while compelling, comes from a controlled environment, real-world results depend on your specific site's server processing time and which resources you are hinting.

Rather than quoting a single number, here is how to measure the actual impact on your WordPress site.

What to Measure Before Enabling

Run your baseline tests before enabling Early Hints in the Cloudflare dashboard. Capture:

  • LCP using SpeedVitals Batch Test
  • FCP and Total Blocking Time
  • The waterfall chart: note when your font, hero image, and critical CSS begin downloading relative to the HTML response start Take 3-5 test runs per location and average them. A single test is not reliable.

What to Look for After Enabling

After enabling Early Hints and verifying the 103 is being served, re-run the same tests under the same conditions. Look for:

MetricWhat Should ImproveWhy
LCPEarlier start of LCP resource downloadBrowser receives preload hint before HTML
FCPEarlier font/CSS availabilityPreconnect and preload hints reduce dependency chain
TTFBLikely unchangedServer processing time is not affected
Resource waterfallEarlier start time for hinted assetsBrowser acts on 103 before 200 arrives
Overall load timeReduced by value of server processing time utilizedDepends on origin processing time

The most visible change will be in the waterfall chart. Without Early Hints, your LCP image or font download begins only after the server returns its 200 OK. With Early Hints, you will see those resources start downloading earlier, overlapping with the server's processing time rather than waiting for it.

A Realistic Expectation Framework

The benefit of Early Hints scales with server processing time. If your WordPress origin has a TTFB of 80ms (a well-optimized, cached response from a fast server), the browser has very little idle time to fill, and the gains will be modest. If your origin's TTFB is 400–800ms (a common range for uncached or partially dynamic WordPress pages), the browser can accomplish a meaningful amount of work on those hinted resources during that wait.

Sites that benefit most from Early Hints:

  • WordPress sites with high server processing time (TTFB > 200ms on non-cached requests)
  • Sites with large LCP elements (hero images, display fonts)
  • Sites that load custom or self-hosted fonts in the critical path
  • Sites connecting to third-party origins (Google Fonts, analytics CDNs, ad servers)

Sites that see minimal benefit:

  • Fully static sites served from edge cache with sub-50ms TTFB
  • Sites whose LCP element is already inlined or base64-encoded
  • Sites where the LCP element is a text node, not an image or font

Best Practices and Common Mistakes

Do: Hint Only Critical, Above-the-Fold Resources

Early Hints are most effective for resources that are needed to render the initial viewport. Adding too many hints, preloading 10 or 15 resources, can actually slow things down by creating bandwidth competition at the exact moment the browser is trying to establish the connection.

A good starting point:

  • 1 LCP image
  • 1 critical CSS file
  • 1-2 self-hosted fonts

Do: Use preconnect for Third-Party Domains, preload for First-Party Assets

preconnect tells the browser to establish a TCP connection and TLS handshake with a third-party origin. It does not fetch a resource, it just reduces the latency when a resource from that domain is eventually requested.

preload tells the browser to actually fetch a specific resource. Use this for your own assets where you know the exact URL.

# Use preconnect for third-party origins
Link: <https://fonts.gstatic.com>; rel=preconnect; crossorigin

# Use preload for your own critical assets
Link: </fonts/brand.woff2>; rel=preload; as=font; crossorigin

Do: Include as= and crossorigin Attributes Correctly

Without the as attribute, the browser cannot prioritize the hint correctly. Without crossorigin on fonts, the browser will download the font twice, once for the hint and once when the @font-face rule is parsed. These are easy mistakes to make and hard to diagnose after the fact.

# Correct for fonts
Link: </fonts/brand.woff2>; rel=preload; as=font; crossorigin

# Correct for images
Link: </images/hero.webp>; rel=preload; as=image

# Correct for stylesheets
Link: </css/style.css>; rel=preload; as=style

# Correct for scripts
Link: </js/app.js>; rel=preload; as=script

Do Not: Hint Resources That Change Between Pages

Cloudflare's Early Hints cache is keyed by URI. If you hint your homepage's hero image on all pages, visits to /about/ or /blog/ will receive an Early Hint for a resource that is not needed on those pages. The browser will fetch it anyway, wasting bandwidth.

Use the WordPress PHP approach (Method 3) with conditional logic to output different hints based on the page type:

if ( is_front_page() ) {
header( 'Link: </images/home-hero.webp>; rel=preload; as=image', false );
} elseif ( is_singular( 'post' ) ) {
header( 'Link: </images/post-hero.webp>; rel=preload; as=image', false );
}

If you enable Early Hints in the Cloudflare dashboard but your origin sends no Link headers, nothing happens. The feature is active but Cloudflare has nothing to cache or replay. This is not harmful, but it is a common reason why users enable Early Hints and see no waterfall change.

These are two different things. The HTML tag is discovered during HTML parsing. The HTTP header is read by Cloudflare before HTML parsing starts. Only the HTTP response header contributes to Cloudflare's Early Hints cache.

Troubleshooting

103 Is Not Appearing in cURL Output

  • Confirm you are using --http2 in your cURL command
  • Check that Early Hints is toggled On in the Cloudflare dashboard
  • Verify that your origin is actually sending Link headers (run curl -sI https://yourdomain.com directly to your origin IP if possible)
  • Cloudflare notes that Early Hints may be emitted less frequently when the full response is already cached. Try testing with cache bypassed or on a URL that is not edge-cached

Seeing 504 Errors in Cloudflare Logs with RequestSource: earlyHintsCache

This is actually expected and not an issue. These 504 responses are internal subrequests from Cloudflare checking its Early Hints cache, a 504 here simply means there was a cache miss (no Early Hints stored for that URI yet). These are filtered out of Cache Analytics automatically and do not indicate a problem with your site.

Early Hints Are Not Helping LCP

  • Verify you are hinting the correct resource. Check PageSpeed Insights or SpeedVitals to confirm what element is flagged as LCP and compare its URL with what you are hinting.
  • Make sure the as attribute is correct. A mismatch will cause the browser to treat it as a low-priority request.
  • Check for crossorigin issues on fonts, a missing attribute causes a double download.
  • If your TTFB is already very low (under 100ms), Early Hints may have minimal room to help.
  • Another common issue with LCP Image is when the Early Hint loads an image in a different image format or dimensions (if you're using serving a different image based on display size)

Wrong Resources Hinted on Non-Homepage URLs

This happens when Link headers are added globally (via .htaccess or Nginx config) without page-level conditions. Use the PHP mu-plugin approach to add conditional logic and serve page-appropriate hints.

Limitations and Caveats

Early Hints is a powerful optimization, but it is not a silver bullet. Keep these constraints in mind:

  • HTTP/2 or HTTP/3 only: HTTP/1.1 connections will not receive a 103 response. Cloudflare enables HTTP/2 by default, but verify under NetworkHTTP/2 in the Cloudflare dashboard. However, most modern websites optimized for speed are most likely already on HTTP/2 or HTTP/3.
  • Browser support. As of 2026, Chrome 94+, Firefox 102+, Safari 16.4+, and Edge 94+ all support Early Hints. Older browsers and some bots will simply ignore the 103 response.
  • No benefit for sub-resources. Early Hints apply to the HTML document request only. They do not cascade to sub-resource requests.
  • Query string caching: Cloudflare ignores query strings when keying the Early Hints cache. Dynamic pages with distinct query parameters may receive hints intended for a different page variant.
  • Authentication pages. Cloudflare may serve the cached 103 Early Hints response before determining that the visitor needs to authenticate. This results in unauthenticated visitors receiving the 103 before a 403 Forbidden. The 103 contains only resource hints, not content, so this is generally not a security issue, but it is worth being aware of on membership or gated-access sites.

Conclusion

Early Hints is one of the few performance optimizations that genuinely changes how browsers and servers work together, rather than just optimizing within the existing model. For WordPress sites on Cloudflare, it is also one of the easiest to implement, the infrastructure is already in place, available on every Cloudflare plan, and requires no server-level changes if you use the PHP mu-plugin approach.

The practical recommendation for most WordPress sites:

  1. Enable Early Hints in the Cloudflare dashboard (Speed → Content Optimization).
  2. Identify your LCP element, critical fonts, and CSS files using PageSpeed Insights or SpeedVitals.
  3. Output Link headers using Perfmatters (if you want plugin-level control) or the custom PHP mu-plugin (if you prefer code).
  4. Verify the 103 response is being served with cURL.
  5. Measure the before and after in your waterfall, look for earlier download start times on your hinted resources. The benefit will be most visible on sites where the origin has meaningful server processing time, a TTFB above 200ms on uncached or dynamically generated pages. If your site already returns cached HTML in under 100ms, the gains will be smaller, but the cost of enabling Early Hints is essentially zero, so there is no reason not to have it on.

As you can see, the setup is straightforward once you understand what Cloudflare needs from your origin. It is not about adding complexity, it is about using the time your server was already taking and putting it to work.