Skip to content

The Black Friday wall of shame: a decade of outages, and the tests that would have caught them

Best Buy, Costco, J.Crew, Macy's, Currys, Shopify, HSBC: a wall of shame of real Black Friday failures, what actually caused each one, and the MaxoPerf test YAML that finds it before your shoppers do.

Black Friday 2026 falls on Friday, 27 November. You have about twenty weeks.

That number is the whole argument for testing. Black Friday is not a surprise outage, a viral moment or an act of God. It is the one traffic peak on the calendar whose date, rough size and traffic shape you can know months ahead. That makes it the most preventable category of load failure and the most humiliating one to suffer. Every year, some of the largest retailers on earth suffer it anyway.

The reason is rarely that nobody tested. Usually the thing that broke was never the thing under test. Storefront reads scale well, and the login path does not. The team rehearsed the peak but not the ramp to the peak. The site held and the payment processor did not. Checkout worked while the order-confirmation write path quietly corrupted gift-card balances. Load does not break systems evenly. It finds the one shared resource nobody thought to point traffic at.

So run the test six to eight weeks out, not two. A test two weeks before Black Friday tells you that you have a problem and leaves no time to fix it, re-test and deploy safely. What follows is a wall of shame: real, dated, sourced Black Friday and Cyber Monday failures going back to 2014, what actually caused each one, and a MaxoPerf test configuration that would have found it.

First, a note on honesty. Retailers almost never publish postmortems. Across every incident below, the company involved confirmed only a handful of causes. Where a cause is known, this post says so. Where it is not, this post says the cause was never disclosed instead of inventing a plausible database that ran out of connections. The failure pattern teaches you something either way. A made-up mechanism does not.

Each example below is a Taurus YAML, MaxoPerf’s test format. Two things to know before you read them: the engine is auto-detected from your entrypoint file, and locations and failure criteria are not YAML keys. You configure those in the test’s Configuration tab (or via the Platform API), which is why you will not see a locations: or thresholds: block anywhere in this post.


1. Best Buy: 2014, and again in 2025

The pattern: you tested the peak, not the ramp.

On Friday 28 November 2014, BestBuy.com went fully offline. Best Buy pulled the site down on purpose to restore performance, and a company spokeswoman attributed it to “a concentrated spike in mobile traffic.” It crashed a second time later that same day. Cause: company-confirmed.

Eleven years later, on Friday 28 November 2025, it happened again. From roughly 9:40 a.m. ET to 11:50 a.m. ET, Best Buy’s site and app failed for a large share of shoppers. Downdetector logged over 1,800 reports at peak, around 80% of them from desktop web. This time Best Buy published no explanation at all. Cause: never disclosed. Search engines often attach the 2014 “mobile traffic” quote to this 2025 outage, but it does not belong to it. Our full write-up of the 2025 incident goes deeper.

The general lesson sits in one word of the 2014 quote: concentrated. A system can hold 40,000 concurrent users at steady state and still fall over when 38,000 of them arrive inside ninety seconds, because deals went live at a posted hour and everyone had the page pre-loaded. Autoscaling reacts in minutes. A deal drop happens in seconds.

Treat the ramp rate as a property you test in its own right, alongside the plateau. Model the deal-drop cliff by compressing the whole arrival into ramp-up, then holding:

execution:
- scenario: deal-drop
concurrency: 40000 # your Black Friday peak target
ramp-up: 90s # deals go live — this is the part that kills you
hold-for: 20m # can you *stay* up, not just arrive
steps: 10 # climb in 10 discrete jumps, so the knee is legible
scenarios:
deal-drop:
variables:
base_url: https://staging.example.com
requests:
- label: GET /
url: ${base_url}/
- label: GET /deals/black-friday
url: ${base_url}/deals/black-friday
- label: GET /search
url: ${base_url}/search?q=television
- label: POST /cart/items
url: ${base_url}/cart/items
method: POST
headers:
Content-Type: application/json
body: '{"sku": "sku-doorbuster-01", "qty": 1}'

Watch requests per second on POST /cart/items during the 90-second climb. If throughput plateaus while virtual users keep climbing, you have found your ceiling, and you found it in September. See also the spike test type.

Native Taurus expresses a ramp as ramp-up plus optional steps, not as a list of arbitrary {duration, target} stages. If you need a real multi-stage profile (up, down, then up harder), use executor: k6 with a k6 script and define the stages there.


2. Shopify: Cyber Monday, 1 December 2025

The pattern: the chokepoint was authentication, not the storefront.

This is the rare entry with a real, company-confirmed technical cause. On Monday 1 December 2025, the peak day of Shopify’s largest BFCM weekend on record, merchant admin dashboards, point-of-sale terminals, and token-based API integrations failed for roughly five to six hours. Shopify stated it had “found and fixed an issue with our login authentication flow.” Storefront checkout, by Shopify’s account, stayed up. Cause: company-confirmed (mechanism beyond “login authentication flow” was never detailed). We covered it in depth here.

Look closely at that shape, because it is the most common way a “well-tested” site dies. Product and category pages are cacheable, idempotent and easy to scale out. Login is a write. It mints a session, touches a shared session store and often calls an identity provider. It saturates first, and when it does, nothing downstream of it works no matter how healthy your CDN looks.

So test the auth path on its own, at its own concurrency, all the way through to a real authenticated action. Ramp until the knee appears:

execution:
- scenario: merchant-login
concurrency: 12000
ramp-up: 10m # climb steadily; the knee is what you're shopping for
hold-for: 10m
scenarios:
merchant-login:
variables:
base_url: https://staging-admin.example.com
access_token: ''
requests:
- label: POST /auth/login
url: ${base_url}/auth/login
method: POST
headers:
Content-Type: application/json
body: '{"email": "${LOGIN_EMAIL}", "password": "${LOGIN_PASSWORD}"}'
extract-jsonpath:
access_token: '$.access_token'
assert:
- subject: http-code
contains: ['200']
- label: GET /admin/dashboard
url: ${base_url}/admin/dashboard
headers:
Authorization: Bearer ${access_token}
- label: POST /pos/transactions
url: ${base_url}/pos/transactions
method: POST
headers:
Authorization: Bearer ${access_token}
Content-Type: application/json
body: '{"terminal_id": "pos-load-01", "amount_cents": 1999}'

LOGIN_EMAIL and LOGIN_PASSWORD are project secrets. Use dedicated load-test credentials, never a real merchant’s. The breakpoint test type turns the knee into a number you can plan capacity against, and the auth login + token capture recipe covers the token plumbing in full.


3. Costco: Thanksgiving 2019, and Black Friday 2024

The pattern: you survived the spike and died in hour three.

On Thursday 28 November 2019, Costco’s site and app went down for more than sixteen hours, spilling into Black Friday morning. Third-party analysts (Love the Sales) estimated around 2.65 million affected customers and roughly $11 million in lost sales. That is an estimate, not a Costco figure. Costco posted only that it was seeing “longer than normal response times.” Five years later, on Friday 29 November 2024, Costco’s site again threw internal server errors for hours. Cause: never disclosed, in either year.

Sixteen hours is not a spike. A spike test that ramps, holds for twenty minutes and drops would have passed. What kills a system over hours is different: memory that never gets reclaimed, connection pools that leak a handle per error, log volumes that fill a disk, caches that thrash once the working set exceeds RAM, replication lag that compounds. These are soak failures, and only sustained load reveals them.

Run the peak shape for longer than the event itself:

execution:
- scenario: storefront-soak
concurrency: 8000 # ~50% of measured peak, held indefinitely
ramp-up: 10m
hold-for: 12h # longer than the outage you are trying not to have
throughput: 4000 # cap RPS so the shape stays predictable
scenarios:
storefront-soak:
variables:
base_url: https://staging.example.com
requests:
- label: GET /
url: ${base_url}/
- label: GET /warehouse/inventory
url: ${base_url}/warehouse/inventory
headers:
Authorization: Bearer ${API_TOKEN}
- label: POST /cart/items
url: ${base_url}/cart/items
method: POST
headers:
Content-Type: application/json
Authorization: Bearer ${API_TOKEN}
body: '{"sku": "sku-soak-01", "qty": 1}'

Keep soak scenarios boring on purpose: sustained pressure, not scenario coverage. Then read p95 latency as a slope, not a value. A p95 that is flat at hour one and 40% higher at hour eight is a leak, even if it never breaches your threshold. Schedule it overnight with the overnight soak recipe so nobody has to babysit it.


4. J.Crew and Lowe’s: 2018, after both had already failed in 2017

The pattern: the regression came back, because nothing was watching.

On Friday 23 November 2018, J.Crew’s site failed for four to five hours from around 10:54 a.m. EST, showing shoppers a “Hang on a Sec” screen with no sign-in, no add-to-cart and no orders. Love the Sales estimated ~323,000 affected shoppers and roughly $775,000 in lost sales (again, a third-party estimate). J.Crew said only: “Due to high demand, we’re experiencing some technical difficulties.” The same day, Lowe’s took its site offline behind a “down for maintenance” page until about 2:30 p.m. ET.

The worst part: both had done exactly this the year before. Lowe’s went down on Black Friday 2017 and J.Crew’s 2017 failure came three days later, on Cyber Monday. Cause: never disclosed, either year, by either company.

A one-off outage is bad luck. Failing the same way twelve months later is a missing feedback loop. Nothing in either company’s release process measured peak-path performance against a known-good baseline, so whatever regressed in 2017 was free to regress again in 2018. The fix is a small load test that runs on every release candidate and fails the build. A bigger annual test is not the fix.

execution:
- scenario: checkout-baseline
concurrency: 200 # small, fast, runs on every merge
ramp-up: 1m
hold-for: 5m
scenarios:
checkout-baseline:
variables:
base_url: https://staging.example.com
requests:
- label: GET /product
url: ${base_url}/product/sku-001
- label: POST /cart/items
url: ${base_url}/cart/items
method: POST
headers:
Content-Type: application/json
body: '{"sku": "sku-001", "qty": 1}'
- label: POST /checkout
url: ${base_url}/checkout
method: POST
headers:
Content-Type: application/json
body: '{"cart_id": "cart-ci-01"}'

The YAML is the easy half. The half that matters is in the Configuration tab. Set failure criteria per request label (say, POST /checkout p95 above 300 ms, or overall error rate above 0.5%) so the run comes back failed, not finished. Then trigger it from CI and let the run status fail the build. That is the whole CI-gated performance test pattern. It turns a baseline regression test into a check that stops a repeat.


5. Argos, Currys PC World, Tesco Direct, Net-a-Porter, GAME: UK, Black Friday week 2014

The pattern: reads were fine; the write path collapsed.

Britain’s first mass-market Black Friday was a rout. Tesco Direct and Currys PC World hit trouble in the early hours, and Currys fell back to queueing shoppers instead of serving them. Argos was still failing on and off at 18:45 (nine hours in), with shoppers reporting they could browse fine but could not get through checkout. Two days earlier, Net-a-Porter had opened its sale early and gone down within the hour, with users reporting failures specifically at the payment page. GAME reported problems too. Police were called to physical Tesco stores over crowd scuffles. Cause: reported as demand overload; no company published a technical postmortem.

Every one of those reports has the same shape. The catalogue worked. The search worked. The last mile broke: the stateful, non-cacheable, write-heavy end of the journey, where every request is unique and nothing can be served from an edge.

That is why a load test that hammers GET / proves nothing. You need unique data per virtual user, so no cache can rescue you and no two shoppers contend for the same cart:

execution:
- scenario: full-journey
concurrency: 6000
ramp-up: 5m
hold-for: 25m
scenarios:
full-journey:
data-sources:
- path: shoppers.csv
variable-names: email,password,sku
random-order: true
requests:
- label: GET /search
url: https://staging.example.com/search?q=${sku}
- label: GET /product
url: https://staging.example.com/product/${sku}
- label: POST /auth/login
url: https://staging.example.com/auth/login
method: POST
headers:
Content-Type: application/json
body: '{"email": "${email}", "password": "${password}"}'
- label: POST /cart/items
url: https://staging.example.com/cart/items
method: POST
headers:
Content-Type: application/json
body: '{"sku": "${sku}", "qty": 1}'
- label: POST /checkout/pay
url: https://staging.example.com/checkout/pay
method: POST
headers:
Content-Type: application/json
body: '{"payment_method": "test-card", "sku": "${sku}"}'

Upload shoppers.csv as a data entity with more rows than you have virtual users, so nothing recycles. Then set failure criteria per label. A run where GET /search sits at 40 ms and POST /checkout/pay sits at 9 seconds passes by any global average, and fails your shoppers on the day.


6. Currys: Black Friday, 27 November 2020

The pattern: it did not go down. It went wrong.

The most interesting failure on this wall is not an outage. On Friday 27 November 2020, Currys’ site buckled under demand. Along the way, customers had gift-card balances drained (hundreds of pounds, £450 in one reported case) for orders that never completed. Some shoppers received confirmation emails for orders that did not exist. Refunds took until the following Wednesday. Currys acknowledged a demand surge plus an order-processing “technical fault.” Cause: company-acknowledged as a fault; the mechanism was never disclosed.

Nobody load tests for this failure mode, because every dashboard stayed green. Requests returned 200. Latency was survivable. Meanwhile the system was quietly taking money without creating orders: a partial write, a non-idempotent retry, a saga that debited before it committed. Under load, timeouts and retries stop being rare, and any write path that is not idempotent will eventually run twice or halfway.

So test correctness under load, not only latency under load. Give each virtual user a unique idempotency key, replay it on purpose, then read the order back and check that the state you expect exists:

execution:
- scenario: order-integrity
concurrency: 2000
ramp-up: 3m
hold-for: 15m
scenarios:
order-integrity:
variables:
order_id: ''
data-sources:
- path: idempotency-keys.csv
variable-names: idem_key,gift_card_id
random-order: false
requests:
# 1. Place the order, capturing the id the server assigns
- label: POST /orders
url: https://staging.example.com/orders
method: POST
headers:
Content-Type: application/json
Idempotency-Key: ${idem_key}
body: '{"gift_card_id": "${gift_card_id}", "amount_cents": 45000}'
extract-jsonpath:
order_id: '$.order_id'
# 2. Replay the *same* key — a duplicate must not create a second order
# and must not debit the gift card twice
- label: POST /orders (replay)
url: https://staging.example.com/orders
method: POST
headers:
Content-Type: application/json
Idempotency-Key: ${idem_key}
body: '{"gift_card_id": "${gift_card_id}", "amount_cents": 45000}'
# 3. Read it back — if money moved, the order must exist
- label: GET /orders/{id}
url: https://staging.example.com/orders/${order_id}
assert:
- subject: http-code
contains: ['200']
# 4. And the balance must reflect exactly one debit
- label: GET /giftcards/{id}/balance
url: https://staging.example.com/giftcards/${gift_card_id}/balance

If extract-jsonpath fails to populate order_id, step 3 requests /orders/ and the error rate on that label goes to 100% and gives you exactly the signal you want. Set a failure criterion on the GET /orders/{id} label’s error rate at zero. The correlation and dynamic values recipe covers extraction in detail.


7. Macy’s (2016, 2017) and HSBC UK (2023)

The pattern: the thing that failed was not your code.

Macy’s, Friday 25 November 2016: roughly five hours of a “temporary shopping jam” throttle page and a broken add-to-cart, which Macy’s confirmed as high volume slowing the site. Macy’s again, Friday 24 November 2017: for several hours, in-store credit-card and gift-card processing failed across Chicago, Washington D.C., San Diego and other markets. Shoppers photographed abandoned carts. Macy’s said its systems were “over capacity” and explicitly not breached. Cause: company-confirmed as capacity. Whether the fault sat in Macy’s systems or its card processor was never detailed.

Then, on Friday 24 November 2023 (the UK press called it “Blackout Friday”), HSBC UK went down from around 07:00 GMT until after 23:00. Online and mobile banking failed, and so did debit, credit and contactless card payments at the point of sale. HSBC called it “an internal system issue.” Cause: company statement only; no postmortem.

The lesson is uncomfortable. On Black Friday, a large part of your revenue path runs on infrastructure you neither own nor can scale. Payment authorisation, tokenisation, fraud scoring, loyalty lookups, tax calculation and address validation are each a synchronous third-party call sitting directly in your checkout. Your capacity is the minimum across all of them, not your own.

Test the payment leg as a rate, because that is how processors think and how they rate-limit. Use rate-controlled load. throughput sets the target requests per second, while concurrency only caps how many virtual users may be in flight to sustain it:

execution:
- scenario: payment-authorisation
throughput: 900 # target authorisations per second at peak
concurrency: 3000 # ceiling on in-flight virtual users
ramp-up: 5m
hold-for: 30m
scenarios:
payment-authorisation:
variables:
base_url: https://staging.example.com
requests:
- label: POST /payments/authorize
url: ${base_url}/payments/authorize
method: POST
headers:
Content-Type: application/json
body: '{"card_token": "${CARD_TOKEN}", "amount_cents": 4999}'
assert:
- subject: http-code
contains: ['200']
- label: POST /giftcards/redeem
url: ${base_url}/giftcards/redeem
method: POST
headers:
Content-Type: application/json
body: '{"gift_card_id": "gc-load-01", "amount_cents": 1000}'
- label: POST /loyalty/points
url: ${base_url}/loyalty/points
method: POST
headers:
Content-Type: application/json
body: '{"member_id": "m-load-01", "order_cents": 4999}'

Point this at your provider’s sandbox, and tell them you are testing. Send 900 authorisations per second at a live processor without warning and you may get your merchant account suspended in the worst possible week. Read third-party and payment dependencies before you run it, and note which of those calls could be asynchronous, cached or gracefully degraded instead of blocking a sale.


8. Amazon Ads: Black Friday weekend, 25–27 November 2022

The pattern: the site was fine. You just couldn’t see anything.

For roughly a day and a half over Black Friday weekend 2022, Amazon’s advertising reporting dashboard displayed around half of advertisers’ actual spend and sales. Ads kept serving and money kept moving, but the numbers were wrong. Brands managing eight-figure budgets on the highest-spend weekend of the year were, as one trade outlet put it, flying blind. Some throttled campaigns that were in fact performing, and others overspent against headroom that did not exist. Amazon described it as a reporting lag. Cause: never disclosed.

Nobody load tests their analytics pipeline. It is not on the revenue path, so it never makes the test plan, until the moment it is the only instrument you have during an incident and it gives you wrong numbers. Ingestion queues back up, aggregation windows slip, and a dashboard that is silently three hours stale looks identical to a dashboard that is fine.

Drive the write volume of a real peak, then query the read side while the writes are still landing and check the aggregate actually moves:

execution:
- scenario: reporting-under-load
concurrency: 4000
ramp-up: 5m
hold-for: 45m
scenarios:
reporting-under-load:
variables:
base_url: https://staging.example.com
requests:
# Fire the events a real peak would generate
- label: POST /events/order-placed
url: ${base_url}/events/order-placed
method: POST
headers:
Content-Type: application/json
Authorization: Bearer ${API_TOKEN}
body: '{"order_cents": 4999, "campaign_id": "cmp-load-01"}'
# Then ask the dashboard what it thinks just happened
- label: GET /reports/revenue
url: ${base_url}/reports/revenue?window=5m&campaign_id=cmp-load-01
headers:
Authorization: Bearer ${API_TOKEN}
assert:
- subject: http-code
contains: ['200']

Then do the part that catches an Amazon-Ads-shaped failure: compare the revenue your dashboard reports against the count of POST /events/order-placed requests in MaxoPerf’s own run artifacts. The load generator knows exactly how many events it sent. If your reporting says half that, you have reproduced the 2022 outage in staging, in September, for free. Set a failure criterion on GET /reports/revenue p95 as well. A reporting endpoint that degrades from 200 ms to 30 seconds under write load is useless in a war room.


9. Cloudflare: 18 November 2025 (ten days before Black Friday)

The pattern: your dependency’s dependency.

This one is not a Black Friday outage, and that is why it is on the wall. On Tuesday 18 November 2025, ten days before Black Friday, Cloudflare’s core proxy failed globally for hours and took a large slice of the web down with it. It is the only entry on this list with a real postmortem. A database permissions change made the Bot Management feature file roughly double in size, which exceeded a preallocated limit and crashed the traffic-routing software. It was a configuration change, not an attack or a traffic surge. Cause: confirmed, with a published postmortem. We analysed it here.

Ten days later and it would have been a Black Friday story. You cannot load test someone else’s config deploy. You can test how your system behaves when the edge is gone. Check that your origin survives a cache-miss storm, that your fallbacks work, and that your status page is not hosted behind the thing that just died.

Run the same requests through the edge and directly against your origin, side by side, and compare:

execution:
- scenario: edge-vs-origin
concurrency: 1000
ramp-up: 3m
hold-for: 15m
scenarios:
edge-vs-origin:
requests:
- label: GET / (via CDN edge)
url: https://staging.example.com/
- label: GET / (origin, bypassing CDN)
url: https://origin-direct.staging.example.com/
headers:
Host: staging.example.com
- label: GET /product (origin, bypassing CDN)
url: https://origin-direct.staging.example.com/product/sku-001
headers:
Host: staging.example.com

One caveat on that Host header: it overrides routing at the HTTP layer, but over HTTPS the TLS SNI still comes from the URL’s hostname, so an origin that routes or validates certificates on SNI will not see a clean CDN bypass. For those, point the URL at the real origin hostname instead.

The origin labels tell you what a total CDN failure costs. If GET / (origin, bypassing CDN) collapses at 1,000 VUs while the edge label is comfortable at 40,000, your real capacity on the day your CDN has a bad afternoon is 1,000.

Then add locations in the Configuration tab (pick two or three regions and set weights summing to 100) so you also see per-region latency and routing anomalies. Locations are runner-level configuration in MaxoPerf, which is why nothing about them appears in the YAML above. The multi-region distributed load recipe walks through it.


The one that didn’t fail: Target, Cyber Monday 2015

On Monday 30 November 2015, Target.com showed shoppers a “please hold tight” banner and a virtual queue. Traffic was running at roughly twice the busiest day in the site’s history. Press coverage at the time filed it under “Target.com crashes.”

It didn’t. Target was metering traffic on purpose, and said so: the holding page was “a tactical way to help customers keep their place in their virtual line.” Cause: company-confirmed, and deliberate.

That is competence at the ceiling. Every system has a maximum. You either find it in advance and choose how to behave there, or you find out at 9:41 a.m. on Black Friday and let the load balancer decide. A queue that admits shoppers in order is a bad experience. A 503 that loses the cart is a lost customer. Best Buy in 2014 chose a third option and pulled the site down on purpose to recover it.

You can only choose if you already know your number. That is why you run the test in September.


What the wall teaches

  • Almost nobody publishes a postmortem. Of every retailer here, only Shopify named a subsystem. Plan around patterns, not causes, and distrust any article that confidently explains a mechanism the company never disclosed.
  • Repeat offenders are the tell. J.Crew, Lowe’s, Macy’s, Costco and Best Buy each failed on Black Friday in two separate years. Surviving last year tells you nothing about this year. The traffic grew and the code changed.
  • Reads scale, writes don’t. Login, cart, checkout, payment and order-confirmation are where every one of these failures lived. Test them at the same concurrency as the homepage, or you have tested nothing.
  • The ramp is the risk, not the plateau. Deals go live at a posted minute. Model the ninety seconds, not the twenty minutes.
  • Green dashboards are not correctness. Currys returned 200s while draining gift cards. Amazon’s reporting was live and wrong. Assert on state as well as status codes.
  • Your ceiling includes everyone else’s. Payment processors, identity providers, CDNs. Your capacity is the minimum across the chain.
  • Six to eight weeks, not two. Every fix on this list needed lead time: capacity provisioning, query optimisation, an idempotency key, a CI gate. None of them ship the week of.

Black Friday 2026 is on 27 November. Nothing about it is a surprise, which means every outage that happens on it will be a choice.

Pick a pattern from this wall that matches your architecture and run the test on MaxoPerf against your own environment. Then work the BFCM readiness checklist from T-6 weeks, and have a war room runbook ready for the day. The goal was never a perfect system. It was knowing exactly where yours breaks before twenty million shoppers find out for you.


Sources

Loss figures attributed to Love the Sales, Capacitas and Catchpoint are third-party estimates, not company-reported numbers. Downdetector counts are crowd-sourced report peaks, not audited metrics. Root causes are marked in the text as company-confirmed, reported, or never disclosed.

Questions this article answers

Why do you need to load test before Black Friday?

Black Friday is the one traffic peak whose date, rough magnitude, and traffic shape are all known months in advance. That makes it the most preventable category of outage, and the most embarrassing one to suffer. A load test run six to eight weeks out gives you time to find your capacity ceiling, fix the bottleneck, and re-test before shoppers find the ceiling for you.

Which big retailers have crashed on Black Friday?

Best Buy (2014 and again in 2025), Costco (Thanksgiving 2019 and Black Friday 2024), J.Crew (Cyber Monday 2017 and Black Friday 2018), Lowe's (2017 and 2018), Macy's (2016 and 2017), Walmart, Currys, Argos, Tesco Direct, Net-a-Porter, John Lewis, and Shopify on Cyber Monday 2025. HSBC UK's card payments failed on Black Friday 2023. Several of these failed two years running.

How far in advance should you run a Black Friday load test?

Six to eight weeks before the event. A test two weeks out tells you that you have a problem but leaves no time to fix it, re-test, and deploy safely. Re-run a confirmation test two weeks out once the fix has landed.

What actually causes Black Friday website crashes?

Very few retailers ever publish a technical postmortem, so the honest answer is that most causes were never disclosed. Where a cause was confirmed, it was usually a chokepoint rather than the site as a whole. Shopify confirmed a login authentication flow issue in 2025, Best Buy cited a concentrated spike in mobile traffic in 2014, and Macy's said its payment systems went over capacity in 2017.