When an application works through a proxy and something goes wrong, the first thing an engineer opens is the client logs. There it says something like connection reset by peer, read timeout, or just EOF. The problem is that these lines say almost nothing about the cause. Who reset the connection: the proxy, the target server, or the carrier between you and the proxy? Did the request even reach the proxy? Did the TLS handshake complete? The HTTP client library doesn't know, so you don't either.

In this article, we'll go down a level, to packets. We'll cover how to capture a tcpdump on the client machine, what exactly is visible in that dump when working through an HTTP proxy and SOCKS5, why in an HTTPS tunnel you'll only see CONNECT and encrypted TLS records, how to read a dump in Wireshark, and how to use RST, FIN, retransmissions, and zero window flags to determine whose side dropped the connection. We'll also discuss decrypting your own traffic via SSLKEYLOGFILE and how to properly prepare a dump for contacting proxy service support, such as Proxeon.

Important caveat: this is not an article about mitmproxy. That's about intercepting and substituting HTTPS at the application level with a fake certificate. Here we work at the packet level, we don't substitute anything or decrypt others' traffic. Our task is purely diagnostic: to understand where the chain client - proxy - target server breaks.

Introduction: When Client Logs Are No Longer Enough

Imagine a typical situation. A Python script goes through a mobile proxy, processes a couple thousand requests per hour, and about two percent of them end with the error Connection aborted, RemoteDisconnected. The developer adds retries, the error doesn't go away. He writes to proxy support, they ask for an example request and time. Support looks at their logs and replies: everything is fine on our end, the connection to the target server was established. Who's right?

Without a dump, this argument is endless. With a dump, it's over in five minutes: you can see that the proxy replied to CONNECT with code 200, the client sent ClientHello, and 180 milliseconds later an RST arrived from the proxy. This means either the proxy couldn't negotiate with the target server, or the server itself closed the connection. Then you can look at timings and clarify. The main thing is that the conversation moved from guesswork to facts.

HTTP client logs operate at the application level. They see the result, but not the process. A traffic dump shows the process: every packet with a precise timestamp, direction, flags, and size. That's why in serious proxy infrastructure operation, the ability to capture and read a dump is considered a basic skill, not an exotic one.

What You'll Get From This Article

  • An understanding of what data physically passes through the client's network interface when working through a proxy and what can be seen in plaintext.
  • Ready-to-use tcpdump commands for recording dumps with filters, rotation, and size limits.
  • A set of Wireshark display filters that can be copied in full.
  • A methodology for distinguishing between a drop on your side, the proxy side, and the target server side.
  • A safe way to decrypt your own TLS traffic for debugging.
  • A checklist for preparing a dump to contact support.

Basics: What's Visible Before the Proxy, Inside the Tunnel, and After It

Let's start with the diagram. When working through a proxy, there are three segments of the path, and on the client machine you can physically observe only the first one.

Three Observation Zones

  1. Zone A: client - proxy. This is the only TCP connection that passes through your network interface. tcpdump on your machine sees it. Here you can see: the TCP handshake with the proxy's IP address, the proxy protocol (HTTP CONNECT or SOCKS5), and then either plain HTTP or encrypted TLS records.
  2. Zone B: inside the tunnel. After the proxy replies with 200 Connection established, all bytes between you and the target server are simply passed through. If the target server uses HTTPS, these bytes are TLS records. You see their structure (record type, length, ClientHello with SNI, ServerHello, alerts), but not the content.
  3. Zone C: proxy - target server. This is a separate TCP connection that the proxy establishes on its own behalf from its external address. It doesn't exist on the client machine at all. You can only see it on the proxy server itself, which is provider infrastructure. Everything you learn about Zone C, you learn indirectly: from response codes to CONNECT, from delays, and from how the proxy closes the tunnel.

This is the key idea of the entire article. A dump on the client doesn't show the target server directly. But it shows the proxy's behavior, and the proxy, like a good relay, translates the target server's behavior into a form that can be interpreted. Our task is to learn to read that translation.

HTTP Proxy and Plain HTTP

The most transparent case. If the target site uses http without encryption, the client sends the proxy a request with an absolute URI:

GET http://example.com/api/items HTTP/1.1
Host: example.com
Proxy-Authorization: Basic dXNlcjpwYXNz
User-Agent: my-client/1.0

In the dump, you see everything: method, path, headers, body, the proxy's response with the body from the server. Note the Proxy-Authorization header. This is your credentials in base64, they lie in the dump in plaintext. Let's remember this fact; it will come in handy when we talk about sharing the dump with third parties.

HTTP Proxy and HTTPS via CONNECT

Here's where it gets interesting. For HTTPS, the client first asks the proxy to establish a TCP tunnel to the host and port:

CONNECT api.example.com:443 HTTP/1.1
Host: api.example.com:443
Proxy-Authorization: Basic dXNlcjpwYXNz
Proxy-Connection: Keep-Alive

The proxy replies:

HTTP/1.1 200 Connection established

From this moment on, the proxy stops parsing HTTP. It simply copies bytes from one socket to another. The client begins the TLS handshake directly with the target server, and all HTTP traffic (GET, POST, headers, bodies) ends up inside TLS. That's why in the dump of an HTTPS tunnel, you only see CONNECT: that's the last thing the client says to the proxy in plaintext. After that come TLS records, which the proxy can't read, and neither can you in the dump.

What remains visible inside the tunnel:

  • ClientHello: TLS versions, cipher suites, extensions, and usually SNI (hostname in plaintext).
  • ServerHello: chosen version and cipher.
  • In TLS 1.2 - the server certificate in plaintext. In TLS 1.3, the certificate is already encrypted.
  • TLS Alert: the alert type is visible in TLS 1.2; in TLS 1.3, alerts after the handshake are encrypted, but the fact of an Alert record with a length of 2 bytes is recognized.
  • Sizes and timings of Application Data records. From these, you can estimate how much data arrived before the connection dropped.

SOCKS5

SOCKS5 works differently, but the idea is the same. The client sends a greeting with authentication methods (bytes 05 02 00 02), the proxy selects a method, then authentication with username and password (subprotocol with bytes 01, length, username, length, password), then the CONNECT command with address type 03 (domain name) and port. The proxy replies 05 00 on success or an error code: 01 general error, 03 network unreachable, 04 host unreachable, 05 connection refused, 06 TTL expired. These error codes are a direct translation of what the proxy saw in Zone C. Wireshark can decode SOCKS if you specify the port via Decode As.

What You'll Never See

From the client machine, you won't see: the proxy's external IP from which it connects to the target server (for mobile proxies, this is the carrier's address), the proxy-to-server TCP connection, the proxy's DNS queries. If Proxeon support says they see an error on the target server side, they're looking at Zone C, which is inaccessible to you. Your task is to bring a Zone A dump so that the two pictures can be compared by time.

Deep Dive: Why the Proxy Can't Show More and How to Read Indirect Signs

It's worth understanding why the picture is this way and what follows for diagnostics.

CONNECT Tunnel as a Byte Pipe

After the 200 response, an HTTP proxy, by specification, must pass bytes in both directions without interpretation until either side closes. It doesn't know what's inside TLS. It doesn't know what HTTP requests you're sending. It sees only two events: a stream of bytes and socket closure. When the target server closes the connection with the proxy (sends FIN or RST), the proxy must close the connection with you. How exactly it does this depends on the implementation: some proxies translate FIN as FIN and RST as RST, others turn everything into FIN, and still others send RST to the client on a read error from the remote socket.

Practical consequence: an RST from the proxy's IP address does not mean the proxy is at fault. It means the tunnel was closed from the proxy side, and the cause could be anywhere behind it. To understand the cause, look at the context: what happened before the RST, how much time passed, did a response arrive.

The Difference Between SYN to the Proxy and SYN to the Target Server

This is the most common source of confusion. In a dump through a proxy, you will never see a SYN to port 443 of the target server. All SYNs go to the proxy's IP and port. If the SYN to the proxy doesn't get a SYN-ACK and repeats at intervals of 1, 2, 4, 8 seconds, the problem is between you and the proxy: network, firewall, wrong address or port, proxy not running. The target server has nothing to do with it; you haven't even tried to reach it in terms of your dump.

But if TCP with the proxy is established, CONNECT is sent, and a response arrives after 20-30 seconds with code 504 Gateway Timeout or 502 Bad Gateway, the proxy couldn't establish Zone C. The timing speaks for itself: your CONNECT packet left instantly, there was no response for a long time, so the proxy was waiting for a timeout in its attempt.

TLS 1.3, ECH, and What's Changing by 2026

The landscape is slowly closing. TLS 1.3 dominates, and in it, the server certificate is encrypted, so you can no longer check which certificate the server returned from a dump without keys. The ECH (Encrypted Client Hello) extension is gradually being rolled out by browsers and major CDNs, and there SNI also becomes encrypted. For diagnostics through a proxy, this is less critical because the hostname is still visible in the CONNECT line, but the usual SNI filter will trigger less often.

Another trend is HTTP/3 over QUIC. A classic HTTP proxy with CONNECT only tunnels TCP. If in the dump you suddenly see UDP traffic to port 443 directly to the target server's address, bypassing the proxy, that's a sign of a leak: the application is trying to use QUIC directly instead of through the proxy. A properly configured client should either disable QUIC when working through a proxy or use UDP proxying through separate mechanisms. A quick check filter: udp.port == 443. If a dump through a proxy shows such packets, the client configuration needs fixing.

Where to Place the Capture Point

The usual answer is one - on the client machine. But there are nuances. If the client runs in a Docker container, tcpdump on the host on the docker0 interface or on the bridge interface will show traffic before NAT, while on the external interface it will show traffic after NAT, with a different source address. It's easier to enter the container's network namespace: nsenter -t PID -n tcpdump ... or run the container with tcpdump via --net=container:name. On cloud VMs, pay attention to offloading: packets in the dump may look larger than MTU because the NIC coalesces segments. This is normal and doesn't affect flag analysis.

tcpdump in Practice: Filters, Writing to File, Rotation

tcpdump is available on almost any Linux machine and on macOS. On Windows, the equivalent role is played by Wireshark with the Npcap driver or the console dumpcap. Let's go over commands that cover 95 percent of proxy diagnostics tasks.

Basic Capture of Traffic to the Proxy

Suppose the proxy address is 203.0.113.10, port 8080. We record everything that goes between us and the proxy to a file:

sudo tcpdump -i any -nn -s 0 -w proxy.pcap 'host 203.0.113.10 and port 8080'

Explanation of the keys:

  • -i any - listen on all interfaces. Convenient when you don't know which interface the traffic goes through. If you know, it's better to specify a concrete one: -i eth0 or -i wlan0. On macOS, -i any is not supported; specify en0.
  • -nn - don't resolve IPs to names and ports to service names. Resolution slows down capture and adds extra DNS queries to the network.
  • -s 0 - capture the entire packet. In modern versions, this is the default, but explicit is better.
  • -w proxy.pcap - write to a file in pcap format instead of printing to screen. Only this way can the dump later be opened in Wireshark.
  • Filter in single quotes - this is a BPF capture filter. It cuts off everything unnecessary in the kernel, so the file will contain only what's needed.

Filters by Host and Port

Multiple proxies or a pool of ports:

sudo tcpdump -i eth0 -nn -w proxy.pcap 'host 203.0.113.10 and portrange 10000-10100'

Proxy by domain name (tcpdump will resolve it once at startup, which is not always suitable for rotating addresses):

sudo tcpdump -i eth0 -nn -w proxy.pcap 'host proxy.example.net and port 8080'

Capture only service packets without data, to look at handshakes and drops with large traffic volumes:

sudo tcpdump -i eth0 -nn -w flags.pcap 'host 203.0.113.10 and tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0'

Only RST in both directions:

sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-rst != 0'

Capture excluding your own SSH session, so as not to clutter the dump when capturing on a remote server:

sudo tcpdump -i eth0 -nn -w proxy.pcap 'host 203.0.113.10 and not port 22'

How Not to Collect Gigabytes: Rotation by Size and Time

If the error is rare and reproduces once an hour, the dump needs to run for a long time. Without rotation, the disk will fill up. Rotation by size, 100 MB per file, ring of 10 files:

sudo tcpdump -i eth0 -nn -w proxy-%Y%m%d-%H%M%S.pcap -C 100 -W 10 'host 203.0.113.10 and port 8080'

Here -C sets the size in millions of bytes, -W limits the number of files: the eleventh file will overwrite the first. Rotation by time, a new file every 10 minutes, keep the last 24 files (4 hours):

sudo tcpdump -i eth0 -nn -w proxy-%Y%m%d-%H%M%S.pcap -G 600 -W 24 'host 203.0.113.10 and port 8080'

The -G key specifies the interval in seconds, and the time template in the filename is mandatory, otherwise tcpdump will overwrite a single file. A useful key -Z user drops privileges after opening the interface, and -U forces packets to be written to the file immediately, without buffering, which is important if you will read the file in parallel or are afraid to lose the tail on abnormal termination.

Trimming the Payload

Another way to reduce volume is to capture only headers. For diagnosing drops, the content of TLS records is not needed, the first 128 bytes of each packet are enough:

sudo tcpdump -i eth0 -nn -s 128 -w headers.pcap 'host 203.0.113.10'

Careful: with -s 128, the CONNECT line and headers may be truncated, and Wireshark will mark packets as truncated. For a full TLS handshake analysis, this is not enough; ClientHello often takes 300-600 bytes or more. A compromise is -s 600.

Reading the Dump Directly in the Console

Wireshark isn't always at hand, but you need a quick look. Reading a file with TCP flags and absolute sequence numbers:

tcpdump -nn -r proxy.pcap -S -tttt 'tcp[tcpflags] & (tcp-rst|tcp-fin) != 0'

The -tttt key prints the full date and time, -S shows absolute sequence numbers, which helps to correlate with logs. Printing packet contents in ASCII to see the CONNECT line and the proxy response:

tcpdump -nn -r proxy.pcap -A 'port 8080' | grep -E 'CONNECT|HTTP/1'

tshark as a Console Wireshark

If Wireshark is installed on the server, tshark gives access to the same dissectors from the console. List all CONNECTs with response codes:

tshark -r proxy.pcap -Y 'http.request.method == "CONNECT" or http.response.code' -T fields -e frame.time -e tcp.stream -e http.request.uri -e http.response.code

List streams that ended with RST, indicating who sent it:

tshark -r proxy.pcap -Y 'tcp.flags.reset == 1' -T fields -e frame.time_relative -e tcp.stream -e ip.src -e ip.dst

Wireshark: Display Filters, Follow TCP Stream, Reading TLS Without Decryption

You opened the pcap in Wireshark and saw thousands of lines. Where to start? With display filters. Unlike BPF capture filters, they work on the already recorded file and understand protocol structure.

Basic Filters for Working Through a Proxy

Everything related to the proxy:

ip.addr == 203.0.113.10 && tcp.port == 8080

All CONNECT requests:

http.request.method == "CONNECT"

CONNECT to a specific host:

http.request.method == "CONNECT" && http.host contains "api.example.com"

Proxy responses other than 200 (authorization errors 407, unavailability 502, timeouts 504):

http.response.code >= 400 && tcp.port == 8080

Only 407 responses, a sign of incorrect credentials or exhausted limit:

http.response.code == 407

TLS Filters Inside the Tunnel

Wireshark can recognize that after CONNECT with a 200 response, TLS begins, and it automatically applies the TLS dissector. If it doesn't recognize (happens with truncated packets), right-click on the packet, select Decode As, and specify TLS for that port.

All ClientHellos:

tls.handshake.type == 1

ClientHello with a specific SNI:

tls.handshake.extensions_server_name contains "example.com"

ServerHello (if it's missing after ClientHello - the handshake didn't start from the server side):

tls.handshake.type == 2

TLS alerts:

tls.alert_message

Finding a stream with a ClientHello but no ServerHello is harder with a single filter; it's more convenient via Statistics > Conversations, sort by packet count and look at streams with 5-7 packets.

TCP Problem Filters

All packets with RST:

tcp.flags.reset == 1

RSTs sent by the proxy to us:

tcp.flags.reset == 1 && ip.src == 203.0.113.10

RSTs sent by us:

tcp.flags.reset == 1 && ip.dst == 203.0.113.10

Retransmissions:

tcp.analysis.retransmission

Zero window and its consequences:

tcp.analysis.zero_window || tcp.analysis.window_full

All anomalies Wireshark noticed itself (retransmissions, duplicate ACKs, lost segments, zero window):

tcp.analysis.flags && !tcp.analysis.window_update

SYN without a response, i.e., repeated SYNs:

tcp.flags.syn == 1 && tcp.flags.ack == 0 && tcp.analysis.retransmission

Large pauses within a stream, more than 5 seconds between packets:

tcp.time_delta > 5

For this filter, you need to enable Edit > Preferences > Protocols > TCP > Calculate conversation timestamps.

Follow TCP Stream

You found a suspicious packet, like an RST. Right-click > Follow > TCP Stream. Wireshark will show the entire dialogue of this connection as text: our traffic in one color, the proxy's traffic in another. For HTTPS via CONNECT, you'll see the CONNECT line, the 200 Connection established response, and then unreadable TLS bytes. This is normal. Look at the volumes: how many bytes went from us (our ClientHello and requests), how many came (ServerHello and response), and where it all ended.

At the bottom of the Follow window there are counters: so many bytes client, so many server. If 0 bytes came from the server after the 200 response, the target server didn't respond to ClientHello at all. If about 100-4000 bytes came and then stopped, the handshake started but didn't complete. If tens of kilobytes came and then a drop, the problem is in the middle of data transfer.

A useful habit: filter by stream number. After Follow, the filter line will automatically show tcp.stream eq 42. Close the Follow window, and the main list will show only this stream with flags and timings. This is the most convenient view for analyzing a drop.

Reading the TLS Handshake Without Decryption

Expand ClientHello in the packet tree. What to look at:

  • Version and supported_versions - does the client offer TLS 1.3. If the server requires 1.3 and the client offers only 1.2, the server will close the connection with a protocol_version alert or just FIN.
  • server_name - does the SNI match the host in CONNECT. A mismatch happens with incorrect client configuration and leads to certificate errors.
  • Cipher Suites - list of ciphers. Too short or outdated list is a cause of handshake_failure alert.
  • ALPN - does the client offer h2. If the server chose h2 and the client inside expected HTTP/1.1, the application may crash with unclear errors.

In ServerHello, look at the chosen version and cipher. In TLS 1.2, the Certificate follows in plaintext, you can check the name and validity period. In TLS 1.3, after ServerHello almost everything is encrypted, and the next thing you can read is the record type. Application Data means the handshake completed successfully and data started flowing. An Alert of length 2 bytes immediately after ServerHello or instead of it means something is wrong: in TLS 1.3, you won't see the alert code without keys, but the fact itself is already informative.

In Statistics > Conversations > TCP, it's convenient to assess the overall picture: how many streams, how many bytes in each direction, duration. Streams with a duration of 0.05 seconds and 6 packets are most likely connections reset immediately after the handshake.

Diagnostics from the Dump: RST, FIN, Retransmission, Zero Window

Now the main part. How to determine from a Zone A dump exactly where it broke? Let's go through each signal and its interpretation in the context of a proxy.

Direction Matrix

The first question is always the same: who sent the closing packet? In the dump, this is the ip.src field. There are only two options - our address or the proxy's address.

  • RST or FIN from us - the connection was closed by our application, our OS, or something on our host. The proxy and target server weren't involved. Typical causes: timeout in the HTTP client, abnormal process termination, file descriptor exhaustion, local firewall or antivirus.
  • RST or FIN from the proxy - the tunnel was closed from the proxy side. The cause is either the proxy itself (limits, policy, idle timeout), or translated from the target server, or from the network between the proxy and the server. We distinguish by context and timings.
  • Neither RST nor FIN, only retransmissions - packets are being lost on the path between us and the proxy. Neither side closed the connection, it just died from losses.

RST After CONNECT Without a 200 Response

The picture: TCP established, we sent CONNECT, the proxy responded with RST without an HTTP response. This behavior usually means the proxy refused at the policy level or couldn't parse the request. Causes: disallowed destination port, limit on simultaneous connections, incorrect request format. Here the problem is in Zone A or in the proxy itself. The target server is not involved.

Response 502, 503, or 504 to CONNECT

The picture: CONNECT left, after N seconds an HTTP response with code 5xx arrived. Look at N. If the response arrived in about the RTT to the proxy, the proxy refused immediately - perhaps the DNS name doesn't resolve on its side or the address is instantly unreachable (ICMP unreachable). If N is about 10-30 seconds, the proxy was waiting for a timeout when connecting to the target server: the server isn't responding to SYN. In both cases, this is Zone C, and your dump proves you did everything right, and the proxy honestly reported the impossibility.

FIN or RST Immediately After ClientHello

The picture: CONNECT - 200 - our ClientHello - after one RTT to the proxy plus something, FIN or RST arrives from the proxy. Here there are two candidates: the target server rejected the connection at the TLS stage (didn't like the SNI, version, missing client certificate), or the server's security system reset the connection based on ClientHello characteristics. The key sign is time. If between our ClientHello and the drop more time passed than the RTT to the proxy, then the proxy managed to send ClientHello further and receive a reaction. This is not the proxy deciding to close; this is a translated reaction from the target server.

Compare with the RTT to the proxy, which you measure by the TCP handshake: the time between SYN and SYN-ACK. If the RTT to the proxy is 40 ms and the drop after ClientHello came after 200 ms, the difference of 160 ms is approximately the proxy-to-server round trip. The picture is fully consistent with server rejection.

Drop After ServerHello or After Some Data

The handshake started, the server responded, data started flowing, and in the middle FIN or RST. If it's a FIN and from the proxy, and before it the last Application Data record has a logical size, perhaps the server simply closed the connection after the response (Connection: close inside TLS), and the client misinterpreted it. If it's an RST in the middle of a data stream without a preceding slowdown, it's either a forced close on the server or the proxy interrupted the tunnel due to a traffic limit or session lifetime. For mobile proxies with time-based IP rotation, the latter is very likely: the drop occurs exactly at the moment of IP change. Check if the drop time coincides with the rotation interval in your proxy settings.

Retransmissions and Their Direction

Wireshark marks a packet as retransmission when it sees a repeat of the same sequence numbers. Look at who is retransmitting:

  • We are retransmitting - our packets aren't being acknowledged by the proxy. Either they're lost on the way to the proxy, or acknowledgments are lost on the return path. In both cases, the problem is in the Zone A network.
  • The proxy is retransmitting - its packets aren't being acknowledged by us. We don't receive them, or our ACKs don't get through. Also Zone A, but leaning toward the incoming direction.
  • SYN retransmissions - a separate case: the proxy is unreachable at the TCP level, the connection isn't being established at all.

Important: losses in Zone C won't show up as retransmissions. The proxy handles the target server itself. The only trace of losses in Zone C is pauses: the proxy sends us data unevenly, with gaps, although there are no retransmissions in the dump. The filter tcp.time_delta > 1 for packets from the proxy will show such pauses.

Zero Window

A zero-size TCP window means the receiver can't keep up with reading data from the socket buffer. If we announce a zero window, our application isn't reading the response fast enough: busy thread, blocking, slow processing. The proxy waits, then sends a Zero Window Probe, and if the application still doesn't start reading, after a few tens of seconds it may close the connection. The client will see a drop and blame the proxy, although the cause is in the client.

If the proxy announces a zero window, it means it can't pass our data further - the target server is accepting slowly. This is an indirect sign of problems in Zone C, occurs when uploading large files through a proxy to a slow server.

Duplicate ACKs and SACK

Duplicate ACK from the proxy is a signal that it received a packet out of order, something from us is missing. Many duplicate ACKs and subsequent retransmissions are a classic picture of losses in a mobile or Wi-Fi network on our side. Duplicate ACKs from us - packets from the proxy are missing. The presence of the SACK option in the handshake allows TCP to recover more efficiently, but the fact of losses is still visible.

TTL Heuristic

An advanced technique. Look at the IP TTL field in normal packets from the proxy and in the RST packet. If the TTL in the RST differs by several units, the RST was generated not by the proxy itself but by an intermediate node on the path: a firewall, a load balancer, or a carrier filtering system. This is not absolute proof, but a strong hint that you should check the path between you and the proxy, not blame the proxy or target server.

Summary Table of Interpretations

  • SYN repeats, no SYN-ACK: proxy unreachable or network to it. Zone A.
  • SYN - RST: proxy port closed or filtered. Zone A.
  • CONNECT - 407: incorrect authorization or account limit. Proxy.
  • CONNECT - 502/504 quickly: proxy couldn't start connecting to the server. Zone C, likely DNS or route.
  • CONNECT - 504 after 20-30 seconds: server not responding to proxy's SYN. Zone C.
  • 200 - ClientHello - RST/FIN after time greater than RTT: server rejected TLS. Zone C.
  • 200 - ClientHello - silence - drop after client timeout: server accepts TCP but doesn't respond to TLS. Zone C or a blocking filter behind the proxy.
  • Data flowing - RST from proxy at a fixed moment: limit or proxy rotation. Proxy.
  • Retransmissions and duplicate ACKs without RST: losses in the network between us and the proxy. Zone A.
  • Zero Window from us: our application isn't reading. Client.
  • RST from us after a long silence: our timeout. Client.

Decrypting Your Own Traffic via SSLKEYLOGFILE

Sometimes flags aren't enough and you need to see exactly what the server returned inside TLS: response code, headers, error body. This doesn't require mitmproxy and a fake certificate. It's enough for your own client to write session keys to a file, and for Wireshark to use them for decryption. This works only for your client and your connections: the keys are only held by the side that participated in the handshake. It's impossible to decrypt someone else's traffic or the traffic the proxy handles with the server in Zone C in this way, and that's correct.

How to Enable in Different Clients

Chromium-based browsers and Firefox read the SSLKEYLOGFILE environment variable and write keys there in NSS Key Log format:

export SSLKEYLOGFILE=/home/user/tls-keys.log
google-chrome --proxy-server=http://203.0.113.10:8080

curl, built with OpenSSL, also reads this variable:

export SSLKEYLOGFILE=/home/user/tls-keys.log
curl -v -x http://user:pass@203.0.113.10:8080 https://api.example.com/health

Node.js with a command-line key:

node --tls-keylog=/home/user/tls-keys.log app.js

Python doesn't read the variable automatically, but starting from version 3.8 the SSLContext has a keylog_filename attribute. For requests via an adapter:

import os, ssl, requests
from requests.adapters import HTTPAdapter

class KeyLogAdapter(HTTPAdapter):
def init_poolmanager(self, *args, **kwargs):
ctx = ssl.create_default_context()
ctx.keylog_filename = os.environ.get("SSLKEYLOGFILE")
kwargs["ssl_context"] = ctx
return super().init_poolmanager(*args, **kwargs)

s = requests.Session()
s.mount("https://", KeyLogAdapter())
s.proxies = {"https": "http://user:pass@203.0.113.10:8080"}
r = s.get("https://api.example.com/health", timeout=30)
print(r.status_code)

Go via the KeyLogWriter field in tls.Config:

f, _ := os.OpenFile(os.Getenv("SSLKEYLOGFILE"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
tlsCfg := &tls.Config{KeyLogWriter: f}
tr := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: tlsCfg,
}

Connecting Keys in Wireshark

Two ways. First: Edit > Preferences > Protocols > TLS > (Pre)-Master-Secret log filename, specify the path to the key file. Wireshark will decrypt all streams for which it finds a match by Client Random. The second way is more reliable for sharing the file with colleagues: embed the keys directly into the pcapng:

editcap --inject-secrets tls,/home/user/tls-keys.log proxy.pcap proxy-with-keys.pcapng

After that, Wireshark will show decrypted HTTP/1.1 or HTTP/2 requests and responses inside the CONNECT tunnel. Filter for decrypted HTTP/2:

http2.header.name == ":status"

For decrypted HTTP/1.1 inside the tunnel, the usual http.response.code and http.request.uri work.

What This Gives for Proxy Diagnostics

Decryption removes the last uncertainty. You see that the server responded 429 with a Retry-After header and then closed the connection, or that it returned 200 but the body was cut off at 40 percent, or that the request went out but there was no response at all. With such a picture, contacting proxy support becomes specific: either the problem is clearly on the server, or clearly in the tunnel.

Security Rules

  • The key file allows decrypting recorded sessions completely, including cookies and tokens. Store it like a password and delete it after analysis.
  • Do not share the key file with the dump to proxy support or anyone else. Support doesn't need keys to analyze drops; flags and timings are enough.
  • If you still need to show decrypted content, do it with a test account on the target service and test proxy credentials.
  • Don't leave SSLKEYLOGFILE enabled in production. An environment variable accidentally placed in a service config will write keys to disk for years.

Typical Pictures: Connection Timeout, Drop in the Middle of a Response, Losses in a Mobile Network

Let's put the analyzed signs into recognizable scenarios. Each is described as it looks in the Wireshark packet list after the filter tcp.stream eq N.

Picture 1: Connection Timeout to the Proxy

0.000 client -> proxy [SYN]
1.002 client -> proxy [SYN] (TCP Retransmission)
3.006 client -> proxy [SYN] (TCP Retransmission)
7.010 client -> proxy [SYN] (TCP Retransmission)
15.02 client -> proxy [SYN] (TCP Retransmission)

Not a single packet from the proxy. Exponential intervals of 1, 2, 4, 8 seconds - the standard Linux kernel retransmission backoff. Diagnosis: proxy unreachable from your point. Check the address, port, firewall, routing, and whether the proxy address has changed. If this is a Proxeon mobile proxy with a dedicated port, make sure the port from your dashboard matches the one in the client config.

Picture 2: Proxy Timeout Connecting to the Target Server

0.000 client -> proxy [SYN]
0.041 proxy -> client [SYN, ACK]
0.041 client -> proxy [ACK]
0.042 client -> proxy CONNECT api.example.com:443 HTTP/1.1
0.083 proxy -> client [ACK]
30.10 proxy -> client HTTP/1.1 504 Gateway Timeout
30.10 proxy -> client [FIN, ACK]

RTT to the proxy 41 ms, the proxy acknowledged receiving CONNECT, then was silent for 30 seconds and returned 504. Diagnosis: the target server isn't responding to the proxy's connection attempt. Possible causes - server down, port closed for the proxy's address pool, network problems on the proxy-server route. Your client and network are not involved.

Picture 3: Server Rejects TLS

0.000 client -> proxy CONNECT api.example.com:443 HTTP/1.1
0.045 proxy -> client HTTP/1.1 200 Connection established
0.046 client -> proxy TLSv1.3 Client Hello (SNI=api.example.com)
0.210 proxy -> client [FIN, ACK]
0.210 client -> proxy [FIN, ACK]

Drop after 164 ms after ClientHello with RTT to the proxy 45 ms. The difference of about 120 ms is the time it took the proxy to forward ClientHello to the server and receive closure. Diagnosis: the server accepted TCP but closed the connection at the TLS stage. Check handshake parameters: versions, ciphers, SNI, ALPN. If ServerHello is missing and there's no alert, the server closed the connection silently, which security systems often do with an atypical ClientHello.

Picture 4: Drop in the Middle of a Response

0.000 client -> proxy CONNECT cdn.example.com:443 HTTP/1.1
0.044 proxy -> client HTTP/1.1 200 Connection established
0.045 client -> proxy Client Hello
0.190 proxy -> client Server Hello, Change Cipher Spec, Application Data
0.195 client -> proxy Application Data (request)
0.350 proxy -> client Application Data (1460 bytes)
... another 340 data packets
4.812 proxy -> client Application Data (1460 bytes)
4.813 proxy -> client [RST, ACK]

The handshake completed, about 500 KB of data arrived, and then RST without slowdown, without retransmissions, without zero window. Look at the absolute time. If it coincides with the moment of IP rotation on the mobile proxy or the expiration of the session duration limit, the cause is in the proxy, and the solution is to align the rotation interval with the duration of your requests or use a mode without rotation during long downloads. If there's no coincidence, a drop on the server or CDN side is likely. This is where decryption via SSLKEYLOGFILE helps: if inside you see a Content-Length header and the body didn't arrive completely, the server interrupted the transfer.

Picture 5: Losses in the Mobile Network on the Client Side

0.000 client -> proxy Application Data (1460 bytes)
0.001 client -> proxy Application Data (1460 bytes)
0.002 client -> proxy Application Data (1460 bytes)
0.180 proxy -> client [ACK] (Dup ACK 1)
0.181 proxy -> client [ACK] (Dup ACK 2)
0.182 proxy -> client [ACK] (Dup ACK 3)
0.183 client -> proxy Application Data (TCP Fast Retransmission)
0.420 client -> proxy Application Data (TCP Retransmission)
0.900 client -> proxy Application Data (TCP Retransmission)

Duplicate ACKs from the proxy, followed by retransmissions from us with growing intervals. Neither RST nor FIN. Diagnosis: losses on the client-proxy path. If the client itself is connected via a cellular network or Wi-Fi, this is expected during handovers between base stations. The solution is to increase client timeouts, enable TCP keepalive, and avoid long idle connections, because carrier NATs drop records of idle connections, usually after 30-300 seconds, and the next packet goes into the void. Filter for assessing the scale of losses:

tcp.analysis.retransmission && ip.dst == 203.0.113.10

Calculate the percentage of such packets from the total via Statistics > Capture File Properties. One to two percent is tolerable for a mobile network; more than five - look for the problem in radio conditions or equipment.

Picture 6: Our Own Timeout

0.000 client -> proxy CONNECT api.example.com:443 HTTP/1.1
0.044 proxy -> client HTTP/1.1 200 Connection established
0.045 client -> proxy Client Hello
0.200 proxy -> client Server Hello ... Application Data
0.205 client -> proxy Application Data (request)
0.250 proxy -> client [ACK]
10.21 client -> proxy [FIN, ACK]
10.25 proxy -> client [FIN, ACK]

The request went out, the proxy acknowledged, the response didn't come, and exactly 10 seconds later we sent FIN. This is our read timeout. The error in the client log will look like a drop, but the dump shows: we closed the connection ourselves because the server took longer than we were willing to wait. The solution is either to increase the timeout or figure out why the server is slow, but the proxy here was just honestly waiting with us.

Common Mistakes When Capturing and Reading Dumps

Let's list what even experienced engineers regularly stumble on.

  • Capturing a dump without a capture filter on a loaded machine. A gigabyte accumulates in a minute, and finding the needed 200 packets in it is inconvenient. Always filter by the proxy host.
  • Filtering by the target server's address. Through a proxy, there are no such packets on your machine. The filter will return nothing, and a person decides the traffic isn't flowing. It is, but to the proxy.
  • Confusing capture filter and display filter. The BPF syntax in tcpdump (host, port, tcp[tcpflags]) and the Wireshark syntax (ip.addr, tcp.port, tcp.flags.reset) are different. A Wireshark filter in tcpdump will cause a syntax error, and vice versa.
  • Looking at an RST from the proxy and immediately blaming the proxy. An RST from the proxy's address is a tunnel closure, not an admission of guilt. Look at the timings and what happened before the RST.
  • Ignoring RTT. Without a measured delay to the proxy, it's impossible to distinguish an instant proxy refusal from a translated server refusal.
  • Capturing with -s 64 and then trying to read CONNECT. Headers will be truncated. For proxy diagnostics, you need full capture or at least -s 600.
  • Not synchronizing time. If the clock on the dump machine is a minute behind, you won't be able to correlate the dump with support logs. Enable NTP and specify UTC in your request.
  • Sending a dump with proxy credentials. The Proxy-Authorization header in a plain HTTP request to the proxy contains the login and password. Either capture with test data or change the password after sending.
  • Sending the key file along with the dump. This reveals all session content. Support doesn't need keys to analyze drops.
  • Keeping one long dump without rotation. The disk will fill up at the worst moment, and tcpdump will crash along with the needed packets.
  • Not checking for QUIC leaks. UDP on 443 directly is a sign that some traffic goes past the proxy, and diagnostics via the TCP tunnel won't show anything.
  • Forgetting about segmentation offload. Packets of 60 KB in a dump on a virtual machine don't mean incorrect MTU. This is the kernel giving tcpdump not-yet-segmented segments.

Tools and Resources

The minimal set for the described methodology.

Capture

  • tcpdump - the standard on Linux and macOS. Installed almost everywhere, requires root or the CAP_NET_RAW capability.
  • dumpcap - the console capturer from the Wireshark suite, supports the same rotation keys (-b filesize, -b files) and pcapng format.
  • Wireshark with Npcap - for Windows. You can capture directly from the GUI with a capture filter in the same BPF syntax.
  • tshark - console analysis with Wireshark dissectors, convenient on servers without a GUI and for automation.

Analysis

  • Wireshark - the main tool. Follow TCP Stream, Expert Info, Statistics > Conversations, IO Graph for visualizing pauses and bursts of retransmissions.
  • editcap - slicing and filtering pcap, injecting TLS keys into pcapng.
  • mergecap - merging rotation files into one for analysis.
  • capinfos - quick summary of a file: duration, packet count, size.

Clients with Debug Support

  • curl with -v and --trace-time keys duplicates the dump picture at the application level and supports SSLKEYLOGFILE.
  • openssl s_client -proxy host:port allows manually performing CONNECT and the TLS handshake through a proxy and seeing the server response without an HTTP client.

Useful One-Liners

Merge rotation files:

mergecap -w all.pcapng proxy-*.pcap

Extract only one stream from the dump to send to support:

tshark -r all.pcapng -Y 'tcp.stream eq 42' -w stream42.pcapng

Quick statistics on streams with RST:

tshark -r all.pcapng -q -z conv,tcp | head -40

Manual CONNECT check via openssl:

openssl s_client -proxy 203.0.113.10:8080 -connect api.example.com:443 -servername api.example.com -brief

Cases and Results

Case 1: Two Percent Drops, the Client Was at Fault

A price collection service worked through a pool of mobile proxies, about 40 thousand requests per day. About 2.3 percent of requests failed with RemoteDisconnected. The team was sure the proxies were at fault. They captured a dump with 100 MB rotation for a day, filtered tcp.flags.reset == 1 and looked at ip.src. In 91 percent of cases, the RST was sent by the client itself. Stream analysis showed: before the RST there was exactly 5 seconds of silence after sending the request, then RST from the client. The code had a read timeout of 5 seconds, and the target server responded in 6-8 seconds during peak hours. Increasing the timeout to 15 seconds reduced errors to 0.3 percent. The remaining 0.3 percent were real FINs from the proxy 160-200 ms after ClientHello: the server periodically rejected connections from part of the address pool. These data were passed to support, where the picture was confirmed by their Zone C logs.

Case 2: Large Download Drops Exactly Every 10 Minutes

A client downloaded 300-800 MB archives through a mobile proxy and complained about drops in the middle. The dump showed RST from the proxy at moments exactly 600 seconds apart, to the second, regardless of when the download started. The cause turned out to be scheduled IP rotation every 10 minutes: when the address changes, the proxy closes active tunnels. The solution was to switch the port to rotation on request and trigger the address change between downloads. The drops disappeared completely, taking two hours to diagnose instead of weeks of correspondence.

Case 3: The Server Is Down, But It Seems the Proxy Is at Fault

In the morning, all requests to one API started returning connection errors. Client logs: Connection aborted. The first reaction - the proxy is down. A one-minute dump showed: TCP with the proxy is established in 38 ms, CONNECT goes out, and after 30 seconds a 504 and FIN arrive. At the same time, a request to another host through the same proxy went through in 300 ms. Diagnosis: the target server isn't accepting connections from the proxy. Forty minutes later, the target service's status page confirmed an incident on their side. The dump saved the morning and avoided a false ticket to proxy support.

Case 4: Wi-Fi Losses Looked Like a Proxy Problem

A developer tested an integration from a laptop through a Proxeon mobile proxy and got sporadic timeouts. The dump showed retransmissions from the client at a rate of 6 percent and duplicate ACKs from the proxy, with not a single RST from the proxy. Connecting the laptop via cable reduced retransmissions to zero, and the timeouts disappeared. The problem was in the office's overloaded access point.

Checklist for Capturing a Dump to Contact Support

If you're going to send a dump to proxy service support, here's the procedure that will save time for both parties.

Preparation

  1. Synchronize the clock on the machine via NTP. Check with the timedatectl or date -u command.
  2. If possible, create separate test proxy credentials for the diagnostic period or schedule a password change after sending the dump.
  3. Record the client version, HTTP library, OS, and internet connection method (cable, Wi-Fi, cellular).
  4. Write down the proxy address and port, the target host, expected and actual behavior.

Capture

  1. Start tcpdump with a filter by the proxy host and port, with rotation, full packet size:
sudo tcpdump -i eth0 -nn -s 0 -U -w proxy-%Y%m%d-%H%M%S.pcap -G 300 -W 48 'host 203.0.113.10 and port 8080'
  1. Reproduce the problem. If it reproduces rarely, leave the capture for the needed time; 48 files of 5 minutes will cover 4 hours.
  2. Simultaneously with the capture, make a control request via curl with -v and --trace-time, save the output. It will give an exact time reference to the packets.
  3. Record the exact time (UTC) of each error occurrence from the client logs.
  4. Stop tcpdump with Ctrl+C. Make sure the files aren't empty: capinfos proxy-*.pcap.

Processing

  1. Merge the files: mergecap -w all.pcapng proxy-*.pcap.
  2. Find the problematic streams with the filter tcp.flags.reset == 1 or by time from the logs.
  3. Extract only the needed streams: tshark -r all.pcapng -Y 'tcp.stream in {12 47 93}' -w report.pcapng. Support doesn't need hours of normal traffic.
  4. Check that the extracted dump contains nothing extra: other connections, traffic from other services.
  5. Do not attach the TLS key file.

Request Text

  • Time of occurrence in UTC to the second.
  • Proxy address and port, target host.
  • Brief interpretation: what you see in the dump (for example, 200 on CONNECT, then FIN from the proxy 170 ms after ClientHello, RTT to the proxy 45 ms).
  • Frame or stream numbers in the attached file.
  • curl -v output with timestamps.
  • What you already checked and ruled out: another host through the same proxy, the same host through a different connection.

Support handles such a request in one go. They just need to correlate your timestamps with Zone C logs and confirm or refute the version.

FAQ

Why isn't the target server's IP address in the dump, I'm connecting to it?

Because you're not connecting to it directly, but through a proxy. Your client establishes a TCP connection with the proxy's address and asks it to connect to the target host. The proxy-server connection exists only on the proxy side. In the dump, the target hostname is visible in the CONNECT line and in the SNI inside ClientHello, but there are no packets to its IP from your machine and there can't be.

Can you tell from the dump which external IP the proxy used to connect to the server?

No. This information belongs to Zone C. The only way is to request a service that returns the client address through the proxy, or look in the provider's dashboard to see which address was active at that moment.

The proxy sent an RST. Does that mean the problem is in the proxy?

Not necessarily. An RST from the proxy's address means the tunnel was closed from the proxy side, and the cause may be in the target server or the network behind the proxy. Look at the timing: if the RST came after a time noticeably exceeding the RTT to the proxy after your last packet, most likely the proxy translated the server's reaction. If the RST came at a fixed moment in time or after an exact interval, the proxy's own policy, such as rotation or a limit, is likely.

How to measure RTT to the proxy from the dump?

The time difference between the SYN from you and the SYN-ACK from the proxy at the start of any connection. In Wireshark, you can enable Statistics > TCP Stream Graphs > Round Trip Time for a stream or use the tcp.analysis.ack_rtt field as a column.

Wireshark doesn't show TLS inside the tunnel, only TCP data. What to do?

Right-click on the packet after the 200 Connection established response, select Decode As, and in the Current column select TLS for the proxy's TCP port. Also make sure the packets aren't truncated (-s 0 during capture) and that the HTTP dissector is configured for the proxy port if it's non-standard: Edit > Preferences > Protocols > HTTP > TCP ports.

How is this approach different from mitmproxy?

mitmproxy works at the application level: it terminates TLS with a fake certificate, reads, and can modify HTTP requests. For this, the client must trust its root certificate. tcpdump and Wireshark work at the packet level and don't substitute anything: you see real packets, real flags and timings, including TCP errors that mitmproxy would hide because it would handle them itself. For diagnosing drops, the packet level is more honest. For viewing request content, mitmproxy is easier, but if desired, the content of your own traffic can also be seen in Wireshark via SSLKEYLOGFILE without certificate substitution.

Can you decrypt in Wireshark the traffic the proxy handles with the server?

No. You have neither the packets of that connection nor the keys for it. SSLKEYLOGFILE gives keys only for your client's sessions, and inside the CONNECT tunnel this is exactly your session with the server, so it can be decrypted. But there is no separate TLS between the proxy and the server in the case of CONNECT: the proxy just passes your bytes through.

How to tell that the client is leaking past the proxy?

Capture a dump without a filter by the proxy, but with a filter by your entire interface, and look at outgoing connections to ports 80 and 443 to addresses other than the proxy, as well as UDP 443 (QUIC) and DNS queries to external resolvers with target hostnames. Wireshark filter: (tcp.port == 443 || udp.port == 443) && !(ip.addr == 203.0.113.10). Any matches are a configuration leak.

How big should the dump be for support?

The smaller, the better. One extracted problematic stream takes from 5 to 500 KB. Support will analyze a file larger than 50 MB longer, and half of that volume will be normal traffic. Extract the needed streams via tshark or File > Export Specified Packets in Wireshark.

What to do if tcpdump on a virtual machine shows packets larger than MTU?

This is generic segmentation offload: the kernel passes large segments to tcpdump before the NIC segments them. This doesn't affect the analysis of flags, timings, and drops. If it bothers you, disable offload during capture with the command ethtool -K eth0 gso off tso off gro off, but remember that this will reduce network performance.

Conclusion

A traffic dump when working through a proxy is not about peeking at content and not about magic. It's about an honest record of what actually happened on the wire: to the millisecond and to every flag. Client logs say the connection dropped. The dump says who did it, exactly when, and what happened before.

Let's summarize the methodology. On the client machine, you only see the connection to the proxy: TCP handshake, CONNECT or SOCKS dialogue, and encrypted TLS records inside the tunnel. The target server is not directly visible, but its behavior is translated by the proxy through response codes to CONNECT, timings, and the way the tunnel is closed. RST or FIN from your address - your problem or your timeout. Retransmissions and duplicate ACKs without closure - losses on the path to the proxy. Closure from the proxy after a time multiple of the RTT to it - a translated server reaction. Closure at fixed moments - proxy policy. Zero Window from you - your application isn't reading.

Practical steps for today: bookmark the tcpdump commands with rotation and Wireshark filters from this article; check whether the clocks on the machines where your clients run are synchronized; make sure your client doesn't leak QUIC past the proxy; set up test credentials for diagnostics so you don't expose production ones in dumps. And next time the logs say connection reset by peer, don't guess. Capture a dump, open the stream, look at the direction and time. In five minutes you'll know who to write to: yourself, proxy support, or the target server owner.