gclid Cache: Ad Click Query Strings, Page Cache Keys, and Measured Time to First Byte

Affiliate disclosure v3.1
Affiliate disclosure v3.2 (mobile)
Test environment RHEL familyAlmaLinux 9.8Nginx 1.28 + Apache 2.4WP Rocket 3.23.5.1Checked 2026-10-17

gclid cache handling means ignoring the gclid query added to the end of an ad click URL and responding with the cache file of the URL without the query. Measurements of time to first byte by query and by cache state on a WordPress server with an Nginx + Apache setup showed that gclid added 0.014–0.015 seconds, and delays of several seconds happened only when the cache could not be used.

Measured inside the same server, time to first byte was 0.014–0.015 seconds without a query and 0.029–0.030 seconds with ad click queries attached. When WordPress built the same page from scratch without cache, it took 3.639 and 3.260 seconds.

1️⃣ gclid Cache: Ad Click URLs and the Cache Key

When Google Ads auto-tagging is on, [?gclid=value] is added to the end of the URL for visitors who click an ad. This value is a parameter that identifies the ad click, so it differs from visitor to visitor, although the same person clicking the same ad again may get the same value.

Real ad click URLs rarely carry gclid alone. gad_source, gad_campaignid and gbraid are often attached too, and sometimes wbraid as well.

https://example.com/sample-page/?gad_source=1&gad_campaignid=1234567890&gbraid=EXAMPLE&gclid=EXAMPLE
https://example.com/sample-page/?gclid=EXAMPLE&gbraid=EXAMPLE&wbraid=EXAMPLE

With a query at the end of the URL, the browser still opens the same page, but to a cache it is a different address. The HTTP caching standard (RFC 9111) defines the cache key, the basis on which a cache picks a stored response, as made up of at least the request method and the target URI, and the target URI includes the query string.

If the query string goes into the cache key as is, every click with a different gclid value becomes a new address. The CDN, nginx and WP Rocket that the response passes through each handle this query by different rules.

Ad click https://example.com/sample-page/?gad_source=1&gbraid=…&gclid=…
CDN
If ad queries are removed from the cache key→ respond with the copy of the URL without a query
If there is no copy→ request to the origin server with the query still attached
nginx
If there is any query at all→ skip serving the cache file directly and pass to Apache
WP Rocket
If there are only queries from the ignored list→ respond with the cache file of the URL without a query
If there is no cache file→ WordPress builds the page from scratch

If the CDN has no copy or its cache key tells queries apart, the request goes on to the origin server. From there, speed depends on whether nginx and WP Rocket find a cache file.

🧭 Ad Click Request Flow
WP RocketExplainer diagram
🔗 Request URL
☁️ CDN copy
🌐 Request
/sample-page/No query
/sample-page/?gclid=…gad_source · gbraid · gclid
→Request
☁️ CDN
🔑 Ad queries excluded from the cache key
✅ Responds with the same copy
➡️ Request to the origin server
➡️ To the origin server with the query attached
→Origin request
🖥️ nginx
🔎 Is the query string empty?
Not reached
✅ Cache file served directly · HIT
➡️ Query present, passed to Apache
→Apache
🚀 WP Rocket
📋 Ignored query list
Apache · PHP
Not reached
Not reached
✅ Drops the ad queries and responds with the same cache file
If the CDN has a copy, it answers a request without a query straight from that copy, and the origin server's nginx and WP Rocket are not involved.
If the CDN removes ad queries from the cache key, it answers with the same copy as the URL without a query even when the gclid value differs, and the request never reaches the origin server.
If the CDN has no copy, the request goes to the origin server, and since there is no query, nginx serves the WP Rocket cache file directly without PHP. Time to first byte measured inside the server was 0.014–0.015 seconds.
A request with ad queries reaches the origin server with the queries still attached, and because there is a query, nginx skips serving the cache file directly and hands it to Apache. WP Rocket then drops the ad queries and responds with the same cache file; time to first byte was 0.029–0.030 seconds.

2️⃣ nginx Static Serving: Requests With a Query Go to Apache

Even when the cache file is on disk, nginx in an Nginx + Apache setup does not serve it directly to a request with a query. That is because the configuration below, which lets nginx read WP Rocket cache files directly, only builds a file path when the query string is empty.

# nginx.conf — http block: conditions for skipping the cache file
map $http_cookie $rocket_cookie {
    default                   0;
    '~*wordpress_logged_in'   1;
    '~*wp-postpass'           1;
    '~*comment_author'        1;
    '~*woocommerce_items'     1;
}
map $request_method $rocket_method {
    default  1;
    GET      0;
    HEAD     0;
}
map $query_string $rocket_query {
    default  1;
    ''       0;      # 0 only when the query string is empty
}

# server block
set $rocket_bypass '';
if ($rocket_cookie = 1) { set $rocket_bypass '1'; }
if ($rocket_method = 1) { set $rocket_bypass '1'; }
if ($rocket_query  = 1) { set $rocket_bypass '1'; }
set $rocket_file '/wp-content/cache/wp-rocket/example.com${request_uri}index-https.html';
if ($rocket_bypass = '1') { set $rocket_file ''; }

location / {
    try_files $rocket_file @apache;                       # to Apache if the path is empty or the file is missing
    add_header X-Rocket-Nginx-Serving-File 'HIT' always;  # marks responses nginx served straight from the cache file
}

[$rocket_query] only checks whether the whole query string is empty, so gclid, which WP Rocket ignores, is treated just like any other query. If mobile copies are kept separately, there is one more check that adds [-mobile] to the file name.

The [.htaccess] rules WP Rocket writes for Apache also require an empty query string. Whether in nginx or Apache, the step that serves the cache file directly without PHP only applies to requests without a query.

Requests without a query carried the [X-Rocket-Nginx-Serving-File: HIT] header, and the six requests with gclid and ad-style queries did not. Time to first byte grew by 0.014–0.015 seconds for requests with a query because Apache and PHP reading and returning the cache file was added to the path.

curl -so /dev/null -H 'Accept-Encoding: gzip' -w '%{time_starttransfer}\n' \
  'https://example.com/sample-page/'
0.015127
curl -so /dev/null -H 'Accept-Encoding: gzip' -w '%{time_starttransfer}\n' \
  'https://example.com/sample-page/?gclid=test123'
0.029525
curl -sI 'https://example.com/sample-page/' | grep -i x-rocket-nginx-serving-file
x-rocket-nginx-serving-file: HIT

[time_starttransfer] is the time from the request until the first byte arrives. To measure inside the server without going through the CDN, add [--resolve example.com:443:127.0.0.1].

3️⃣ WP Rocket's Ignored Query List: Same Cache File Served

Not caching URLs with a query is WP Rocket's default, but ad click queries are an exception. WP Rocket removes the parameters on its ignored list before deciding, and if no queries are left, it responds with the cache file of the URL without a query.

// wp-rocket/inc/classes/Buffer/class-tests.php (3.23.5.1)
public function get_query_params() {
    // …
    $params = array_diff_key(                                  // remove the ignored queries first
        self::$get,
        $this->config->get_config( 'cache_ignored_parameters' )
    );
    // …
}

public function can_process_query_string() {
    // …
    $params = $this->get_query_params();

    if ( ! $params ) {                                         // no queries left: handle with cache
        return self::memoize( __FUNCTION__, [], true );
    }
    // only lang · s · permalink_name · lp-variation-id and queries added to [Cache Query String(s)] pass
    // …
}

If what [get_query_params] returns after removing the ignored queries is empty, the cache file path is also set to the URL without a query. The request with gclid went through Apache and PHP, but the cache comment at the end of the HTML matched the request without a query, and the response size was 350,668 bytes, the same as the cache file (gzip).

The ignored query list is a default list that WP Rocket downloads from its own servers once a week and saves to [wp-content/wp-rocket-config/dynamic-lists.json]. As of October 17, 2026, the list includes gclid, gbraid, wbraid, gad_source and gad_campaignid.

If even one query not on the list is mixed in, WP Rocket does not use the cache: the same page with an arbitrary query not on the list was built fresh by WordPress, with no cache comment. If your ad URLs carry tracking queries you made yourself, those clicks take this path every time.

To change the list, use the helper plugin WP Rocket points to. The plugin adds or removes queries through the [rocket_cache_ignored_parameters] filter and regenerates WP Rocket's config file when it is activated or deactivated.

// from WP Rocket | Ignore Query Strings 1.1.1 (helper plugin by the WP Rocket support team)
function define_ignored_parameters( array $params ) {
    $params['new_query_string'] = 1;      // add to the ignored queries
    //unset ( $params['utm_source'] );    // remove from the ignored queries
    return $params;
}
add_filter( 'rocket_cache_ignored_parameters', __NAMESPACE__ . '\define_ignored_parameters', 999 );

register_activation_hook( __FILE__, __NAMESPACE__ . '\flush_wp_rocket' );   // runs rocket_generate_config_file() on activation

WP Rocket writes this list into the domain-named config file inside [wp-content/wp-rocket-config/] and reads that file on every request, so if you only add the filter without regenerating the config file, the new list does not take effect. Queries on the list share the one cache file of the base URL whatever their value, so do not add queries whose value changes what the page shows.

After adding [uk_test], which was not on the list, to the helper plugin and activating it, the config file was regenerated with that query added to the end of the list. Then the URL without a query and two URLs with different [uk_test] values were requested in turn.

curl -s -o /dev/null -w '%{size_download} %{time_starttransfer}\n' -H 'Accept-Encoding: gzip' 'https://example.com/sample-page/'
curl -s -o /dev/null -w '%{size_download} %{time_starttransfer}\n' -H 'Accept-Encoding: gzip' 'https://example.com/sample-page/?uk_test=abc'
curl -s -o /dev/null -w '%{size_download} %{time_starttransfer}\n' -H 'Accept-Encoding: gzip' 'https://example.com/sample-page/?uk_test=xyz'
21390 0.025720
21390 0.025507
21390 0.027929

All three requests returned the same 21,390 bytes within 0.03 seconds, and the end of the HTML for the [uk_test] request was opened to check the cache time.

curl -s --compressed 'https://example.com/sample-page/?uk_test=abc' | tail -c 200
ck=lazyLoadYoutubeIframe;}});</script></body>
</html>

<!-- This website is like a Rocket, isn't it? Performance optimized by WP Rocket. Learn more: https://wp-rocket.me - Debug: cached@1792248839 -->

The cache time in the closing comment matched the cache file of the URL without a query. Before the helper plugin was activated, the same request was built fresh in 0.36–0.38 seconds, with no cache comment.

Even after activation, a request that also carried [foo], which is not on the list, did not use the cache file and was built fresh.

For one minute right after the config file was regenerated, [uk_test] requests were also built fresh. With PHP OPcache set to recheck changed files every 60 seconds (revalidate_freq), responses came from the cache file only after a minute had passed.

Even after the helper plugin was deactivated and deleted, the [uk_test] line stayed in the config file. The deactivation code tries to remove the filter, added with priority 999, at the default priority, so the config file is regenerated with the filter still in place. Only regenerating the config file once more after deleting the plugin removes it from the list.

4️⃣ CDN Cache Key: Excluding Ad Queries and Origin Requests

If the CDN puts the query string into the cache key as is, every click with a different gclid value finds no copy, and the request goes all the way to the origin server. Dropping queries altogether solves this, but it does not suit sites with URLs whose content changes by query, such as search results, so it is better to remove only the ad queries from the cache key.

With the ad queries removed this way, the CDN answers with the same copy as the URL without a query even when the gclid value differs, and only when it has no copy does it send the request, query attached, to the origin server.

# CDN cache status for two requests with different gclid values (header names vary by CDN)
curl -sI 'https://example.com/sample-page/?gclid=test1' | grep -i -E 'cache|age'
curl -sI 'https://example.com/sample-page/?gclid=test2' | grep -i -E 'cache|age'

If both requests with different values are HITs and their copies were stored at the same time as the URL without a query, that CDN is leaving gclid out of its cache key. The first visit at a location with no copy still goes through nginx and WP Rocket on the origin server, even if the CDN removes ad queries from the key.

5️⃣ What Slows Ad Clicks: An Empty Page Cache

When the first page from an ad loads slowly, it is tempting to blame gclid for bypassing the cache, but if a cache file exists, the same cache file is served even with a query attached. What adds seconds to time to first byte is an empty cache file, which makes WordPress build the page from scratch.

📊Time to first byte with and without cacheMeasured inside the server · seconds
Served from the cache fileBuilt fresh by WordPress
With a cache file
Time to first byte for the same page requested without a query and with ad click queries. With a query, the request goes through Apache and PHP but is still served from the same cache file.
0.0000.0070.0150.0220.030
No query0.014–0.015 sServed directly by nginx
gclid0.029–0.030 sVia Apache · PHP
With and without a cache file
Time to first byte for a request with ad click queries and for requests where WordPress built the page from scratch without a cache file.
0.00.91.82.73.6
gclid0.029–0.030 sCache file present
Built fresh3.639 sRun 1
Built fresh3.260 sRun 2
Figures are time to first byte measured by requesting the same page inside a WordPress server with an Nginx + Apache setup (October 17, 2026).

Cache files are deleted once the Cache lifespan has passed; WP Rocket's default is 10 hours. Saving a post or settings also deletes the related cache, and when the list WP Rocket downloads once a week is replaced with a new version and saved, the whole site cache is emptied too.

With preload on, WP Rocket reopens emptied URLs and refills the cache, and visitors who arrive before that wait while the page is built. A request with only ad queries also creates the cache file under the URL without a query, so the ad click that first hits an empty cache fills it for the next visitor.

Right after the cache was cleared, the first request opened with ad-style queries found no cache file and was built fresh in 0.341–0.369 seconds, and the cache file for the URL without a query appeared in the same second.

curl -s -o /dev/null -w '%{size_download} %{time_starttransfer}\n' -H 'Accept-Encoding: gzip' 'https://example.com/sample-page/?gclid=EXAMPLE1'
ls -l --time-style='+%F %T' wp-content/cache/wp-rocket/example.com/sample-page/
curl -s -o /dev/null -w '%{size_download} %{time_starttransfer}\n' -H 'Accept-Encoding: gzip' 'https://example.com/sample-page/'
21361 0.366881
total 116
-rw-r--r-- 1 example nobody 90908 2026-10-17 23:56:38 index-https.html
-rw-r--r-- 1 example nobody 21385 2026-10-17 23:56:38 index-https.html_gzip
21385 0.025379

All three runs went in the same order, and the request without a query that followed was answered from the new cache file in 0.023–0.025 seconds. Because the request did not come from a mobile device, only the two desktop files were created.

For the URLs your ads link to, check yourself whether a cache file exists and when it was created; if mobile copies are made separately, check both the desktop and mobile files.

ls -l --time-style='+%F %T' wp-content/cache/wp-rocket/example.com/sample-page/
total 3520
-rw-r--r-- 1 example nobody 1445922 2026-10-17 14:07:44 index-https.html
-rw-r--r-- 1 example nobody  350668 2026-10-17 14:07:44 index-https.html_gzip
-rw-r--r-- 1 example nobody 1446862 2026-10-17 14:07:47 index-mobile-https.html
-rw-r--r-- 1 example nobody  351042 2026-10-17 14:07:47 index-mobile-https.html_gzip
curl -s --compressed 'https://example.com/sample-page/?gclid=test123' | tail -c 200
n/1/wp-content/uploads/example-plugin/style.css' media='all' /></noscript></body>
</html>
<!-- Cached for great performance - Debug: cached@1792213663 -->

A response served from a cache file has the time the file was created, in Unix time, after [Debug: cached@] in the comment at the end of the HTML. Without this mark, WordPress built that response from scratch.

🔢 FAQ & Recommended Content

As of October 17, 2026, WP Rocket's default list also includes utm queries such as utm_source, Facebook's fbclid, Microsoft Advertising's msclkid and Google Shopping's srsltid, so they use the same cache file. Queries not on the list skip the cache until you add them with the helper plugin, so first check that your ad platform's query names are in [dynamic-lists.json].

No. WP Rocket removes the ignored queries first and only then checks the [Cache Query String(s)] list, so even if you put gclid in this field, it uses the cache file of the base URL (based on the 3.23.5.1 code). To keep a separate cache file for each value, follow the official instructions: remove it from the ignore list with the helper plugin, then add it to this field.

The default access log formats of nginx and Apache have no processing time, so you have to judge by response size. Fetching the same page twice as a compressed response, the cache file responses were the same size both times, while the freshly built responses differed by 2 bytes. For future requests, adding [$request_time] to the nginx log format records the processing time of each request.

🧭 WP Rocket and Perfmatters related posts 15 posts · 3 tools
🗂️ What is WP?
📋 WP Rocket settings guide 📄 Perfmatters guide 📄 WP Rocket With Perfmatters 📄 WP Rocket MCP Server 📄 WP Rocket Korean URLs 📄 nginx njs 📄 WP Rocket Multilingual Cache 📄 gclid Cache
📄 WP Rocket speed test 📝 Cache and optimization plugins 📝 Speed improvement case
📋 Cache: definition and all fields 📄 Website cache & server-side cache
📋 BEST hosting TOP 7 📄 Fast hosting 2026
🛠️ WordPress Detection 🛠️ Whois Domain Lookup 🛠️ Check My IP Address
WP Rocket Settings Guide: Default Setup for Each Menu and a 20% Promo Code
WordPress · WP Rocket
WP Rocket Settings Guide: Default Setup for Each Menu and a 20% Promo Code
WP Rocket's basic settings for each menu and its pricing.
View Post →Current article
Perfmatters Plugin Review & Guide: WordPress Performance Optimization Settings After 5 Years of Use
WordPress · Perfmatters
Perfmatters Plugin Review & Guide: WordPress Performance Optimization Settings After 5 Years of Use
Recommended Perfmatters settings for each menu and how to use Script Manager.
View Post →Current article
WP Rocket With Perfmatters: How to Set Up Optimization Features
WordPress · Perfmatters
WP Rocket With Perfmatters: How to Set Up Optimization Features
How to set things up when using WP Rocket and Perfmatters together.
View Post →Current article
WP Rocket MCP Server: Handling Cache and Settings with AI Tools
WordPress · MCP
WP Rocket MCP Server: Handling Cache and Settings with AI Tools
How to handle WP Rocket cache and settings from AI tools.
View Post →Current article
WP Rocket Korean URL request flow diagram
WordPress · WP Rocket
WP Rocket Korean URLs
Why nginx can't find the cache file because of the letter case of the encoding in Korean URL requests, and how to fix it with njs.
View Post →Current article
Diagram of nginx njs variables and cache file paths
Server · nginx
nginx njs: Installing the Module, Setting js_set, and Applying a Reload to Create JavaScript Variables
How to install the njs module, which creates variables with JavaScript functions inside the nginx configuration, and apply js_set variables with a reload.
View Post →Current article
WP Rocket multilingual cache post-save flow diagram
WordPress · WP Rocket
WP Rocket Multilingual Cache
Why saving a draft deletes the translated page cache along with the whole language folder, and the code that prevents it.
View Post →Current article
Flow in which a request with an ad query goes to the origin server when the CDN has no copy, passes from nginx to Apache, and WP Rocket drops the ad query and answers with the same cache file
Server · Cache
gclid Cache: Ad Click Query Strings, Page Cache Keys, and Measured Time to First Byte
Measuring, by time to first byte, how the gclid query added to the end of ad-click URLs is ignored and the request is served from the cache file of the URL without the query.
View Post →Current article

ℹ️ Affiliate Disclosure
This site's content contains affiliate links. When a visitor buys a product or service through one of them, the site receives a commission from the seller. The amount the buyer pays(it goes down during event discounts ↓)does not go up. Posted prices, discounts, and stock reflect the time of writing and may differ, so confirm with the seller before buying. Products are chosen and reviewed by our own standards, and commissions do not affect the order or content of reviews.

Leave a Reply

Your email address will not be published. Required fields are marked *

Prove your humanity: 5   +   5   =