Total hosting-provider outage and same-day production failover — on the biggest sale day of the year
TL;DR
On the final Sunday of our summer sale — with trading running above €40,000 per hour — our hosting provider, Shinjiru, went completely dark. Every server we had with them — production backend (Laravel API) and production frontend (Next.js storefront) — became unreachable, and the production database with them. Their support vanished along with their infrastructure: phones unanswered, their own website down, and their one remaining channel — a single Telegram handle — silent. Their outage ultimately lasted more than three days.
Ours lasted about four hours. Instead of waiting for a provider that had gone silent, I switched production over to our warm staging box at a second provider (DigitalOcean, Frankfurt), deliberately isolating the staging database so no production data could be corrupted, and we were taking orders again the same afternoon — 4,000+ orders were processed that day on the failover environment. The next day I made the emergency posture the permanent one.
There was no disaster-recovery plan before this incident. That gap was mine to own, and the honest account of what it cost is below.
Impact in numbers
- End-to-end order-intake downtime: ~4 hours — roughly half of it before we even knew. The outage began late morning; we learned of it around 13:00 Europe/Zurich, about two hours in, because our third-party healthchecks ran on the same provider and died with it.
- Stakes: final day of the summer sale, a Sunday — peak trading above €40K/hour when the provider went down.
- Orders processed on the failover environment that day: 4,000+.
- The provider's own outage: 3+ days. Our permanent migration off them: complete within 24 hours.
flowchart TB
U["Customers — final sale Sunday, €40K+/hour"] --> CF["Cloudflare LB"]
CF x--x SJ["Shinjiru production — backend + frontend DOWN (their outage: 3+ days)"]
CF ==> DO["DigitalOcean Frankfurt — warm staging box, resized to 8 vCPU / 32 GB before repoint"]
DO --> SDB[("Isolated staging DB — emergency orders born here, on purpose")]
SDB -. "next day: merge back with a dedicated order-ID mapping column" .-> PDB[("Production DB")]
style SJ fill:#fdecea,stroke:#c0392b,color:#7b241c
The failover shape: traffic repointed to a resized warm standby; emergency orders deliberately isolated, then merged back traceably.
Timeline (Europe/Zurich)
- ~11:00, Sunday — Shinjiru's infrastructure goes down: production backend and frontend unreachable, production database inaccessible. No alert reaches us — monitoring shared the provider's blast radius.
- ~13:00 — We learn of the outage, about two hours in. The provider is unreachable on every channel: no phone response, their own website down, a lone Telegram handle not answering. With no ETA and no human on the other end, I stop waiting and commit to failover.
- 13:00–15:00 — Failover build-out on the DigitalOcean Frankfurt staging box. First, capacity: I resized the backend droplet to 8 vCPU / 32 GB RAM / 640 GB disk and the Next.js frontend box to the same class, so the emergency environment had production-grade headroom before it saw a single customer — the sizing was arithmetic, not guesswork, because the platform's capacity envelope was already known from pre-launch load testing. Then config: copied the production application config across with targeted overrides — kept the database pointed at the staging copy on purpose, isolating emergency orders from production data; fixed a search-engine API-key mismatch; set the app environment to production so staging-only simulators wouldn't run against real traffic.
- Triage, in strict priority order — products and collections, payment-gateway settings, checkout: the minimum surface that takes money correctly. I explicitly skipped importing historical orders — hours of database loading that no customer would see mid-sale — and accepted the merge-back debt as a written decision instead.
- ~15:00 — Cloudflare repointed to the failover environment; orders flowing again.
- 16:30 — Health verified layer by layer: Redis, database connectivity, search health, live storefront GraphQL — all green. Declared stable and monitored through the remainder of the sale day.
- Same evening — Documented the reconciliation constraints: every order created during the failover lived in the isolated database and would need a merge back into production, with customer-ID mapping, payment-status checks, and duplicate-webhook handling written down as explicit open decisions rather than left implicit.
- Next day — Permanent migration: the Frankfurt box became the production origin. The Cloudflare load-balancer endpoint was repointed, the old provider's nodes were disabled (their config still referenced pre-outage backend state — re-enabling one would have served split data, a hazard I documented explicitly), and I added a proxied fallback DNS record so the API keeps resolving even if the load balancer itself is disabled.
Recovery outcome
The 4,000+ orders created in the isolated database were merged back into production as part of the permanent migration — executed by the developer I had onboarded and trained, under my review, and the implementation came back clean and documented. One detail I'm particularly glad we got right: because merged orders had to be re-sequenced into production's ID space, we added a dedicated mapping column linking each production order to the original order ID the customer had received by email from the failover environment — so every confirmation email in a customer's inbox remained resolvable to a real order. No production data was touched until every check on the decision list had an explicit answer.
What made it hard
- A provider that had simply vanished. No status page, no ETA, no human being. Every decision had to be made assuming they might never come back — which reframes a failover from "temporary patch" to "the new production," and is exactly why I made the migration permanent the next day rather than waiting.
- No retreat available. Falling back to the old Shopify store was not an option: it had been frozen since the replatform — stale prices, collections and templates — so "wait, or go backward" simply didn't exist. The only path was forward, on infrastructure we controlled.
- Why that provider at all. Hosting jurisdiction was a business requirement on the client's side that ruled out the mainstream US/EU clouds — a constraint the engineering had to respect rather than debate. It's also exactly why the warm second-provider staging environment mattered so much: when your primary host is unusual, your insurance has to be conventional.
- The staging database was a test subset. It had products, customers, and master data from a production snapshot — but not production orders. Running live checkout on it meant every new order was born in the wrong database, so the failover had to be paired with an explicit merge-back plan, not just "flip it on."
- Order-reconciliation risk. Orders written during the outage referenced staging customer IDs that might not match production; payment captures, platform sync, and webhook replay on recovery all created duplicate-ingestion risk. I chose to accept and document that debt rather than lose the biggest sale day of the year. (See "Recovery outcome" for how it resolved.)
- DNS and load-balancer topology. Production sat behind a Cloudflare load balancer whose endpoints pointed at the dead provider. Repointing had to account for health-monitor behavior (the monitor kept failing against the new box even while it served traffic correctly — Cloudflare fails open on a sole endpoint) and for what happens if the LB itself is ever disabled — which is why the fallback record exists.
- Two different operating systems. The old production box and the Frankfurt box ran different OS families with different PHP-FPM service names and web users, so every "standard" production command had to be re-verified rather than assumed.
What I did
Safety-first triage under a running sale clock: get revenue flowing on infrastructure I could reason about, isolate anything that could corrupt production data, verify each layer independently before declaring health, and write down every deferred risk as an explicit decision with an owner. Then, instead of waiting for a provider that had gone silent, I converted the emergency posture into the permanent one within 24 hours — a documented origin topology with a fallback DNS path, and written warnings on the sharp edges (notably: never re-enable the stale provider nodes as-is).
What went right / what was missing
Right: a warm, production-shaped staging environment at a second provider existed because I had invested in it — the single reason this took hours instead of days. The capacity envelope was known from pre-launch k6 load testing, so emergency resizing was arithmetic. Config overrides were surgical, and nothing in production data was touched blindly.
Missing — and this is the headline: there was no disaster-recovery plan, and no monitoring outside the provider's blast radius — our third-party healthchecks lived on the same infrastructure they were meant to watch, and died with it. That is why the outage ran two hours before a human told us. No tested restore, no second provider ready to take traffic, no runbook for "the provider is gone." In almost twenty years I had never seen a hosting provider vanish entirely — which is why it sat outside my threat model, and exactly why it no longer does: the improbable-but-total failure belongs in the plan precisely because experience keeps insisting it doesn't happen. The improvisation worked, but improvisation is not a strategy. The client gave notice on the engagement following the incident, and the missing plan B contributed to that decision; I completed a structured handover and trained my successor. That's a fair outcome to learn from, and I don't dress it up.
Lessons that now shape how I run production
- DR/BCP is a day-one deliverable, not a someday project. The seed of that practice exists from this incident — the failover runbook, the documented origin topology with its DNS fallback, and the written do-not-re-enable hazard notes all went into the handover package. Going forward, documented failover runbooks, tested restores, and a multi-provider posture sized to the business are week-one scope.
- A staging environment that mirrors production is your cheapest insurance — it's the only reason this failover took hours instead of days.
- Monitoring must live outside the stack it monitors. Ours didn't, and it cost us two of the four hours.
- Know your capacity envelope before you need it. Resizing the emergency box under a €40K/hour clock was calm arithmetic only because the load-test numbers already existed.
- When you defer risk under pressure, write it down. The order-merge constraints were documented the same evening as decisions with owners — not discovered later as surprises.
- Make the emergency shape the documented shape fast. A temporary topology nobody wrote down is the next outage.