Metrics & Testing

Proxy Success Rate Explained: What Counts as Good?

A provider quoting 99.9% and a scraper seeing 78% can both be telling the truth. The difference is the predicate, the denominator and the target, and all three are yours to fix.

Published Updated 11 min readBy The Proxies.click Benchmark Team

Key takeaways

  • Proxy success rate is the share of attempted requests that returned a usable response, which means connection errors belong in the denominator alongside HTTP failures.
  • The failure most measurements miss is the soft block: a challenge page or placeholder served with HTTP 200, which every naive success counter records as a win.
  • Advertised figures around 99.9% are measured against neutral endpoints that block nobody, so they describe gateway health rather than how your job will fare on a defended target.
  • Your effective cost per usable gigabyte is the list price divided by your success rate, so a cheaper pool that fails a fifth of its requests is not cheaper.
  • On an undefended target, expect the high nineties; on a site with active bot management, a per-attempt rate sustained between 70% and 90% is a working setup.

Success rate is the share of requests that produced the response you actually wanted. That is a stricter test than it sounds, and the gap between it and the number on a provider pricing page is where most bandwidth budgets quietly disappear.

A provider quoting 99.9% and a scraping team measuring 78% on the same pool can both be right, because they are asking different questions of different endpoints. Once you know which failures a measurement includes, the percentage starts to mean something.

A definition you can implement

Success rate needs a denominator and a predicate, and neither can change during an experiment. The denominator is every request your client attempted, including the ones that never left your machine because the gateway refused the connection. Dropping those is the most common way a measurement flatters a pool, and it is usually accidental: the library raises an exception, the handler logs it, and the counter never increments.

The predicate is a boolean over the entire response, covering status code, headers and body, that returns true only when the response is usable by whatever comes next in your pipeline. Anything short of that is measuring reachability.

You also have to decide whether retries collapse. A task that succeeded on its third attempt is one success and two failures if you count attempts, or one success if you count tasks. Both numbers are useful and they answer different questions: the per-attempt rate describes the pool, and the per-task rate describes your pipeline. Report both, and always say which one a headline figure refers to.

The failure classes and who owns them

A single success percentage is close to useless for debugging. A breakdown by failure class tells you whether to change provider or change your own client.

FailureWhat you observeUsually caused by
Connection refused or resetA TCP error before any HTTP exchangeGateway saturation, a wrong port, or the exit device dropping off mid-handshake
TimeoutNo response inside your budgetA congested household uplink, a dead exit, or the target deliberately stalling you
TLS handshake failureHandshake alerts or certificate verification errorsInterception on the exit path, an unsupported cipher suite, or a wrong SNI through the tunnel
407 Proxy Authentication RequiredA 407 with a Proxy-Authenticate header from the gatewayMalformed username parameters, an exhausted plan, or an IP allowlist mismatch
403 ForbiddenA 403 body served by the targetExit IP reputation, a browser fingerprint that does not match your headers, or a geo restriction
429 Too Many RequestsA 429, often with Retry-AfterToo many requests from one exit inside the rate window, which means insufficient rotation
5xx from the target502 or 503 responsesThe target under genuine load, or its edge shedding traffic it considers suspicious
Empty or truncated bodyA 200 with zero bytes, or a body shorter than Content-LengthThe exit device dropping the connection mid-transfer
Soft blockA 200 carrying a challenge page, a login wall or an empty result setBot management deciding not to tell you that it caught you
Failure classes, the signal each one produces, and where the fault usually lies.

The 407 row is worth internalising. A 407 comes from the proxy and means your credentials or parameters were rejected before anything was forwarded; a 403 comes from the target. Teams that conflate the two spend days blaming a pool for a typo in a session parameter, and rotating vs sticky proxy sessions covers the username syntax that generates most of them.

The soft block that returns HTTP 200

Modern bot management prefers not to announce itself. A 403 tells an attacker exactly what happened and when to change tactics, so the more effective response is a 200 with something useless in the body: a JavaScript challenge, an interstitial, a login wall, an empty result set, or a page rendered from stale cache with the wrong prices. As far as HTTP is concerned nothing went wrong, and every status-code-based counter marks it as a win.

This is why teams discover their scraper has been storing garbage for three weeks while the dashboard said 99%. Catching soft blocks means asserting on content, and four assertions catch most of it.

  • A positive invariant. Something that only appears in a real response: a CSS selector, a JSON key, a currency symbol next to a number. Assert on its presence rather than on the absence of a block message, because block pages change wording and real pages do not lose their schema.
  • A byte-size floor. Challenge pages are small and remarkably uniform. If a product page is normally 80 KB and you receive 3 KB with a 200, treat it as a failure regardless of what the body says.
  • Vendor markers. Some systems label their own interventions, for example a cf-mitigated response header, or a script path belonging to a challenge platform. Cheap to check and unambiguous when present.
  • Collapsing size variance. Real pages vary in length; challenge pages do not. A sudden drop in the standard deviation of response size across a batch is an early warning that a target has switched you onto a static block page.
CHALLENGE_MARKERS = (
    b'/cdn-cgi/challenge-platform',
    b'g-recaptcha',
    b'Please enable JavaScript',
)

def is_success(resp) -> bool:
    if resp.status_code != 200:
        return False
    if resp.headers.get('cf-mitigated'):
        return False
    body = resp.content
    if len(body) < 2048:                       # challenge pages are tiny
        return False
    if any(m in body for m in CHALLENGE_MARKERS):
        return False
    return b'"offers"' in body                 # invariant only a real page carries

Why advertised success rates sit so high

A figure like 99.9% is normally measured against a neutral endpoint: an echo service or the provider's own probe host, something that blocks nobody. That measurement answers a real question, which is whether the gateway is up and whether exits can complete a request end to end. It answers nothing about the site you care about, because target behaviour is excluded by construction.

Generous timeouts and gateway-side retries push it higher still. If the gateway silently tries a second exit when the first one fails, one logical success can hide several exit failures, and the number you are shown is closer to a service-availability figure than a pool-quality one.

Our own benchmark does the same thing deliberately. Comparing providers requires holding the target constant, so we test against stable Cloudflare-hosted endpoints from both US and EU infrastructure and publish the result per region as well as combined. That isolates the pool from the target, which is the only way a cross-provider comparison means anything. The thresholds and weights are documented in our methodology, and the current standings are on the live benchmark table.

99%+
The band we treat as healthy on a neutral endpoint
25%
Weight success rate carries in our provider score
HTTP 200
The status code most soft blocks arrive with
price / rate
Effective cost per usable gigabyte

Measuring success rate on your own targets

A provider figure tells you the network is up. Your own figure tells you whether the job will finish. Producing one that survives scrutiny takes a fixed procedure.

  1. Pick three to five real URL templates per target, spanning a cheap page and an expensive one, and hold that set constant for the whole comparison.
  2. Freeze the client. Same headers, same TLS configuration, same concurrency, same timeouts across every provider. Change one of those and you are measuring your scraper rather than the pool.
  3. Interleave providers request by request instead of testing them in blocks, so that time-of-day effects and target-side incidents hit everyone equally.
  4. Run at least a few hundred attempts per provider per target, then repeat at a different hour. Residential pools shift with consumer traffic in the exit country.
  5. Record per attempt: status code, byte count, elapsed time, exit IP, predicate result and failure class. The exit IP is what distinguishes a bad pool from a bad slice of one.
  6. Compute per-attempt and per-task rates, then break both down by failure class before you draw any conclusion.

The breakdown is what makes the exercise actionable. A pool with a 6% timeout rate is a provider problem. A pool with a 6% rate of 403s on one target and none on another is a fingerprint problem you own, and switching provider will not fix it. Latency instrumentation belongs in the same harness; how to test proxy speed covers the timing side, and how to benchmark proxy providers covers running the whole comparison as a programme.

Success rate sets your real cost per gigabyte

Residential bandwidth is billed on bytes transferred, and failed requests transfer bytes. A challenge page, a redirect chain and a 403 body all move data you pay for. So the price on the pricing page is not the price you pay per unit of usable data. Divide it by your success rate.

At $4 per GB and a 100% success rate, a usable gigabyte costs $4. At 70% it costs about $5.70 before retries, and retries multiply: a task needing three attempts pays for three transfers. Two providers listed at $3.50 and $5.00 swap places the moment their success rates on your target differ by a fifth, which is why we publish the most reliable providers next to the cheapest per 100GB rather than ranking on either alone.

The same arithmetic makes bandwidth hygiene a reliability lever: blocking images and third-party requests reduces the bytes wasted per failure as well as per success. Residential proxy pricing explained covers how tiers are structured once you have an effective rate to compare.

What a good success rate looks like

There is no single good number, because the number is a property of the pairing between a pool and a target. There are defensible bands.

Neutral endpoints and undefended targets

Against an endpoint that blocks nobody, anything below 99% is a warning about the network itself: gateway capacity, exit churn, or your own timeouts being too tight. Against a real target with no bot management, such as a documented API, a small site or a public data portal, expect the high nineties once your client behaves. If you see 90% there, examine your connection handling and timeout budget before you blame the provider.

Targets behind active bot management

On a well-defended consumer site, a per-attempt rate sustained between 70% and 90% is a functioning setup, and anything above 95% usually means either an unusually good fingerprint stack or a target that has not yet noticed you. Judge on trend rather than on a single run. A rate that starts at 98% and decays over an hour is the signature of reputation burn across the exits you draw from, and an average hides it completely. That decay is what proxy fraud score explained helps you predict; challenge-driven failures are covered in captchas and proxies.

Measurement mistakes that inflate the number

  • Counting only requests that got an HTTP response. Connection errors, TLS failures and timeouts belong in the denominator. Excluding them can add ten points to a bad pool.
  • Letting the client retry internally. Library-level retries turn several failures into one success and make the pool look healthier than it is. Disable them and retry explicitly at a layer you can count.
  • Comparing providers measured at different hours or at different concurrency levels. Both effects are larger than the differences you are trying to detect.
  • Using a single URL. One cached edge response ends up speaking for an entire target, and caching behaviour differs by path.
  • Treating any 200 as success. The expensive one, and the reason to test your predicate against a known block.
  • Averaging across countries. One geography carrying all the failures disappears into a global mean, so break the rate down by exit country as well as by target.

We run this measurement continuously rather than as a one-off, because a pool that was excellent last month can be mediocre today as its exits churn. The rolling history is on the downloadable performance graphs, and the endpoints and plans we test are listed on each provider profile, for example Oxylabs.

Frequently asked questions

What is a good proxy success rate?

Against a neutral endpoint, healthy pools sit above 99% and anything lower points at the network itself. Against a real target with no bot management, expect the high nineties. Against a well-defended consumer site, a per-attempt rate holding between 70% and 90% is a working setup. Any figure quoted without naming the target and the failure predicate is not comparable.

Why is my proxy success rate lower than the provider advertises?

Because the advertised figure is measured against an endpoint that blocks nobody, so it captures gateway and exit health while excluding target behaviour entirely. Your number also includes 403s, 429s and soft blocks driven by IP reputation and by your own request fingerprint. Both numbers can be accurate at the same time; they answer different questions.

Does an HTTP 200 response mean the proxy request succeeded?

No, and assuming it does is the most expensive mistake in this area. Bot management commonly returns 200 with a JavaScript challenge, a login wall, an empty result set or stale cached content, because a silent block gives away less than a 403. Validate the body against an invariant that only a genuine response contains, and enforce a minimum byte size.

Should retries count as failures in a success rate?

Track both. Per-attempt success rate counts each retry as its own attempt and tells you about the pool. Per-task success rate counts a task that eventually completed as one success and tells you about your pipeline. Publishing only the per-task figure hides how much bandwidth retries are consuming, since every failed attempt was still billed to you.

How many requests do I need to measure success rate reliably?

A few hundred attempts per provider, per target and per exit country gives you a usable point estimate, and you need to repeat it at different hours because residential pools track consumer traffic patterns. One short run at a quiet hour is not a result. If you are comparing providers, interleave their requests so incidents on the target hit every pool equally.

See how the providers actually perform

Our benchmark tests 18 residential proxy providers around the clock from US and EU infrastructure. Success rate, latency, fraud score and price per 100GB, refreshed every five minutes.

Metrics & Testing10 min read

How to Benchmark Proxy Providers: A Repeatable Test Plan

Run every provider at the same time, against the same targets, from the same machine, and log enough that you can compute cost per successful request when the trial ends.

Read the guide
Scraping & Automation11 min read

How to Avoid Getting Blocked While Web Scraping

A 403, a 429, a CAPTCHA and a silent empty 200 are four different problems with four different fixes. Identify which one you have before you change a single header.

Read the guide