- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
What Is a CDN Cache Key, and Why Are Your Users Seeing Stale Pages?
About Post
You deployed the fix twenty minutes ago. Your laptop shows the new page. Your colleague's phone shows the old one. A customer sends a screenshot of a version you replaced last week. Someone suggests "clear your cache", and it works for exactly one person.
And then there's the scarier cousin of that bug: a user who opens the site and sees a page meant for someone else, or a page in the wrong language.
Both problems usually come from the same small concept that most developers never look at directly: the CDN cache key. Let's open it up.
A cache key is the CDN's filing system
A CDN keeps copies of your responses on servers close to your users. When a request arrives, it has to answer one question: do I already have this exact response?
To answer it, it builds a cache key from parts of the request. A typical default is roughly:
https + www.example.com + /units + ?page=2
Scheme, host, path and query string. If the key matches a stored copy that's still fresh, the CDN serves it without asking your server. The exact defaults differ between providers, and most let you configure them (CloudFront, for example, uses cache policies that decide which headers, cookies and query strings are part of the key).
The crucial part is what's not in the key. By default that's usually cookies, most headers, and anything about who the user is. The CDN treats all requests with the same key as the same request.
Think of a library that files books only by title. Two different books with the same title? One of them will be handed to the wrong person.
Mystery 1: the page that belonged to someone else
Your server renders a page differently depending on something outside the key: the session cookie, the Accept-Language header, the device type. The CDN doesn't know that. It stores the first version it sees, and serves it to everyone with the same URL.
That's how one user's dashboard, greeting or language ends up shown to others. It's not a hacker; it's a filing mistake. And when the page contains personal data, it's a serious one.
The fixes, from most to least important:
- Mark personal responses as uncacheable by shared caches:
Cache-Control: private, no-storeon account pages and authenticated API responses. Don't rely on your CDN's defaults to guess. - Put the thing that varies in the URL (
/en/,/ar/) rather than in a header, so it's naturally part of the key. - Or add it to the key explicitly, using the
Varyheader or your CDN's cache key settings.
The Vary header, and why it's a trap
Vary tells caches "this response depends on these request headers, so include them in the key". Vary: Accept-Encoding is normal and harmless: gzip and Brotli versions are stored separately.
But Vary: Cookie or Vary: User-Agent is a different story. Every user has different cookies; there are countless user agent strings. Your one cached page becomes thousands of tiny caches, each used once, and your hit rate collapses. CDNs also differ in how fully they honour Vary, so check your provider's docs rather than assuming.
Mystery 2: query strings that split (or merge) your cache
Query strings cause problems in both directions.
Too much in the key. A marketing campaign adds ?utm_source=newsletter&utm_campaign=june to your links. To the CDN, every combination is a different page, so every visitor from the campaign misses the cache and hits your server, exactly when traffic is highest. Parameter order matters too: ?a=1&b=2 and ?b=2&a=1 may be two keys unless your CDN normalises them.
Too little in the key. Someone "fixes" that by telling the CDN to ignore query strings. Now /units?page=2 returns the cached page 1. Search results all look identical.
The right setting is an allowlist: include the parameters that change the content (page, sort, q), ignore the ones that don't (tracking parameters).
Mystery 3: the fix that didn't arrive
Now the stale page. How long a copy stays fresh comes from your Cache-Control header (or the CDN's default TTL if you don't send one). Two directives matter most:
max-age: how long any cache, including the user's browser, may reuse the response.s-maxage: overridesmax-agefor shared caches like CDNs only.
Here's the part that catches people: purging the CDN doesn't touch browsers. If you sent HTML with max-age=86400, every browser that loaded it may keep showing the old page for a day, whatever you do at the CDN. You can't call those copies back.
A setup that avoids most of this pain:
# Fingerprinted assets (app.3f9a2b.js): cache "forever"
Cache-Control: public, max-age=31536000, immutable
# HTML: browsers revalidate, the CDN keeps it briefly
Cache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=60
# Account pages and personal API responses: never shared
Cache-Control: private, no-store
Build tools like Vite already put a content hash in asset file names, so a new deploy means new URLs and new cache keys. Nothing needs purging. The HTML that references them stays short-lived, so users pick up new asset URLs quickly.
In Laravel, the built-in cache.headers middleware sets these for a group of routes:
Route::middleware('cache.headers:public;max_age=0;s_maxage=300;etag')
->group(function () {
Route::get('/units', [UnitController::class, 'index']);
});
One Laravel gotcha: routes in the web middleware group start a session and send a Set-Cookie header on every response. Many CDNs won't cache a response that sets a cookie, and a CDN configured to cache it anyway may hand one visitor's session cookie to the next. Public, cacheable pages should not set cookies at all.
The rule: cache by URL design, not by hope. Versioned file names for assets, short TTLs for HTML, private, no-store for anything personal, and a query string allowlist. Then purging becomes rare instead of routine.
When you do need to purge
- By URL: precise, but remember every variant of the key (with and without query strings, both hostnames).
- By prefix or tag: some CDNs let you tag responses (for example, every page that shows unit 42) and purge the tag. Very useful for content-heavy sites.
- Everything: the big red button. It works, but every request suddenly goes to your origin at once. Avoid it during peak traffic.
How to debug it in two minutes
Look at the response headers with curl -I or the browser's network tab. The Age header tells you how many seconds the copy has been in a cache. Most CDNs add a hit/miss header too (CF-Cache-Status on Cloudflare, X-Cache on CloudFront). If Age is large and the status is a hit, you're looking at a cached copy, and now you know which layer to blame.
Recap
- The cache key decides what counts as "the same page". Know what's in yours.
- Anything that changes the response but isn't in the key is a bug waiting to happen.
- Be careful with
Vary; allowlist query strings. - Purges don't reach browsers; versioned URLs do.
What's the strangest caching bug you've chased? I have a feeling most of them end with the words "it was the query string".

Be first to comment it...