Skip to content

Static cache lock key exceeds VARCHAR(255) limit with long URLs, causing infinite 503 refresh loop when using database cache driver #14684

@david-windsock

Description

@david-windsock

Bug description

When using CACHE_STORE=database and STATAMIC_STATIC_CACHING_STRATEGY=half, requests to URLs with
long query strings (e.g. UTM campaign parameters) hang for 30 seconds and return a 503 response
containing <meta http-equiv="refresh" content="1; URL='...'">, which causes the browser to reload
the page indefinitely. The same URL without query parameters works correctly. Switching to CACHE_STORE=file resolves the
issue immediately.

The static cache middleware (Statamic\StaticCaching\Middleware\Cache) creates a database lock to
serialize page rendering. The lock key is built by appending the full URL (including query string)
to a fixed prefix: {cache_prefix}static-cache-lock-{full_url}. For example:

statamic-cache-static-cache-lock-http://example.com/?utm_source=google&utm_medium=email&utm_campaign=summer_sale_2026_premium_collection&utm_content=banner_top_homepage_hero&utm_term=sofas+modulares+modernos+diseno+italiano&utm_id=campaign_123456789&gclid=EAIaIQob123

This key is 267 characters, exceeding the VARCHAR(255) constraint on the key column of the Laravel's
cache_locks table.

In MySQL/MariaDB with STRICT_TRANS_TABLES enabled, the INSERT fails with error 1406 ("Data too
long"). DatabaseLock::acquire() catches the QueryException and falls back to an UPDATE that
finds no rows (since the INSERT never succeeded), returning false. Lock::block() retries every
250ms for 30 seconds, then throws LockTimeoutException. The middleware responds with 503 + meta
refresh, which the browser immediately reloads — creating an infinite loop.

Projects created with statamic/statamic (statamic new) include a static_cache store in
config/cache.php with a file driver. StaticCacheManager::cacheStore() detects this store via
hasCustomStore() and uses it instead of the default database store, so locks use flock (no
length limit) and the bug never manifests.

Installations where Statamic is added to an existing Laravel project (composer require statamic/cms + php artisan statamic:install) or upgraded from older versions do not get this store added to
config/cache.php, so cacheStore() falls back to the default store (database), triggering the bug.

How to reproduce

Quick steps using Statamic installer:

statamic new statamic-db-cache
cd statamic-db-cache
sed -i'' 's/^CACHE_STORE=file/CACHE_STORE=database/' .env
# configure a MySQL/MariaDB database
sed -i'' 's/^STATAMIC_STATIC_CACHING_STRATEGY=null/STATAMIC_STATIC_CACHING_STRATEGY=half/' .env
sed -i'' "s/'ignore_query_strings' => true,/'ignore_query_strings' => false,/" config/statamic/static_caching.php
# remove 'static_cache' block on config/cache.php
# remove marketing parameters from 'disallowed_query_strings' on 'config/statamic/static_caching.php'
php artisan migrate
php artisan serve

# make a request
curl 'http://localhost:8000/?utm_source=google&utm_medium=email&utm_campaign=summer_sale_2026_premium_collection&utm_content=banner_top_homepage_hero&utm_term=sofas+modulares+modernos+diseno+italiano&utm_id=campaign_123456789&gclid=EAIaIQob123'

Logs

Environment

Environment
Laravel Version: 12.59.0
PHP Version: 8.4.21
Composer Version: 2.9.8
Environment: local
Debug Mode: ENABLED
Maintenance Mode: OFF
Timezone: UTC
Locale: en

Cache
Config: NOT CACHED
Events: NOT CACHED
Routes: NOT CACHED
Views: CACHED

Drivers
Broadcasting: log
Cache: database
Database: mysql
Logs: stack / single
Mail: log
Queue: sync
Session: file

Storage
public/storage: NOT LINKED

Statamic
Addons: 0
License Key: Not set
Sites: 1
Stache Watcher: Enabled (auto)
Static Caching: half
Version: 6.19.0 PRO

Installation

Existing Laravel app

Additional details

Proposed fix: hash the URL before using it as the lock key in Cache::createLock(), consistent with how
ApplicationCacher already hashes URLs for response cache keys:

// src/StaticCaching/Middleware/Cache.php
$key .= '-' . md5($this->cacher->getUrl($request));  // always 32 chars

Note that configuring disallowed_query_strings to filter common tracking parameters (UTM, gclid, etc.) reduces the likelihood of hitting this limit but does not eliminate the risk. Any combination of query parameters not covered by that list (like Pinterest epik) and a log SEO-oriented slug that pushes the lock key past 255 characters will trigger the same failure. The only reliable fix is to hash the URL before using it as the lock key.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions