← All posts Blog Guides

How proxy bandwidth is measured, and why upload counts too

Guides
How proxy bandwidth is measured, and why upload counts too

A customer once spent the better part of an hour with us on this, and he was not being difficult. He had a per-GB plan, his own logs said he had pulled about 4 GB, and our meter said closer to 5. He had checked another provider and got a similar answer there, which made him more suspicious rather than less. His position was reasonable: he asked for pages, pages came back, so surely the pages are what he pays for.

The answer is that every metered proxy network on the market counts traffic in both directions, and hardly any of them says so plainly. That silence is what makes it feel like a trick. It is not one, but you deserve the actual explanation rather than a line in a terms page.

What actually crosses the wire

When you picture a request you probably picture a URL going out and a page coming back. What really moves is a good deal more than that, and all of it is real traffic the network has to carry.

Going out, per request: the TLS handshake, which is a full negotiation before a single byte of your own data moves. Then your request line, then your headers — and headers are not small on a modern client. A realistic browser-like request carries a user agent, an accept chain, language preferences, encoding preferences, a referer, client hints, and a cookie jar that grows every time the site sets something. On a session that has been running a while, cookies alone can outweigh the request that carries them. Then, if you are posting anything — a form, a search body, a GraphQL query, an image upload — that payload goes out too.

Coming back: response headers, the body, and on a browser-driven session every subresource the page decides to fetch on its own.

A meter that counted only the response body would be describing something other than what the network did. So it counts what left and what returned, which is why a per-GB figure sits a little above the sum of the pages you think you asked for.

Upload is real traffic, and sometimes it is most of it

For plain page fetching, upload is the smaller half — typically a modest share of the total, and you will barely notice it. That is the case where the objection feels strongest, and it is also the case where the difference is too small to be worth an argument.

It stops being small the moment your workload changes shape. Posting data, submitting forms at volume, uploading media, or running an API integration that sends large JSON bodies can shift the balance until upload is the dominant direction. Sessions that accumulate large cookies and repeat thousands of requests do the same thing more quietly: a few extra kilobytes of headers per request is nothing once, and meaningful across a million requests.

The useful mental model is not “download is what I buy.” It is “bytes are what I buy, in whichever direction they move.”

Why your application’s own counter disagrees

This is the part that turns a small discrepancy into a dispute, because your numbers are not wrong. They are measuring something narrower.

Most HTTP libraries report the length of the decoded response body. That single choice excludes several things at once:

  • Headers, in both directions. Your client hands you the parsed body, not the envelope it arrived in.
  • The TLS handshake, which happens below the layer your library reports on and costs real bytes on every new connection.
  • Redirects. If a URL redirects twice before it lands, you see one final body and three round trips actually happened.
  • Retries. A request that timed out, got refused, or returned a challenge page still moved bytes. Many clients retry silently and report only the attempt that worked.
  • Everything a browser fetched on its own. If you are driving Playwright or Puppeteer and counting only the pages you navigated to, you are ignoring most of the requests in the session. We covered how badly that skews success rates in your scraper’s success rate is measuring the wrong thing, and the same gap distorts bandwidth.

None of that is your library misbehaving. It is reporting the thing it was designed to report. It simply is not the same quantity as “what the network carried”.

Compression is the other half of the gap

Nearly every site serves compressed responses. Your parser sees the decompressed HTML — say 400 KB of markup — while the wire carried perhaps 60 KB of it. Here the wire number is lower than the one you counted, and people tend to be pleased rather than annoyed.

The two effects run in opposite directions, which is why nobody can hand you a fixed ratio. On a text-heavy scrape with good compression, the meter can land below your own body count. On a session-heavy job with large cookies, many redirects and lots of retries, it lands above. Neither outcome is evidence of anything except that decoded body size and wire bytes are different measurements.

Failed requests still cost bytes

Worth stating on its own, because it surprises people more than compression does. A 403, a captcha page, a timeout after the handshake completed, a connection reset partway through — all of those moved data. The handshake happened. The headers went out. Something came back, even if it was useless to you.

If your job has a poor success rate, you are paying for the failures alongside the successes. That makes success rate a bandwidth question as well as a reliability one, and it is one of the better reasons to fix blocks properly rather than paper over them with retry loops. Getting past anti-bot defenses covers the fixes that hold, and why a proxy connection gets reset covers the failures that are not blocks at all.

The thirty-second test

You do not have to take any of this on trust. curl will report both directions itself. Swap in your own credentials, endpoint and port:

curl -x "http://user123:[email protected]:10000" \
  -s -o /dev/null \
  -w "request=%{size_request}B upload=%{size_upload}B resp_headers=%{size_header}B download=%{size_download}B\n" \
  https://example.com/

Run it against a real page you scrape rather than a test endpoint. Two things usually stand out. First, size_request is never zero and is rarely trivial — that is your headers, and they are carried on every single request you make. Second, size_download will not match what your parser reported, because of compression, redirects, or both.

Do it once with a fresh session and once with a session that has been running long enough to collect cookies, and you will watch the request size climb on its own.

What actually reduces the number

In rough order of how much they save:

  • Block images, fonts, media and stylesheets in headless browsers. This is usually the largest single cut available and it is a few lines of code. On many commercial pages the content you came for is a small fraction of the bytes.
  • Block third-party hosts entirely. Ad, tracking and telemetry endpoints add bytes and failures in equal measure.
  • Do not re-download unchanged pages. Send If-Modified-Since or If-None-Match and honour a 304, which costs headers only.
  • Keep connections and sessions alive rather than renegotiating TLS for every request. Fewer handshakes is fewer bytes, and holding a session has other benefits worth reading about in rotating or sticky sessions.
  • Prune your own request headers. A smaller cookie jar and a shorter header set on high-volume API work is free money at scale.
  • Fix blocks rather than retrying through them. Every retry is a full-price request.

Two things that will not help, in case you were about to try them: switching between SOCKS5 and HTTP makes no meaningful difference to volume, since the payload is the same either way (the real trade-offs are elsewhere), and narrowing your targeting does not reduce bytes — a request from a specific city carries exactly as much as one from anywhere in the country (targeting by country, city or ASN).

Which is why the plan shape matters

Once you accept that you are buying bytes in both directions, the billing model becomes an engineering decision rather than a pricing preference. If your workload is modest in volume but needs precise targeting — country, state, city or ASN — a metered plan like Platinum or Mobile is the right shape, and the byte accounting is worth optimising carefully because every kilobyte is on the invoice. If your workload is heavy and repetitive, the meter stops being the interesting variable and concurrency starts, which is exactly what a port-based plan like Budget Unlimited or a speed-tiered one like Premium Unlimited charges for instead. We worked the break-even through with real numbers in per-GB, per-port, or unlimited.

The short version: measure your own traffic in both directions before you argue with anyone’s meter, ours included. If your number and ours still disagree by more than the effects above can explain, send us the job details and we will go through it line by line. That is a support ticket, not a debate.

Endpoint and port details for every product are in the documentation.

Start routing today. Spin up in 90 seconds.

Create an account and ship your first ProxyOmega request before your coffee's cold.

ProxyOmega ProxyOmega

90M+ ethically-sourced IPs across 200+ countries and 30,000+ cities. Residential, mobile, ISP and IPv6 proxies for scraping and AI agents.

GDPRCCPA
Product
Premium Unlimited Budget Unlimited Unlimited Residential Proxies Residential / ISP Mobile IPv6 Chrome Extension
Solutions
Web scraping AI agents Price monitoring SERP & SEO Integrations All use cases
Resources
Glossary Error codes Free tools Proxies by platform Locations
Company
About Blog Docs Reseller program Affiliate Contact Sign in
© 2026 ProxyOmega Ltd. All rights reserved.