How to record and replay HTTP traffic.

•

•

Record the Call Once, Replay It Forever

I once spent an entire weekend trying to replicate a race condition in our staging environment, only to realize that the “synthetic” load tests we were running were nothing like the chaotic, malformed garbage hitting our actual production endpoints. We had all these expensive observability suites promising insights, but none of them actually showed me how to record and replay http traffic in a way that captured the actual nuance of a failing request. It turns out, simulating a user is easy; simulating the specific, weirdly-timed sequence of headers that actually breaks your state machine is a much different beast.

I’m not going to sell you on some shiny, enterprise-grade service that requires a six-month implementation plan just to catch a few edge cases. Instead, I want to talk about the pragmatic, sometimes messy ways to actually capture real-world payloads and feed them back into your local environment. I’ll walk through what I’ve learned from the failed attempts and the production outages that taught me the hard way, focusing on tools and patterns that actually work when you’re staring at a broken dashboard at 2 AM.

Table of Contents

Capturing Network Packets Before the Evidence Vanishes

Capturing Network Packets Before the Evidence Vanishes

The problem with most debugging sessions is that by the time you’ve actually realized something is wrong, the specific sequence of events that caused the failure has already evaporated. You’re left staring at logs that tell you what happened, but they rarely tell you the how of the actual payload. When I was at the insurance firm, we once lost a whole afternoon because we couldn’t recreate a specific race condition caused by a weirdly formatted header. We weren’t just looking for logs; we needed capturing network packets in a way that actually preserved the state of the conversation between services.

If you want to move toward deterministic network testing, you have to stop relying on manual reproduction. I’ve found that setting up a proxy server recording layer is usually the most pragmatic way to go. Instead of trying to build complex mocks for every edge case, you just sit a transparent proxy in the middle of the traffic flow. It catches the raw, messy reality of the wire—the timeouts, the unexpected 503s, and the malformed JSON—before they get sanitized by your application’s error handling. It’s much easier to debug a recorded stream of reality than it is to guess what a service might have sent you three hours ago.

Why Proxy Server Recording Beats Guessing in the Dark

Why Proxy Server Recording Beats Guessing in the Dark

I’ve spent way too many hours trying to reproduce a race condition by manually tweaking JSON payloads in Postman, hoping I’d eventually hit the same edge case that crashed the service last Tuesday. It’s a guessing game, and a losing one at that. When you rely on manual reproduction, you aren’t actually testing the system; you’re just performing a high-stress version of charades. Using proxy server recording changes the math entirely because it moves you away from “I think the request looked like this” to “I know exactly what the bytes looked like.”

The real value shows up when you move from chaos to deterministic network testing. Instead of praying that your staging environment behaves like production, you can use the recorded traces to feed your local setup. This is where you stop treating the network as a black box and start treating it as a predictable input. If you can feed your service the exact same sequence of headers and payloads that caused the spike in latency, you aren’t just guessing anymore—you’re actually debugging. It turns a frantic search for a needle in a haystack into a controlled experiment where you can finally see the needle.

Five things I learned the hard way about traffic capture

  • Sanitize your payloads before they hit the disk. I once spent an entire afternoon trying to scrub PII from a recorded trace because I realized too late that our staging environment was accidentally replaying real customer credit card numbers. If you don’t automate the masking, you’re just building a security vulnerability by another name.
  • Don’t ignore the headers. It’s easy to focus on the JSON body, but I’ve seen entire services fail because a specific `X-Forwarded-For` or `Content-Type` header was missing in the replay. The body is the message, but the headers are the context that actually makes the request work.
  • Watch your clock skew. When you’re replaying traffic into a system that relies on timestamps for idempotency or TTLs, you’ll find that the replay fails immediately if you aren’t careful. You either need to strip those timestamps or ensure your replay tool can simulate a “current” time context.
  • Mind the side effects. Replaying traffic against a live database is a great way to delete your production data or double-charge a user. I’ve learned to always wrap my replay sessions in a way that either targets a dedicated sandbox or uses a mock layer that can handle the inevitable “write” operations.
  • Capture the errors, not just the 200 OKs. If you only record successful requests, you’re building a library of a perfect world that doesn’t exist. You need to capture the 503s and the timeouts too; otherwise, you’ll never be able to test how your system actually recovers when the network starts acting up.

What I’ve learned from the wreckage

Don’t rely on logs alone; they tell you what the application thought happened, but a recorded traffic trace tells you what actually happened on the wire.

If you can’t replay a specific production failure in your local environment, you aren’t actually debugging—you’re just guessing and hoping for the best.

The cost of setting up a proxy to capture traffic is high upfront, but it’s significantly lower than the cost of another 3 AM incident where the root cause is a single, unrepeatable malformed request.

The Hard Truth About Reproducing Reality

The Hard Truth About Reproducing Reality.

At the end of the day, you have to choose your poison. You can try to sniff packets at the network layer and deal with the headache of decrypting TLS, or you can bake recording into your proxy layer and accept the slight latency trade-off. I’ve learned the hard way that guessing what a client sent is a losing game. Whether you’re using a dedicated service mesh or just a well-configured Envoy instance, the goal is the same: stop relying on your memory of the incident and start relying on the actual bytes that hit your wire. If you can’t replay the exact sequence of headers and payloads that caused the crash, you haven’t actually solved the problem; you’ve just patched a symptom.

I don’t expect anyone to implement a perfect traffic mirroring setup overnight. It’s heavy, it’s complex, and it requires discipline to manage the storage costs of all those captures. But I do suggest you start small. Pick one critical service—the one that keeps you awake on a Saturday—and figure out how to capture its input. Because one of these days, the system is going to do something completely unexpected, and instead of staring at a blank terminal in a panic, you’ll have the replayable evidence you need to actually fix the root cause and finally go back to sleep.

Frequently Asked Questions

How do I handle sensitive stuff like auth tokens or PII so I'm not accidentally leaking customer data into my local dev environment?

The last thing I want is to be the reason a customer’s session token ends up in a plaintext log file on my laptop. If you’re replaying traffic, you need a scrubbing layer. I usually bake a transformation step into my proxy setup—something that identifies headers like `Authorization` or fields like `email` and replaces them with deterministic hashes or dummy values. It’s a bit more setup upfront, but it beats the alternative.

What happens when the API schema changes halfway through a recorded session—does the whole replay just fall apart?

Yeah, it falls apart. Hard. I’ve been there: you replay a month’s worth of traffic against a new deployment, only to watch the error rates spike because a field that used to be a string is now an object. If your recorded payloads don’t match the current schema, the parser chokes and the replay is useless. You can’t just record blindly; you need a way to version your traces or, better yet, a way to transform the old data on the fly.

Is there a way to simulate high-latency or flaky connections during the replay, or am I stuck testing against perfect local network conditions?

If you only test against a perfect local loopback, you’re basically lying to yourself. I’ve seen plenty of “passed” CI pipelines melt the moment they hit a real-world mobile network. You can use something like `tc` (traffic control) on Linux to inject artificial latency or packet loss, or just use a proxy like Toxiproxy. It’s a bit of a chore to set up, but it’s better than being surprised by a timeout in production.

How much overhead am I actually adding to the system if I decide to run a transparent proxy in a production-like staging environment?

It’s a valid concern. If you’re running a transparent proxy in a staging environment that actually mirrors production load, you’re looking at a latency hit—usually a few milliseconds per hop—and some CPU overhead for the packet inspection. It’s not zero. But in my experience, the “overhead” of a slightly slower staging environment is a bargain compared to the overhead of a production outage because you couldn’t reproduce the edge case. I’d rather take the latency hit now.

Tomasz Wierzbicki

About Tomasz Wierzbicki

I am not interested in arguing about frameworks. I am interested in why the thing broke at two in the morning and what would have prevented it. Most of what I know came from being wrong in production and writing it down afterwards.