All articles
Forward Deployed

Debugging in the Customer's Environment Without Direct Access: A Black-Box Playbook

FDE Coach EditorialJuly 29, 202610 min read

The Locked-Room Scenario

It’s 2:14 AM. A P0 incident just lit up your phone. The customer’s procurement workflow is down. They are losing six figures an hour. You are the Forward Deployed Engineer on point.

You open your laptop. You try to SSH into the bastion host.

Connection refused.

You ask the customer’s IT lead for temporary access to the Kubernetes cluster.

“No. We are air-gapped. SOC2 Type II. We can’t open a port. Send us the instructions.”

This is not a hypothetical. In defense, finance, and healthcare, you will never get a shell. You cannot kubectl exec. You cannot attach a remote debugger. You are debugging a black box through a letterbox slot.

This playbook is your survival kit. It covers the instrumentation, proxy patterns, and binary-search deployment strategies that let you fix critical bugs when you are completely blind.

Instrumentation as Your Eyes and Ears

If you cannot see the system, the system must describe itself. This is not a “nice-to-have.” It is a hard requirement for any on-prem or air-gapped deployment. During the sales-to-engineering handoff (covered in our post on How FDEs Work with Product and Engineering After the Sale to Prevent Churn), you must insist on a telemetry sidecar.

The Telemetry Sidecar Pattern

Do not rely on the customer to tail -f a log file and copy-paste the output. Instead, ship a lightweight agent—often a static Go binary—that runs alongside your application. It collects structured logs, metrics, and error traces, then pushes them to a customer-controlled relay.

The sidecar never phones home directly. It pushes to a relay bucket (S3-compatible, MinIO) that the customer owns. The customer then grants you read-only access to that specific bucket. This satisfies air-gap requirements because the data flow is outbound-only and customer-auditable.

What to Instrument

  • Error rates by endpoint: 5xx counts per route.
  • Latency histograms: p50, p95, p99 for external API calls.
  • Circuit breaker state: Open, half-open, closed.
  • Memory/heap profiles: Triggered automatically when heap usage exceeds 85%.
  • Deadlock detection: A goroutine/thread dump on SIGQUIT, written to the sidecar.

One FDE at a fintech unicorn reduced mean-time-to-resolution (MTTR) from 4 hours to 22 minutes purely by adding a heap profile trigger to their Java agent. The trigger fired during a production memory leak, and the .hprof file was waiting in the bucket before the customer even noticed the slowdown.

The Proxy Recording Man-in-the-Middle

Sometimes logs are not enough. You need to see the actual HTTP requests between services. But you cannot run Wireshark on the customer’s network.

The solution: a recording proxy deployed as a sidecar or init container that captures traffic non-intrusively.

Implementation with GoProxy

You ship a tiny goproxy binary configured in RECORD mode. It sits between the application and the downstream dependency (e.g., a legacy mainframe API). It logs every request and response—headers, body, latency—to a rotating file.

// Simplified FDE-built recording proxy handler
func (p *Recorder) handleRequest(req *http.Request, ctx *goproxy.ProxyCtx) {
    record := &Transaction{
        Timestamp: time.Now().UTC(),
        Method:    req.Method,
        URL:       req.URL.String(),
        Headers:   req.Header,
    }
    if req.Body != nil {
        bodyBytes, _ := io.ReadAll(req.Body)
        req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
        record.Body = bodyBytes
    }
    p.storage.Write(record)
}

The Privacy Filter

Customers will reject this unless you scrub PII. Build a configurable redaction layer that hashes or redacts fields matching patterns (credit card regex, SSN regex, custom JSON paths). The customer reviews the redaction config. Once they sign off, the recording proxy is safe to run in production.

Real-World Win

An FDE at a defense logistics company used this pattern to debug a 7-second latency spike that only happened with real production data. The logs showed the request, but the response was missing. The recording proxy revealed that the legacy SOAP service was returning a malformed XML envelope that the Java XML parser was silently swallowing for 7 seconds before throwing a timeout. Without the raw response body, the bug was invisible.

Scientific Method: Binary-Search Deploys

When you cannot attach a debugger, you debug by bisecting the codebase in time. This requires the customer to deploy versions you provide, so you must make it painless.

The One-Click Rollback

Before you start debugging, ensure the customer can roll back to the last known good version with a single command. If rollback is manual and takes 20 minutes, you cannot bisect efficiently. You often build this during the initial deployment—a Helm chart with helm rollback or a Docker Compose file with tagged images.

The Debug Build Matrix

You ship the customer a series of Docker images, each with a specific debug flag or log level enabled. You do not ask them to edit config files. You give them exact docker run commands.

Build TagChangePurpose
v1.4.2-debug-sqlSQL query logging enabledIs the ORM generating a bad join?
v1.4.2-debug-cacheRedis command loggingIs the cache returning stale data?
v1.4.2-debug-tlsTLS handshake verboseIs the certificate chain broken?

You walk the customer through deploying each build, running the failing workflow, and sending you the log output from the sidecar bucket. This is slow—each cycle might take 15 minutes—but it is methodical and proves exactly which component is failing.

Feature Flags as a Remote Scalpel

If you have a feature flag system (LaunchDarkly, or a custom on-prem equivalent), you can bisect without redeploying. You toggle specific code paths off while the customer watches the behavior. One FDE I know keeps a “debug mode” flag that, when enabled, dumps the full execution context—stack trace, local variables, request payload—to the sidecar for a specific user ID. This gives you a remote breakpoint without a debugger.

Structured Logging and the Correlation ID

In a black-box debug, the most common failure is inability to trace a request across services. You see an error in the API gateway log, but you cannot find the corresponding log in the downstream service.

The fix is a correlation ID propagated through every HTTP header, gRPC metadata, and message queue envelope.

The Header Contract

Every service must read X-Correlation-ID from incoming requests and propagate it to outgoing calls. If it is missing, the edge service generates a UUIDv4.

# Middleware in every service
@app.middleware("http")
async def add_correlation_id(request: Request, call_next):
    corr_id = request.headers.get("X-Correlation-ID", str(uuid4()))
    # Attach to structured logger context
    structlog.contextvars.bind_contextvars(correlation_id=corr_id)
    response = await call_next(request)
    response.headers["X-Correlation-ID"] = corr_id
    return response

Now, when the customer sends you a chunk of logs, you can grep for a specific correlation ID and see the entire request lifecycle. This is table stakes for any FDE working on distributed systems, and it ties directly into the highest-leverage skills for an FDE in the AI era—systems thinking and observability design.

The Art of the Diagnostic Snapshot

When a bug is intermittent and occurs under specific production data conditions, you need a diagnostic snapshot: a point-in-time capture of the application state when an error condition is met.

Triggered Heap Dumps

Configure the JVM or Go runtime to write a heap dump to a mounted volume when OutOfMemoryError is thrown. No customer action required.

# JVM flag in the deployment
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/diagnostics/heap.hprof

Conditional State Serialization

For application-level bugs, you write a small hook: if a specific exception type is thrown, serialize the relevant domain objects to JSON and write them to the diagnostics volume. This is like console.log on steroids, but it is structured and automatic.

One FDE built a DiagnosticSnapshot annotation for their Java services. When a method annotated with @SnapshotOn(DataIntegrityException.class) threw that exception, the framework serialized the method arguments, the current database transaction state, and the relevant cache entries to a timestamped file. The customer’s ops team just had to zip up the snapshots/ directory and upload it to the bucket.

Career Comp and the FDE Debugging Premium

This skillset—debugging blind under customer pressure—commands a premium. FDEs who can resolve air-gapped P0s without access are viewed as insurance policies by the business.

Comp data from the field (2024-2025, US-based, enterprise SaaS):

LevelBase SalaryEquity/YearTypical Debugging Expectation
FDE I (1-3 yrs)$130k–$160k$20k–$40kDebugs with access, follows runbooks
FDE II (3-5 yrs)$160k–$200k$40k–$80kDebugs black-box with sidecars, writes runbooks
Senior FDE (5+ yrs)$200k–$250k$80k–$150kDesigns telemetry architecture, leads war rooms blind
Staff FDE$250k+$150k+Sets cross-company air-gap debugging standards

The jump from FDE I to FDE II often hinges on one skill: solving a critical incident without asking for access. When you can tell a hiring manager, “I debugged a $2M/day revenue outage at a Tier 1 bank without ever seeing their screen,” you have negotiating power.

To build this muscle before you need it, practice on your own projects. Spin up a Docker Compose stack. Inject a bug. Restrict yourself to reading logs from a volume mount. No docker exec. No debugger. Time yourself. This is the kind of deliberate practice we emphasize in FDE coaching because it replicates the stress and constraints of the field.

FAQ

How to debug in a production environment? Start with structured logging and correlation IDs to isolate the failing request. Use a telemetry sidecar to ship logs, metrics, and heap profiles to an accessible bucket. If you cannot attach a debugger, use feature flags or conditional snapshots to capture state at the point of failure. Always ensure a one-click rollback is in place before any diagnostic deployment.

How to restrict debug access in SAP? SAP systems restrict debug access through authorization objects like S_DEVELOP with activity DEBUG. To limit debugging in production, replace the standard debugger with a read-only variant or use the ABAP Debugger with ACT_RW set to DISPLAY. Many customers also implement a “firefighter” privileged access management (PAM) system that grants temporary debug access with full audit logging.

How do you handle debugging? In an FDE context, debugging follows a scientific method: observe the symptoms via telemetry, form a hypothesis, and test it with the smallest possible change (a debug build, a feature flag toggle, or a targeted log line). Never make multiple changes at once. Always compare against the last known good state.

What does running without debugging do? In IDEs like Visual Studio or IntelliJ, “Run without debugging” starts the application without attaching the debugger process. This means breakpoints are ignored, and you cannot step through code. It runs at full speed, which is closer to production behavior. In an air-gapped production context, you are always “running without debugging” in the traditional sense—you must rely on passive instrumentation instead of interactive breakpoints.


Want to build the diagnostic intuition to handle these high-stakes scenarios? Our coaching focuses on real-world simulations—air-gapped debugging, telemetry design, and customer communication under pressure. Reach out if you are ready to level up.

#debugging#air-gapped-networks#log-analysis#proxy-requests

Want to build like a Forward Deployed Engineer?

FDE Coach is a cohort-based program in frontend, backend, AWS, and AI. Build real products and get referred to 200+ hiring partners.

Explore the program

More forward deployed

August 15 · 0d left
Enroll Now