Skip to main content
← All posts
6 min read

Your Staging Environment Is Lying to You

Staging exists to catch problems before production. Most staging environments catch the wrong problems and miss the real ones, because they differ from production in exactly the ways that matter.

Share

Every team has a staging environment. The deploy goes there first, someone clicks around, the smoke tests pass, and then it ships to production. Staging is the gate. It exists to catch problems before users do.

And then the incident happens anyway. The postmortem says "this didn't reproduce in staging." Everyone nods. Nobody asks the obvious follow-up: then what is staging for?

The uncomfortable answer is that most staging environments don't validate the things that break in production. They validate a fiction — a version of your system that's close enough to look right and different enough to be useless.

The four ways staging diverges

Staging fails to predict production because it differs along axes that determine whether software actually works.

Data. Production has 40 million rows; staging has 4,000. Production data is messy — null values in columns that "can't" be null, encoding artifacts from a migration three years ago, a user whose name is 600 characters long. Staging data is synthetic, clean, and recent. The query that's instant on 4,000 rows does a full table scan on 40 million. You will not see that in staging.

Scale and concurrency. Staging gets traffic from a handful of engineers clicking around. Production gets thousands of concurrent requests. Race conditions, connection-pool exhaustion, lock contention, and cache stampedes are all concurrency phenomena. They are structurally invisible in an environment with no concurrency.

Configuration. Staging has its own environment variables, its own secrets, its own feature-flag values, its own scaled-down instance sizes. Every one of those differences is a place where staging and production disagree. The config that works in staging and fails in production is one of the most common incident causes there is — and staging is constitutionally incapable of catching it, because the whole point is that the config is different.

Integrations. Staging talks to the staging version of every dependency, or to mocks, or to nothing. Production talks to real third-party APIs with real rate limits, real latency distributions, and real outages. The payment provider's sandbox always returns success in 50ms. The real one times out at 2pm on the busiest day of the quarter.

Notice what these have in common: they're the things that cause real incidents. Staging is excellent at catching a button that doesn't render. It is blind to the failures that actually page you.

The cost of a lying gate

A staging environment that misses real problems would be merely useless. The deeper problem is that it actively misleads.

Staging is a gate, and a gate produces a signal. "It passed staging" becomes a statement of confidence. Engineers merge on it, managers cite it, and the deploy proceeds with everyone slightly more relaxed than they should be. The signal is treated as meaningful because the environment exists and the check ran.

But the signal is mostly noise. It correlates weakly with whether the change is safe. You've built an elaborate apparatus whose primary output is unearned confidence — which is worse than no apparatus at all, because no apparatus at least keeps everyone appropriately nervous.

There's also a direct cost. Staging is a full second copy of your infrastructure. It has compute bills, a maintenance burden, and a standing claim on engineering attention. When staging breaks — and it breaks constantly, because nobody owns it the way they own production — someone spends a day fixing an environment whose only job is to predict production, and does it badly.

What staging is actually good at

This isn't an argument to delete staging. It's an argument to be honest about its job.

Staging genuinely catches: build and deployment failures, broken database migrations, obvious functional regressions, integration wiring mistakes ("the new service can't reach the old one"), and gross configuration errors like a missing environment variable. These are real classes of bug, and catching them before production is worth something.

What staging is good at is a pre-flight check: does this thing start, connect to its dependencies, and serve a request without falling over. That's a legitimate and valuable function. It is just a much smaller function than "validates that this change is safe for production," and the gap between those two framings is where teams get hurt.

So the first fix is linguistic. Stop saying "it passed staging" as if it means "it's safe." Say what it means: "it deploys and runs." Those are different claims.

Where the real validation has to happen

If staging can't validate the things that break — data scale, concurrency, real config, real integrations — then validation has to move to the only environment that has all four. Production.

This sounds reckless. It is the opposite of reckless. It's the recognition that production is the only place your system actually exists, so testing has to meet it there, carefully.

Progressive delivery. Don't flip a change from 0% to 100% of traffic. Route 1% to the new version, watch the metrics that matter — error rate, latency percentiles, the specific business metric the change touches — and expand only if they hold. A canary on real traffic tells you in ten minutes what staging couldn't tell you in a week.

Feature flags decoupled from deploy. Ship the code dark, then turn the behavior on for internal users, then 5% of real users, then everyone. Each step is a real test against real data and real concurrency, with an instant rollback that doesn't require a redeploy.

Production-shaped pre-merge testing. The checks that run before merge should test against a realistic data shape. A copy of production data, anonymized, restored into the test database is far more honest than synthetic fixtures. The query plan on real data volume is the thing you actually need to know.

Observability good enough to make production safe to test in. This is the precondition for all of the above. If you can detect a regression in your canary within minutes and roll back in seconds, then testing in production is safer than testing in staging — because the feedback is real instead of fictional. If you can't detect or roll back quickly, fix that first. It's the highest-leverage investment on this list.

The honest version

Keep a pre-production environment if it earns its cost as a pre-flight check. Name it for what it does. Don't let "it passed staging" function as a synonym for "it's safe."

Then put your real validation effort where the real conditions are. The goal isn't to predict production from a model of it. The goal is to make production observable enough, and rollback fast enough, that you can validate changes against reality without betting the whole user base on each one.

A staging environment tries to answer "will this work?" by simulating the world. A good production rollout answers the same question by carefully sampling the world itself. Only one of those is telling you the truth.

Work with me

I consult with engineering teams on AI adoption, cloud architecture, and engineering effectiveness. If this post surfaced a challenge you're facing, let's talk.

Get in touch →

Explore more on these topics:

Subscribe to new posts

Get an email when I publish something new. No spam, unsubscribe any time.