In short and in plain language
When a sale starts, prices change. This empties a store of ready pages. This store is called a cache.
If the cache is empty, the server builds each page again. When many people come at once, the shop gets slow.
The fix: a small program visits the key pages first. Then they are ready. This is called warming the cache.
The order matters. First change the prices. Then warm the cache. Then start ads and the newsletter.
Before that, you check that the pages come from the cache. We are happy to help you with this.
At 12:00 am Black Week begins, the newsletter is scheduled, the ads are going live, and in that very minute the prices in the shop have just been switched. For the cache, this is the worst possible moment: the price switch knocks pages out of the cache, and the first visitors of the sale hit a server that has to build every page from scratch. In 2026, Black Friday falls on November 27, the day after Thanksgiving (US Office of Personnel Management). That leaves time to rehearse the night once. Using Shopware as the example, this article shows what drops out of the cache during the price switch, how to build a warm-up list from field data and the sitemap, in which order the price switch, cache warming and release should run, and how to measure the cache hit rate before the newsletter goes out.
Key takeaways
- Shopware deprecated its built-in HTTP cache warmers in version 6.5.8.0 (Shopware upgrade notes 6.5) and recommends a real crawler that fetches pages along the same path your customers use.
- Since Shopware 6.7, the shop discards changed entries in batches every 5 minutes (Shopware upgrade notes 6.7). A price import shortly before midnight can push its invalidation right into the sale launch.
- Shopware does not purge search results selectively when a price changes; they run on their cache lifetime. Category pages follow the product via their own listing tag, but with a delay. The price switch therefore calls for clearing the HTTP cache and fetching pages again.
- The warm-up list has three stages: promo pages including newsletter and ad link targets, then categories, then top sellers ranked by views in the last sale. The sitemap supplies the URLs, field data the order.
- Release the newsletter, push messages and ads only once stage one is served from the cache: an Age value above zero shows the hit, and a response time of up to 0.8 seconds (web.dev) counts as good.
Why the first minute finds an empty cache
A sale launch packs three events into a few seconds. Prices change, banners and promo pages go live, and newsletters, push messages and ads send visitors to exactly those pages at the same time. Roughly half of internet users aged 16 and over in Germany planned to use the Black Friday deals in 2025, 52 percent (Bitkom 2025), most of them online. For Black Friday and Cyber Monday combined, the German Retail Federation expected revenue of EUR 5.8 billion (HDE 2025). If you announce a fixed start time, part of that demand arrives at exactly that time.
In that minute, the cache works against you. An HTTP cache answers a request itself only if it already holds a valid response for exactly that URL and that context. After a price switch, that is no longer true for many pages: Shopware tags every stored response with cache tags and, when a product is saved, discards the entries that carry that product (Shopware documentation). The next visitor has to fetch every discarded page from PHP and the database again. When many visitors hit many different, freshly discarded pages at once, all of those requests land in the same PHP process pool. How many of them run in parallel is capped by the pm.max_children setting (PHP manual); everything else waits. There is a reason the Shopware documentation calls the HTTP cache a must-have for every production system.
You feel this in the server response time. A Time to First Byte of 0.8 seconds or less counts as good, anything above 1.8 seconds as poor (web.dev). From the cache, a Shopware page usually comes in well below that; without the cache, it depends on the database, rule evaluation and template rendering. How much load your system can take overall is what a load test before peak season tells you. This article is about not letting the load hit PHP unchecked at sale launch in the first place.
Scope
What drops out of the cache during the price switch
Which pages are cold after the price switch depends on how the switch works technically. Shopware treats two kinds of pages differently. Product pages carry the tags of the products they show and are discarded when one of those products is saved. Listings, on the other hand, meaning category pages, search results and product lists, are not tagged with every product they contain, according to the documentation. They rely on their cache lifetime, and the documentation explicitly accepts that they can be out of date for a while (Shopware documentation).
These routes instead rely on their TTL as defined by the active HTTP Caching Policy.
The source code handles category pages more finely: the product listing of a category carries its own listing tag, and after a product is saved, Shopware discards that tag for every category the product is assigned to, with the same delay as the product page (Shopware source code). Search results get no such tag. For the sale launch, this means: after a price import, search results can keep showing the old price until their cache lifetime runs out. The cart and checkout are not served from the cache and already calculate with the new price. To customers, this looks like an error, even though it is not one. How cache tags and the reverse proxy work together in Shopware is summarized in Shopware performance: faster loading times.
Delayed invalidation since Shopware 6.7
Since Shopware 6.7.0.0, the shop no longer discards entries immediately. It collects the affected tags and processes them at a fixed interval, and the default interval is 5 minutes (Shopware upgrade notes 6.7). Day to day, this raises the hit rate because duplicate invalidations disappear. On the night of the sale, it shifts the problem: a price import at 11:58 pm may only take effect in the cache after midnight, exactly when visitors arrive. Shopware offers two tools for such cases. The sw-force-cache-invalidate: 1 header on an Admin API request invalidates immediately, and the cache:clear:delayed command runs all collected invalidations at once. With cache:watch:delayed, you can see in advance which tags are still waiting in the queue.
Time-based prices via rules
One way to avoid the import at night is advanced prices bound to a rule with a date range; the Rule Builder accepts both date and time for this (Shopware user documentation). For the cache, that has a downside. At midnight, no record is saved, so nothing triggers an invalidation. Anonymous visitors with an empty cart also do not carry their own cache hash, because Shopware only sets it once the state differs from the default (Shopware documentation). A page that entered the cache with the old price at 11:55 pm can therefore keep being served after midnight until its lifetime ends or someone clears the cache. Whether your shop behaves this way is shown by a trial run on staging with a rule that starts a few minutes in the future.
What a reverse proxy still serves afterwards
If a reverse proxy sits in front of the shop, a second question comes up: what does it serve after a page has expired or been discarded? The example configuration Shopware provides for Varnish keeps serving expired entries for three days after expiry while fetching a fresh copy in the background (Shopware example configuration). In normal operation, that is intended. For the price switch, it means a page whose lifetime ends at midnight is served once more with the old price on the first request afterwards. Soft purging, which Shopware describes as an option, has a similar effect: it keeps the old page and refreshes it in the background (Shopware documentation).
The same principle sits behind the stale-while-revalidate directive in the Cache-Control header. If you set it together with max-age to 600 seconds each, the RFC says you have to accept that a response may be served from the cache for up to 20 minutes (RFC 5861). Outside the sale night, that is a good trade; at the price switch, it is not. That is why the switch calls for a hard purge, followed by cache warming.
| Path to the sale price | What the cache notices | What to do that night |
|---|---|---|
| Price import via the Admin API | Product and category pages are discarded via their tags, batched at the delayed invalidation interval | Send the import with sw-force-cache-invalidate or run cache:clear:delayed afterwards |
| Advanced prices with a date range | Nothing is saved at midnight, no event discards entries | Clear the HTTP cache at the switch and warm it afterwards |
| Search results | Not discarded via product tags, they run on their cache lifetime | Purge them selectively or clear the HTTP cache |
| New promo bar in the header | Affects the header's own fragment, not every page individually | Stage one of the warm-up list rebuilds it along the way |
| Expired entries in the reverse proxy | Depending on configuration, served once more with the old state | Hard purge instead of soft purge at the switch |
The cache rework in Shopware 6.7.0.0 brings some relief: since then, the storefront loads the header and footer via ESI and stores them separately from the rest of the page (Shopware upgrade notes 6.7). A new promo bar in the header therefore affects one fragment, not every page individually. How to build that bar without a layout shift is covered in Promo banner for Black Week without layout shift.
The built-in warmer is deprecated
Shopware used to ship its own command for warming the HTTP cache. In version 6.5.8.0, the vendor deprecated all built-in HTTP cache warmers (Shopware upgrade notes 6.5). The reason given in the same section: they do not fit the new HTTP kernel, the cache rework, or systems with a reverse proxy or load balancer in front. The recommendation follows directly below.
You should use instead a real website crawler to warmup your desired sites, which is much more suitable and realistic for your system.
Realistic means the crawler fetches pages along exactly the path your customers take. It addresses the shop through the public domain and HTTPS, through the load balancer and reverse proxy, not a single application server directly. Only then does the response end up in the cache that later serves visitors. It sends plain GET requests without cookies, so the responses land in the default variant shared by all anonymous visitors with an empty cart.
For logged-in customers, other currencies or net prices, Shopware creates separate variants, distinguished by the cache hash. These variants cannot be warmed with a made-up hash. Since Shopware 6.7.6.0 with the cache rework enabled, and by default from version 6.8, the shop compares the visitor's hash with the one it calculates itself and does not store the response in the cache if they differ (Shopware documentation). If you want to warm B2B prices, you need real sessions with dedicated test accounts per customer group, not accounts from live operation.
The warm-up list: promo pages, categories, top sellers
A crawler that fetches the entire shop takes longer than the night allows for large catalogs, and it loads the server right before the launch. A list sorted by expected demand makes more sense. Three sources feed it:
- Sitemap: It contains the URLs of all indexable pages per sales channel. A single sitemap file holds at most 50,000 URLs; beyond that, the list is split across several files under an index (sitemaps.org). So the crawler has to follow the index.
- Field data: Server access logs and the analysis of the last sale show which pages were actually requested in the first hours. How to collect field data reliably is covered in RUM vs. CrUX.
- Campaign plan: Marketing knows the link targets of the newsletter, push messages and ads days in advance. These URLs belong in the list in full, even if they are not in any sitemap.
The sitemap itself should have been generated long before. The documentation describes generating it for large catalogs as resource-intensive and shows how to run it as a separate cron job at a quiet time (Shopware documentation). On the night of the sale, it has no place in the queue.
Three stages by urgency
- Promo pages: the sale landing page, the home page, the sale category and every URL that the newsletter, push messages or ads point to. This stage must be served entirely from the cache before release.
- Categories: the main categories with their first page in the default sort order, plus the filter and sort combinations that occur frequently in the field data from the last sale.
- Top sellers: product pages in the order of views and orders during the previous sale, plus the items that marketing features in the newsletter and ads.
Campaign parameters deserve a separate look. Shopware's example configuration for Varnish removes common parameters such as utm_source or gclid before the cache lookup and sorts the remaining ones (Shopware example configuration). If your shop runs with the built-in cache or its own proxy configuration, check in advance whether a newsletter link with parameters hits the same entry as the warmed URL. How to measure this is shown in the article on cache fragmentation from cookies and Vary.
Keep the list small
Schedule for the night: switch early, warm, release
The most important lever is not a tool but a separation: the moment prices change and the moment visitors arrive do not have to be the same. You trigger the visitor flow yourself, with the newsletter, push message and ad launch. The window for cache warming lies in between.
Option A: switch prices early
The most robust approach is to switch prices before the announced start time, for example at 11:30 pm, when few visitors are in the shop. The cold cache then meets light traffic, the crawler has half an hour, and at midnight the newsletter and ads go out to a filled cache. Whether an earlier start fits the campaign messaging is for marketing to decide. For customers, a deal that applies a few minutes early is usually no disadvantage.
Option B: switch exactly at midnight
If the price has to change at exactly 12:00 am, for instance because marketplaces or price comparison sites expect that time, the window gets shorter, but not zero. Everything heavy runs beforehand: sale prices as prepared data, indexing, sitemap, a dry run of the crawler. At midnight, only the switch itself follows, immediately after that the purge and the crawler. The newsletter and ads go out only once stage one is served from the cache, usually a few minutes after midnight.
- From 10:00 pm, a quiet system: no deployments, no bulk imports, the ERP sync is paused, indexing and sitemap are finished.
cache:watch:delayedshows an empty queue. - 11:15 pm, check list and crawler: final warm-up list, dry run against a few URLs, status codes and timings in the log.
- 11:30 pm, price switch (option A): import with immediate invalidation or start of the rule, then
cache:clear:httpso that category pages and search results are rebuilt as well. - From 11:31 pm, warm the cache: stage 1, then stage 2, then stage 3, with limited concurrency.
- 11:50 pm, measure: share of hits in stage 1, response times, error rate. Only continue once the numbers hold.
- 12:00 am, release: newsletter, push message and ads start. From now on, observe and stop changing things.
Since Shopware 6.6.10, there is a dedicated command for clearing: cache:clear:http clears the reverse proxy or, without a proxy, the shop's HTTP cache (Shopware documentation). The general cache:clear is the wrong tool here; it clears and rebuilds the application cache, and in older versions it cleared the HTTP cache along with it. The crawler itself needs no special software. A short script with curl is enough, as long as it limits concurrency and logs what comes back:
#!/usr/bin/env bash
# Fetch the warm-up list: one URL per line, stage 1 first.
# Usage: ./warm-cache.sh stage-1.txt
LIST="$1"
LOG="warm-$(date +%H%M%S).log"
PARALLEL=4 # keep it small: PHP needs headroom for real visitors
xargs -P "$PARALLEL" -I{} curl -s -o /dev/null \
-A 'cache-warmer/1.0' \
-w '%{http_code};%header{age};%{time_starttransfer};%{url_effective}\n' \
'{}' < "$LIST" > "$LOG"
# Summary: status, hits (Age above 0) and slowest response.
awk -F';' '$1 != 200 {err++} $2 > 0 {hit++} $3 > max {max = $3}
END {printf "%d URLs, %d hits, %d without status 200, slowest %.2f s\n", NR, hit, err, max}' "$LOG"
The concurrency in the script is deliberately low. The crawler is meant to fill the cache, not to become a load test of its own. The load test is a good reference: once you know how many simultaneous page requests the server can handle without the cache, keep the crawler well below that. Newer curl versions understand the %header{age} field; older ones can write the headers to a file with -D.
What should not happen on the night of the sale
Measure the hit rate instead of hoping
Every single response shows whether the cache is filled. The Age header states how much time has passed since the response was generated or last validated at the origin server (RFC 9111). A value above zero means this response came from a cache. If you see only 0 as the Age, the cache is not working according to Shopware (Shopware documentation); the most common reason is a response without Cache-Control: public. Three measuring points are enough for the night of the sale:
- Hit share per stage: a second crawler pass over stage 1 counts how many responses carry an Age above zero. Before release, that should be every URL in the stage.
- Response time: the time to first byte from the same log, compared with a run on the previous day. From the cache, it is well below the 0.8-second mark that web.dev rates as good.
- Load and errors: busy PHP processes, responses with a 5xx status and the length of the queue in the minutes after release.
If you want to anchor this measurement in daily operations, have the server send hits and misses in the Server-Timing header; the article on Server-Timing shows how. The reverse proxy statistics also count hits and misses across all visitors and show after release whether the rate holds. If it drops noticeably in the first minutes, the most common reason is an invalidation only now coming out of the queue.
The rule for the night of the sale
Trial run: rehearse the night before Black Week
A procedure that runs once a year tends to fail on small things: a missing permission for a command, a crawler that bypasses the load balancer, a newsletter tool with its own time zone. That is why a trial run on an ordinary evening in the weeks before pays off, with a small real price change. You measure two things: how long the crawler needs for stage one, and how long the hit rate stays down after clearing without a crawler. The difference is your gain, and it shows whether you need the option with the early price switch.
A fixed log per step helps: time, command, result, measured value. On the night of the sale, it replaces memory and makes the procedure repeatable next year. Where the shop already reaches its limits under normal traffic, look at server optimization and server response time before the trial run. A warm cache only hides bottlenecks for the pages it knows; the first search, the cart and the checkout still run through PHP.
We support this procedure for Shopware shops: a warm-up list from your field data, a crawler along the real path, invalidation that matches your price import, and a trial run with a measurement log. More on our approach is on the pages about caching strategies and Shopware performance; for online shops with seasonal peaks, we bring these topics together under PageSpeed for e-commerce. How to estimate the effort for this kind of work is covered in What does PageSpeed optimization cost?.
Sources and documentation
cache:clear:http at the price switch, followed by cache warming.cache:clear:delayed after the import.cache:clear:delayed, then fetch the product page and the categories that contain the item again. Clearing the entire HTTP cache in the middle of the sale sends all pages back to PHP at once and only makes sense as a last step. How a shop can prepare for interventions like this is something we clarify in a technical analysis.Related Articles
Shopware Performance: Faster Loading Times
Make Shopware CE faster with built-in tools: integrated HTTP cache and reverse proxy, cache warmup, OpenSearch and the performance gain of a 6.x update.
Slow Shop Backend: Speeding Up the Admin for Your Team
When the Shopware admin turns sluggish in peak season, four levers help: CLI workers instead of browser tabs, admin search, lean lists and nightly indexing.
Adding Black Week Promo Banners Without Layout Shift
Add Black Week notice bars, countdowns, pop-ups and badges without anything jumping: reserve space, deliver from the server and keep CLS at 0.1 or below.