<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 98IP Proxy</title>
    <description>The latest articles on DEV Community by 98IP Proxy (@98ip).</description>
    <link>https://dev.to/98ip</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2555187%2F00b2eab5-579e-4c07-94f3-d3ddb8435e32.png</url>
      <title>DEV Community: 98IP Proxy</title>
      <link>https://dev.to/98ip</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/98ip"/>
    <language>en</language>
    <item>
      <title>Find Proxy Throughput Before Retries Find You</title>
      <dc:creator>98IP Proxy</dc:creator>
      <pubDate>Thu, 03 Sep 2026 21:15:27 +0000</pubDate>
      <link>https://dev.to/98ip/find-proxy-throughput-before-retries-find-you-1f88</link>
      <guid>https://dev.to/98ip/find-proxy-throughput-before-retries-find-you-1f88</guid>
      <description>&lt;h1&gt;
  
  
  Find Proxy Throughput Before Retries Find You
&lt;/h1&gt;

&lt;p&gt;Doubling workers does not guarantee twice as many useful pages. A proxy-backed pipeline can saturate its own connection pool, gateway, browser fleet, authorized destination allowance, parser, or downstream queue. Retries then create more work while the useful output curve goes flat.&lt;/p&gt;

&lt;p&gt;I work with 98IP. This is the compact ramp model I would use before buying more capacity—including from us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the output before the load
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StageResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;originalJobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;totalAttempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;validResults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;elapsedMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;queueP95Ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;latencyP95Ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;rateLimited&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;proxyAndComputeCost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StageResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;usefulPerMinute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validResults&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elapsedMs&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;retryAmplification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalAttempts&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;originalJobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;costPerValid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validResults&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;proxyAndComputeCost&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validResults&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;Infinity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Raw requests per second is a transport metric. &lt;code&gt;usefulPerMinute&lt;/code&gt; is closer to a business capacity metric because it requires validated output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ramp one variable set at a time
&lt;/h2&gt;

&lt;p&gt;Hold the client build, proxy product, region, protocol, address family, session policy, target set, timeouts, retry rules, and parser contract constant.&lt;/p&gt;

&lt;p&gt;Then run safe stages such as 1, 2, 4, 8, 12, and 16 workers—only inside the limits of systems you own or are authorized to test. Warm each stage, observe long enough to include normal latency, and repeat before promotion.&lt;/p&gt;

&lt;p&gt;Stop when any gate fails. Do not jump straight to the contractual maximum.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep first attempts visible
&lt;/h2&gt;

&lt;p&gt;Measure content-valid first attempts before retries. A status 200 can still be a login shell, empty table, regional variant, stale page, or block response.&lt;/p&gt;

&lt;p&gt;Classify connection, proxy authentication, DNS, TLS, header timeout, 429/503, content contract, region/session, parser, and storage failures separately.&lt;/p&gt;

&lt;p&gt;If the service sends &lt;code&gt;Retry-After&lt;/code&gt;, respect it as a minimum delay for that scope. Do not rotate identities to defeat a rate limit. Eligible transient retries need exponential backoff with jitter, an attempt ceiling, and a batch-level budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch queue age
&lt;/h2&gt;

&lt;p&gt;One slow target can occupy a shared worker pool while dashboards report high utilization. Track queue depth and p95 age by target class. Add per-target concurrency caps or isolated queues so one domain cannot starve unrelated authorized work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Select the point before the knee
&lt;/h2&gt;

&lt;p&gt;Promote a stage only while:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;useful throughput rises materially;&lt;/li&gt;
&lt;li&gt;first-attempt validity remains inside the gate;&lt;/li&gt;
&lt;li&gt;p95 latency and queue age stay bounded;&lt;/li&gt;
&lt;li&gt;retry amplification stays near baseline;&lt;/li&gt;
&lt;li&gt;429/503 does not trend upward;&lt;/li&gt;
&lt;li&gt;region and session accuracy stay valid;&lt;/li&gt;
&lt;li&gt;cost per valid output stays flat or improves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next stage after these relationships break is the knee. Set production below it and retain headroom.&lt;/p&gt;

&lt;p&gt;Normalize providers with one question: &lt;strong&gt;What will this exact authorized workload cost per validated result inside its freshness window?&lt;/strong&gt; Per-GB, per-request, and per-thread prices are otherwise difficult to compare.&lt;/p&gt;

&lt;p&gt;Use proxies only for systems and data you are authorized to access. Respect contracts, access policies, rate limits, privacy obligations, and applicable law. Concurrency and rotation must never be used to evade controls or misrepresent identity.&lt;/p&gt;

&lt;p&gt;Disclosure: I work with 98IP. More proxy engineering guidance: &lt;a href="https://en.98ip.com/?k=dev" rel="noopener noreferrer"&gt;https://en.98ip.com/?k=dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>performance</category>
      <category>testing</category>
      <category>networking</category>
    </item>
    <item>
      <title>Your Monthly Browser-Proxy Test Window Just Became Too Slow</title>
      <dc:creator>98IP Proxy</dc:creator>
      <pubDate>Thu, 03 Sep 2026 19:15:18 +0000</pubDate>
      <link>https://dev.to/98ip/your-monthly-browser-proxy-test-window-just-became-too-slow-3a16</link>
      <guid>https://dev.to/98ip/your-monthly-browser-proxy-test-window-just-became-too-slow-3a16</guid>
      <description>&lt;h1&gt;
  
  
  Your Monthly Browser-Proxy Test Window Just Became Too Slow
&lt;/h1&gt;

&lt;p&gt;Chrome 153 is scheduled to begin Chrome's two-week stable milestone cadence on September 8. If a browser automation team still validates the browser-plus-proxy stack once a month, it can spend much of every cycle certifying yesterday's build.&lt;/p&gt;

&lt;p&gt;I work with 98IP. The practical response is not “test everything twice as often.” It is to define a small support window and make every result reproducible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model the intersection, not the components
&lt;/h2&gt;

&lt;p&gt;A direct browser check does not cover an authenticated proxy path. At minimum, the release gate should identify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ProxyBrowserCase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;browserBuild&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;driverBuild&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;osImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;gatewayProduct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rotating-residential&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sticky-residential&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http-connect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;socks5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;requestedRegion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;addressFamily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ipv4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ipv6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sessionMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rotating&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sticky&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;targetContract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avoid &lt;code&gt;browserBuild: "latest"&lt;/code&gt;. A failed run with an unknown binary cannot be compared or rolled back cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the support window small
&lt;/h2&gt;

&lt;p&gt;For each production host family, test only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;current production stable and its matching driver;&lt;/li&gt;
&lt;li&gt;next beta and its matching driver;&lt;/li&gt;
&lt;li&gt;previous stable while it remains an approved rollback;&lt;/li&gt;
&lt;li&gt;proxy protocols and address families actually used by production;&lt;/li&gt;
&lt;li&gt;one controlled target plus one representative authorized workload.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a rolling window, not a historical museum.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make “valid result” stricter than status 200
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ProbeResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;caseId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;startedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tunnelOk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tlsOk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;contentContractOk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;regionOk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;stickySequenceOk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;firstAttempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;latencyMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;errorClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ProbeResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tunnelOk&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tlsOk&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentContractOk&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;regionOk&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstAttempt&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;latencyMs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A consent page, login shell, regional redirect, or block page can all be HTTP-successful. Assert the title, schema, locale, region marker, or navigation state the job actually requires.&lt;/p&gt;

&lt;h2&gt;
  
  
  Promote in stages
&lt;/h2&gt;

&lt;p&gt;Run beta canaries against systems you own or are allowed to test. Then promote a new stable build through 5%, 25%, 50%, and 100% of authorized traffic. Keep the previous image available until the observation window closes.&lt;/p&gt;

&lt;p&gt;Use failure groups that map to action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;launch or driver attachment;&lt;/li&gt;
&lt;li&gt;proxy authentication or CONNECT;&lt;/li&gt;
&lt;li&gt;DNS and address-family selection;&lt;/li&gt;
&lt;li&gt;TLS or certificate handling;&lt;/li&gt;
&lt;li&gt;rendering or navigation;&lt;/li&gt;
&lt;li&gt;content contract;&lt;/li&gt;
&lt;li&gt;geography or session continuity;&lt;/li&gt;
&lt;li&gt;capacity, latency, or retry amplification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a regression appears, compare the previous browser, direct path, known-good gateway, rotating versus sticky mode, and IPv4 versus IPv6—one variable at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not let retries hide a bad release
&lt;/h2&gt;

&lt;p&gt;Report first-attempt useful-result rate separately from retry-recovered success. Otherwise an incompatible build can look healthy while consuming more proxy bandwidth, destination capacity, and time.&lt;/p&gt;

&lt;p&gt;The release gate should keep the same thresholds even when releases arrive faster. Shorter cadence calls for smaller automated tests, not weaker acceptance criteria.&lt;/p&gt;

&lt;p&gt;Use browser automation and proxies only on systems and data you are authorized to access. Respect destination terms, rate limits, privacy duties, provider restrictions, and applicable law.&lt;/p&gt;

&lt;p&gt;Disclosure: I work with 98IP. More proxy testing guidance: &lt;a href="https://en.98ip.com/?k=dev" rel="noopener noreferrer"&gt;https://en.98ip.com/?k=dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>testing</category>
      <category>automation</category>
      <category>networking</category>
    </item>
    <item>
      <title>ASN Data Cannot Prove a Residential Proxy Is Residential</title>
      <dc:creator>98IP Proxy</dc:creator>
      <pubDate>Thu, 03 Sep 2026 17:22:39 +0000</pubDate>
      <link>https://dev.to/98ip/asn-data-cannot-prove-a-residential-proxy-is-residential-13p5</link>
      <guid>https://dev.to/98ip/asn-data-cannot-prove-a-residential-proxy-is-residential-13p5</guid>
      <description>&lt;h1&gt;
  
  
  ASN Data Cannot Prove a Residential Proxy Is Residential
&lt;/h1&gt;

&lt;p&gt;A proxy exits through an ASN with a familiar broadband-carrier name. Is that proof that the route is residential?&lt;/p&gt;

&lt;p&gt;No. It is useful evidence about routing context, but it does not prove physical access type, device location, participant consent, exclusivity, reputation, or how a destination will classify the address.&lt;/p&gt;

&lt;p&gt;I work with 98IP. This is the audit model I would want a buyer to use before approving any residential proxy product—including ours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep five evidence layers separate
&lt;/h2&gt;

&lt;p&gt;Represent every sampled exit as a record with distinct fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ExitAudit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sampleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;observedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;requested&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rotating-residential&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;static-isp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mobile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;datacenter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;addressFamily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ipv4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ipv6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;sessionMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rotating&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sticky&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;routing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;asn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;rdapOrganization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;classifications&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;residential&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mobile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;datacenter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;checkedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;provenance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;productDocumentVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;consentMechanismReviewed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;revocationProcessReviewed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;upstreamControlsReviewed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;tunnelOk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;contentValid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;regionValid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;firstAttempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;latencyMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The structure prevents a common analytical shortcut: allowing a broadband-sounding ASN to overwrite missing provenance evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Score disagreement, not “truth”
&lt;/h2&gt;

&lt;p&gt;Network-type databases can disagree. Do not average their labels into a fake consensus. Preserve every observation and compute a disagreement rate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;classificationSummary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExitAudit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;residential&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedVotes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;known&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;known&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;expectedRate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;known&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;expectedVotes&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;known&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;unanimous&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This still does not certify provenance. It only describes how classification sources treated the sample at specific times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add concentration to the audit
&lt;/h2&gt;

&lt;p&gt;Thirty sampled exits may look diverse while 80% of the traffic comes from one ASN. Measure shares by region and session mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;largestShare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;usable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;usable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;usable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this for ASNs and for a consistently defined prefix bucket. A large advertised pool does not guarantee that your workload sees diverse networks in the countries you buy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provenance needs documents and controls
&lt;/h2&gt;

&lt;p&gt;Ask how devices or networks enter the exact product, what participants see before opting in, how they revoke participation, how fast removal occurs, whether upstream suppliers are involved, and how the same requirements are audited downstream.&lt;/p&gt;

&lt;p&gt;Also review abuse handling, destination restrictions, metadata retention, security ownership, and the date and scope of any independent assurance.&lt;/p&gt;

&lt;p&gt;The result is not a binary “ethical” badge. It is a product-specific evidence package with an owner and an expiry date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the workload separately
&lt;/h2&gt;

&lt;p&gt;Only use targets you own or are authorized to test. For every sample, validate the content contract—not only HTTP status. Record the requested and observed region, first-attempt result, latency, session continuity, and cost per valid result.&lt;/p&gt;

&lt;p&gt;Keep retry recovery separate. Otherwise a weak route can look successful after consuming extra bandwidth and target capacity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use four decisions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pass:&lt;/strong&gt; documentation and sampled behavior meet the predefined gates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditional pass:&lt;/strong&gt; only named products, regions, or traffic levels are approved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail:&lt;/strong&gt; repeated contradictory clusters or missing required controls remain unresolved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inconclusive:&lt;/strong&gt; the sample or documents cannot support a decision.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inconclusive should trigger better evidence, not automatic approval.&lt;/p&gt;

&lt;p&gt;Use proxies only for authorized systems and data. Respect provider contracts, destination rules, rate limits, privacy requirements, and applicable law. Network-type analysis must not be used to evade controls or misrepresent identity.&lt;/p&gt;

&lt;p&gt;Disclosure: I work with 98IP. More proxy testing guidance: &lt;a href="https://en.98ip.com/?k=dev" rel="noopener noreferrer"&gt;https://en.98ip.com/?k=dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>networking</category>
      <category>testing</category>
      <category>security</category>
    </item>
    <item>
      <title>Estimate Proxy Bandwidth From Valid Outcomes, Not Page Size</title>
      <dc:creator>98IP Proxy</dc:creator>
      <pubDate>Wed, 02 Sep 2026 21:12:59 +0000</pubDate>
      <link>https://dev.to/98ip/estimate-proxy-bandwidth-from-valid-outcomes-not-page-size-42jb</link>
      <guid>https://dev.to/98ip/estimate-proxy-bandwidth-from-valid-outcomes-not-page-size-42jb</guid>
      <description>&lt;p&gt;When a proxy plan is priced by gigabytes, it is tempting to multiply “average page size” by request count. That estimate usually misses redirects, failed bodies, retries, uploads, headers, browser assets, cache behavior, and the gap between an HTTP 200 and a result your application can actually use.&lt;/p&gt;

&lt;p&gt;Here is a small capacity model that is much harder to fool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the denominator that matters
&lt;/h2&gt;

&lt;p&gt;Define a valid outcome first: the required fields are present, the market and locale are correct, freshness passes, and the response is not a block page or consent screen.&lt;/p&gt;

&lt;p&gt;Then calculate each workload cell separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;monthly_bytes = valid_outcomes
              × attempts_per_valid_outcome
              × billable_bytes_per_attempt

capacity_bytes = monthly_bytes
               × (1 + growth_rate)
               × (1 + safety_headroom)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Measure &lt;code&gt;attempts_per_valid_outcome&lt;/code&gt; during a pilot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;attempts_per_valid_outcome = all_billable_attempts / valid_outcomes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That ratio reveals retry amplification without pretending every failed request is independently recoverable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep three byte counters
&lt;/h2&gt;

&lt;p&gt;Use matching request IDs and time windows to compare:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Application payload bytes&lt;/strong&gt; — what the app consumed after decoding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client transfer bytes&lt;/strong&gt; — what curl, the HTTP library, or the browser reports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provider-billed bytes&lt;/strong&gt; — what the proxy dashboard counts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;They are not interchangeable. The application may omit headers and failed attempts. Automatic decompression can make the in-memory response larger than the transferred body. A provider may count uploads, redirects, error bodies, or both directions of a tunnel.&lt;/p&gt;

&lt;p&gt;The contract's billing definition should drive the forecast; the other counters help explain mismatches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Never mix browser and API averages
&lt;/h2&gt;

&lt;p&gt;An API request might transfer a small JSON response. A browser visit can load HTML, scripts, styles, fonts, images, video, analytics, API calls, service-worker updates, and speculative resources.&lt;/p&gt;

&lt;p&gt;Measure cold-cache and warm-cache browser sessions separately. Isolated profiles and rotating workers may not keep the cache savings you see on a developer laptop.&lt;/p&gt;

&lt;p&gt;Block only resources proven unnecessary for the authorized output. Breaking consent, security, or application behavior to save bandwidth produces an invalid benchmark.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a range, not one number
&lt;/h2&gt;

&lt;p&gt;For each target × region × client × proxy product × session mode cell, create low, expected, and high assumptions for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;attempts per valid outcome;&lt;/li&gt;
&lt;li&gt;billable bytes per attempt;&lt;/li&gt;
&lt;li&gt;monthly valid outcomes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Outcomes&lt;/th&gt;
&lt;th&gt;Attempts/outcome&lt;/th&gt;
&lt;th&gt;MB/attempt&lt;/th&gt;
&lt;th&gt;Base GB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;200,000&lt;/td&gt;
&lt;td&gt;1.05&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expected&lt;/td&gt;
&lt;td&gt;200,000&lt;/td&gt;
&lt;td&gt;1.18&lt;/td&gt;
&lt;td&gt;0.24&lt;/td&gt;
&lt;td&gt;56.64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;200,000&lt;/td&gt;
&lt;td&gt;1.45&lt;/td&gt;
&lt;td&gt;0.32&lt;/td&gt;
&lt;td&gt;92.8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With 15% growth and 20% safety headroom, the expected capacity is about 78.16 GB.&lt;/p&gt;

&lt;p&gt;Those are example numbers, not defaults. A browser workflow can be orders of magnitude larger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calibrate before committing
&lt;/h2&gt;

&lt;p&gt;Run a small authorized pilot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;representative targets for every critical cell;&lt;/li&gt;
&lt;li&gt;concurrency one at first;&lt;/li&gt;
&lt;li&gt;retries disabled for the baseline;&lt;/li&gt;
&lt;li&gt;planned bounded retries for the second run;&lt;/li&gt;
&lt;li&gt;cold and warm cache separated;&lt;/li&gt;
&lt;li&gt;application, client, and provider counters reconciled;&lt;/li&gt;
&lt;li&gt;403 and 429 treated as stop signals, not rotation triggers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Extend the pilot when a critical cell still has an unstable range. A global average can hide the one small market that drives the purchase.&lt;/p&gt;

&lt;p&gt;Finally, review overage pricing, rollover, expiration, minimum commitment, and whether the vendor calls a billion bytes a GB or uses GiB.&lt;/p&gt;

&lt;p&gt;I work with 98IP, a proxy service. I am sharing this because forecasting from successful outcomes makes plan comparisons more honest and reduces retry waste. Product information: &lt;a href="https://en.98ip.com/?k=dev" rel="noopener noreferrer"&gt;https://en.98ip.com/?k=dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use proxies only for authorized systems and data. Follow destination terms, robots guidance, rate limits, privacy obligations, and data-minimization requirements. Do not budget traffic for repeatedly evading denials.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
      <category>devops</category>
    </item>
    <item>
      <title>curl 8.22 Fixed an HTTP/3 Proxy Crash. Test the Boundary, Not Just the Happy Path</title>
      <dc:creator>98IP Proxy</dc:creator>
      <pubDate>Wed, 02 Sep 2026 19:14:50 +0000</pubDate>
      <link>https://dev.to/98ip/curl-822-fixed-an-http3-proxy-crash-test-the-boundary-not-just-the-happy-path-d1k</link>
      <guid>https://dev.to/98ip/curl-822-fixed-an-http3-proxy-crash-test-the-boundary-not-just-the-happy-path-d1k</guid>
      <description>&lt;p&gt;curl 8.22.0 shipped on September 2 with two proxy-relevant fixes: an HTTP/3 proxy path no longer dereferences a null pointer when a non-status response header arrives before &lt;code&gt;:status&lt;/code&gt;, and CONNECT trailer handling was corrected.&lt;/p&gt;

&lt;p&gt;If you operate proxy-backed jobs, the useful lesson is not “turn on more retries.” It is that your test suite needs to distinguish a client parser failure from a gateway failure, exit failure, and destination response.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a client bug becomes fake proxy churn
&lt;/h2&gt;

&lt;p&gt;Imagine a worker crashes while parsing an unusual HTTP/3 proxy response. The scheduler sees an incomplete job, selects a different exit, and retries. The same client hits the same condition and crashes again.&lt;/p&gt;

&lt;p&gt;Your dashboard now shows several failed exits, but the exit was never the independent variable. Rotation multiplied the symptom.&lt;/p&gt;

&lt;p&gt;CONNECT framing errors can produce a quieter version of the same mistake: a tunnel is labelled unavailable even though authentication and routing completed. That can degrade a healthy gateway cohort and increase cost per successful request.&lt;/p&gt;

&lt;h2&gt;
  
  
  A compact paired test
&lt;/h2&gt;

&lt;p&gt;Run the old and new curl builds against the same authorized fixture. Hold the gateway, credentials, protocol backend, target, concurrency, timeouts, and rotation policy constant.&lt;/p&gt;

&lt;p&gt;Include six cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Normal HTTP/1.1 CONNECT.&lt;/li&gt;
&lt;li&gt;Your supported HTTP/2 proxy path.&lt;/li&gt;
&lt;li&gt;HTTP/3 proxy handling in a controlled fixture.&lt;/li&gt;
&lt;li&gt;A controlled CONNECT response with trailers.&lt;/li&gt;
&lt;li&gt;Invalid proxy credentials as a negative control.&lt;/li&gt;
&lt;li&gt;An unreachable gateway to prove fail-closed behavior.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For each run, capture the curl version and linked backend, negotiated protocol, sanitized gateway cohort, curl error code, process exit or crash signal, tunnel outcome, total latency, retry count, and application-level validity.&lt;/p&gt;

&lt;p&gt;Do not store proxy credentials, cookies, authorization headers, or unnecessary payloads in the artifact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Classify at the first failure boundary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Process crash:&lt;/strong&gt; quarantine the client build or test condition. Do not rotate exits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocol parser error:&lt;/strong&gt; preserve a sanitized diagnostic and reproduce against a controlled server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;407 or explicit proxy denial:&lt;/strong&gt; inspect authentication and gateway policy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tunnel timeout:&lt;/strong&gt; inspect gateway reachability, TLS to the proxy, and timeout phase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;403/429 from the destination:&lt;/strong&gt; respect the response; do not treat rotation as a fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;200 with invalid content:&lt;/strong&gt; transport succeeded. Validate locale, redirect, cache, and schema separately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This one change in taxonomy prevents a client problem from corrupting pool-health data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Canary metrics that matter
&lt;/h2&gt;

&lt;p&gt;Start with one worker or no more than one percent of eligible traffic. Compare old and new builds by protocol and gateway cohort:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;crashes per 10,000 attempts;&lt;/li&gt;
&lt;li&gt;successful tunnels per attempt;&lt;/li&gt;
&lt;li&gt;retries per valid result;&lt;/li&gt;
&lt;li&gt;unique exits consumed per valid result;&lt;/li&gt;
&lt;li&gt;p50/p95 connection time;&lt;/li&gt;
&lt;li&gt;categorized curl errors;&lt;/li&gt;
&lt;li&gt;application-valid responses per attempt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Roll back if crash rate is nonzero, direct fallback appears, authentication errors rise unexpectedly, or cost per valid result worsens materially. Adding retries is not a rollback plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  One important limit
&lt;/h2&gt;

&lt;p&gt;A curl fix does not prove your proxy provider supports HTTP/3. Support still depends on how curl was built, the HTTP/3 backend, the proxy service, and the network path. Keep malformed-response tests inside infrastructure you control or are authorized to test.&lt;/p&gt;

&lt;p&gt;I work with 98IP, a proxy service. I am sharing this because client-side failures are routinely mislabelled as “bad proxies,” which makes both reliability and cost analysis worse. For product details: &lt;a href="https://en.98ip.com/?k=dev" rel="noopener noreferrer"&gt;https://en.98ip.com/?k=dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use proxies only where authorized. Follow destination terms, rate limits, robots guidance, privacy rules, and data-minimization requirements. Do not use retries or rotation to evade access controls.&lt;/p&gt;

&lt;p&gt;Official basis: curl project, “Changes in 8.22.0,” September 2, 2026.&lt;br&gt;
``&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>Stop Comparing Proxy Plans by Price per GB</title>
      <dc:creator>98IP Proxy</dc:creator>
      <pubDate>Wed, 02 Sep 2026 17:14:15 +0000</pubDate>
      <link>https://dev.to/98ip/stop-comparing-proxy-plans-by-price-per-gb-1k0m</link>
      <guid>https://dev.to/98ip/stop-comparing-proxy-plans-by-price-per-gb-1k0m</guid>
      <description>&lt;p&gt;The least expensive proxy plan on a pricing page can be the most expensive route in production.&lt;/p&gt;

&lt;p&gt;Price per GB and price per IP describe what you buy. They do not describe what the workload actually delivers. Retries consume traffic, slow responses occupy workers, block pages pass superficial status checks, and invalid records create repair work later. A better procurement metric is &lt;strong&gt;cost per validated successful request&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is the scorecard I would use for an authorized crawler, monitoring job, ad-verification system, or market-research workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a business-level success definition
&lt;/h2&gt;

&lt;p&gt;Do not count every HTTP &lt;code&gt;200&lt;/code&gt; as success. Define the result that downstream users can actually consume.&lt;/p&gt;

&lt;p&gt;A request might count only when all of these are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the proxy connection and TLS negotiation complete;&lt;/li&gt;
&lt;li&gt;the response status and content type are expected;&lt;/li&gt;
&lt;li&gt;the body is not a challenge or error template;&lt;/li&gt;
&lt;li&gt;required fields are present and fresh;&lt;/li&gt;
&lt;li&gt;the requested country or session behavior is correct;&lt;/li&gt;
&lt;li&gt;latency remains inside the job deadline;&lt;/li&gt;
&lt;li&gt;the access is authorized and consistent with the destination's rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The definition should be machine-checkable. If an analyst has to inspect every response manually, the test is measuring operator patience as much as proxy quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculate the full numerator
&lt;/h2&gt;

&lt;p&gt;Use this basic equation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cost_per_validated_success = total_workload_cost / validated_successes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The numerator should include more than the proxy invoice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;proxy bandwidth, IP, port, or subscription charges;&lt;/li&gt;
&lt;li&gt;bandwidth spent on failed attempts and permitted retries;&lt;/li&gt;
&lt;li&gt;browser or worker compute time;&lt;/li&gt;
&lt;li&gt;parsing, validation, storage, and observability costs;&lt;/li&gt;
&lt;li&gt;engineering time used to investigate failures;&lt;/li&gt;
&lt;li&gt;manual repair required before data becomes usable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Suppose Plan A costs $100 and produces 9,000 validated results with $25 of compute and operations. Its unit cost is about $0.0139. Plan B costs only $75 but produces 5,000 valid results and creates $40 of additional work. Its unit cost is $0.023. The cheaper invoice produced a result that was roughly 65% more expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrument the request funnel
&lt;/h2&gt;

&lt;p&gt;A single success percentage hides where money disappears. Record at least five stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;attempted
  -&amp;gt; transport response received
  -&amp;gt; acceptable HTTP/content response
  -&amp;gt; validation passed
  -&amp;gt; usable downstream outcome
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gaps have different meanings.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attempted to transport response: inspect DNS, proxy negotiation, TLS, timeouts, and gateway capacity.&lt;/li&gt;
&lt;li&gt;Transport to acceptable response: inspect status classes, content types, and destination backpressure.&lt;/li&gt;
&lt;li&gt;Acceptable response to validation: detect challenge templates, missing fields, stale pages, and locale errors.&lt;/li&gt;
&lt;li&gt;Validation to usable outcome: inspect parsers, schemas, deduplication, and downstream delivery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation prevents a parser regression from being blamed on the proxy and prevents a weak route from being hidden behind aggressive retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the comparison controlled
&lt;/h2&gt;

&lt;p&gt;Run competing plans against the same representative workload. Keep these variables fixed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;target set and request method;&lt;/li&gt;
&lt;li&gt;country and city requirements;&lt;/li&gt;
&lt;li&gt;concurrency and time-of-day window;&lt;/li&gt;
&lt;li&gt;timeout and retry budget;&lt;/li&gt;
&lt;li&gt;static or rotating session policy;&lt;/li&gt;
&lt;li&gt;browser or client version;&lt;/li&gt;
&lt;li&gt;validation rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Randomize request assignment so one provider does not receive only easy targets. Report first-attempt success separately from final success after retries. Otherwise a plan can appear reliable while consuming two or three times the traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Segment before choosing a winner
&lt;/h2&gt;

&lt;p&gt;Global averages are dangerous. Calculate unit cost by region, destination, response size, session mode, and concurrency band.&lt;/p&gt;

&lt;p&gt;A pool might be excellent for small North American API responses and inefficient for large APAC pages. Another might have a slightly higher blended price but far fewer retries in the one market that matters to the business. Routing workloads by measured strengths is often better than declaring one universal winner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add stop conditions to the pilot
&lt;/h2&gt;

&lt;p&gt;Write procurement gates before the test begins. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validated success must remain above the required threshold;&lt;/li&gt;
&lt;li&gt;p95 latency must fit the job deadline;&lt;/li&gt;
&lt;li&gt;retries per usable result must stay below a fixed budget;&lt;/li&gt;
&lt;li&gt;location mismatches must remain below an acceptable rate;&lt;/li&gt;
&lt;li&gt;unit cost must remain below the business ceiling;&lt;/li&gt;
&lt;li&gt;no test may bypass access controls or continue after explicit backpressure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Predefined gates make it harder to rationalize a poor result because a plan looked attractive on the pricing page.&lt;/p&gt;

&lt;h2&gt;
  
  
  A compact scorecard
&lt;/h2&gt;

&lt;p&gt;For every candidate, capture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;attempted requests;&lt;/li&gt;
&lt;li&gt;first-attempt validated successes;&lt;/li&gt;
&lt;li&gt;final validated successes;&lt;/li&gt;
&lt;li&gt;bytes transferred;&lt;/li&gt;
&lt;li&gt;retry count by reason;&lt;/li&gt;
&lt;li&gt;median and p95 latency;&lt;/li&gt;
&lt;li&gt;route or location mismatches;&lt;/li&gt;
&lt;li&gt;challenge and invalid-content rate;&lt;/li&gt;
&lt;li&gt;compute and operator time;&lt;/li&gt;
&lt;li&gt;total cost and cost per validated success.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Re-run the same scorecard periodically. Proxy performance, destination behavior, traffic mix, and your own client code all change.&lt;/p&gt;

&lt;p&gt;The goal is not the cheapest connection. It is the lowest sustainable cost for a result the business can trust.&lt;/p&gt;

&lt;p&gt;Disclosure: I work with 98IP. This article reflects an operator perspective and does not claim that a proxy permits access to systems or data you are not authorized to use. Respect destination policies, rate limits, privacy obligations, and applicable law. More English proxy engineering resources are available at &lt;a href="https://en.98ip.com/?k=dev" rel="noopener noreferrer"&gt;https://en.98ip.com/?k=dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>devops</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Rotate Proxy Credentials Without Turning Rotation Into an Outage</title>
      <dc:creator>98IP Proxy</dc:creator>
      <pubDate>Wed, 02 Sep 2026 13:11:41 +0000</pubDate>
      <link>https://dev.to/98ip/rotate-proxy-credentials-without-turning-rotation-into-an-outage-3el7</link>
      <guid>https://dev.to/98ip/rotate-proxy-credentials-without-turning-rotation-into-an-outage-3el7</guid>
      <description>&lt;p&gt;Proxy credentials are often treated like a configuration value: replace the old secret, restart the workers, and move on.&lt;/p&gt;

&lt;p&gt;That is exactly how a routine security task becomes a production incident.&lt;/p&gt;

&lt;p&gt;A crawler, monitoring agent, or data-collection service rarely has one credential consumer. The secret may exist in a scheduler, container platform, local development profile, serverless job, browser worker, health check, and emergency runbook. Those consumers do not all reload at the same time. A safe rotation therefore needs overlap, observability, and a rollback boundary.&lt;/p&gt;

&lt;p&gt;This guide describes a provider-neutral rollout for systems you own or are authorized to operate.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, separate the credential from proxy policy
&lt;/h2&gt;

&lt;p&gt;Treat these as different configuration objects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gateway hostname and port;&lt;/li&gt;
&lt;li&gt;authentication username or account identifier;&lt;/li&gt;
&lt;li&gt;secret or token;&lt;/li&gt;
&lt;li&gt;target country, region, or session parameters;&lt;/li&gt;
&lt;li&gt;retry and timeout policy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If all five values live in one opaque URL, a password change can accidentally change routing behavior too. Parse the connection configuration once, keep the secret in a secret manager, and inject only the current credential at runtime.&lt;/p&gt;

&lt;p&gt;Never print the full proxy URL. A masked log such as &lt;code&gt;http://user:***@gateway:port&lt;/code&gt; is still useful for identifying the gateway without turning logs into a credential store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use an overlap window
&lt;/h2&gt;

&lt;p&gt;The safest model is two valid credentials for a short, controlled period:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the new credential without revoking the old one.&lt;/li&gt;
&lt;li&gt;Deploy the new secret to a small canary group.&lt;/li&gt;
&lt;li&gt;Verify authentication, tunnel creation, TLS completion, DNS behavior, target-region accuracy, and session stability.&lt;/li&gt;
&lt;li&gt;Expand the rollout in stages.&lt;/li&gt;
&lt;li&gt;Confirm that no active workload is using the old credential.&lt;/li&gt;
&lt;li&gt;Revoke the old credential.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your provider cannot support overlapping credentials, schedule a maintenance window or use a blue/green account pattern. Do not assume that a synchronized restart will be truly simultaneous across queues, autoscalers, and long-running jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the client reloadable
&lt;/h2&gt;

&lt;p&gt;A long-running worker should be able to obtain the current secret without a full application redeploy. The exact mechanism depends on your platform, but the design should have three properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the secret is read from a managed source;&lt;/li&gt;
&lt;li&gt;workers can refresh it on a bounded interval or configuration event;&lt;/li&gt;
&lt;li&gt;in-flight requests keep their original credential while new requests use the new one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid retrying an authentication failure forever. A &lt;code&gt;407 Proxy Authentication Required&lt;/code&gt; after rotation is usually a configuration signal, not evidence that you need another exit IP.&lt;/p&gt;

&lt;p&gt;Here is a language-neutral decision model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if response == 407:
    refresh_secret_once()
    retry_once_with_same_route()
    if response == 407:
        open_rotation_circuit()
        alert("credential rejected")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same route matters. Rotating the exit at the same time changes two variables and makes diagnosis harder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Canary on more than status code
&lt;/h2&gt;

&lt;p&gt;A successful TCP connection is not enough. For each canary, record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;secret version identifier, never the secret value;&lt;/li&gt;
&lt;li&gt;worker release and configuration revision;&lt;/li&gt;
&lt;li&gt;gateway and requested region;&lt;/li&gt;
&lt;li&gt;proxy authentication result;&lt;/li&gt;
&lt;li&gt;CONNECT or SOCKS handshake result;&lt;/li&gt;
&lt;li&gt;DNS mode and negotiated IP family;&lt;/li&gt;
&lt;li&gt;TLS result;&lt;/li&gt;
&lt;li&gt;exit geography and ASN, when that is part of the contract;&lt;/li&gt;
&lt;li&gt;request latency and application result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare the new credential with a known-good control under the same workload. A sudden increase in &lt;code&gt;407&lt;/code&gt; responses points toward distribution or activation problems. Normal authentication with changed geography points toward a routing configuration change. Normal routing with application rejection points farther downstream.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roll out in bounded stages
&lt;/h2&gt;

&lt;p&gt;A practical sequence is 1%, 10%, 50%, and 100% of workers. Hold each stage long enough to observe at least one normal job cycle.&lt;/p&gt;

&lt;p&gt;Define stop conditions before deployment. Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;more than 0.5% proxy-authentication failures;&lt;/li&gt;
&lt;li&gt;any region mismatch for a regulated workflow;&lt;/li&gt;
&lt;li&gt;p95 tunnel latency increasing by more than 20%;&lt;/li&gt;
&lt;li&gt;workers continuing to request the retired secret version;&lt;/li&gt;
&lt;li&gt;queue growth beyond the normal retry budget.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a stop condition triggers, pause expansion. Do not compensate with unlimited retries: that can amplify load and hide the actual distribution failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prove the old credential is unused
&lt;/h2&gt;

&lt;p&gt;Revocation should be based on evidence, not elapsed time alone. Check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;secret-manager access logs by version;&lt;/li&gt;
&lt;li&gt;active worker configuration versions;&lt;/li&gt;
&lt;li&gt;scheduled and paused jobs;&lt;/li&gt;
&lt;li&gt;disaster-recovery environments;&lt;/li&gt;
&lt;li&gt;local or CI profiles that can reach production;&lt;/li&gt;
&lt;li&gt;health checks and synthetic monitors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then run a negative test from an isolated, authorized environment: the old credential should fail, while the new credential should succeed. Do not place either secret in a ticket, chat transcript, screenshot, or HAR file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rotation checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Inventory every credential consumer.&lt;/li&gt;
&lt;li&gt;[ ] Separate authentication from routing policy.&lt;/li&gt;
&lt;li&gt;[ ] Create a new secret version and record its owner and expiry.&lt;/li&gt;
&lt;li&gt;[ ] Confirm an overlap or maintenance strategy.&lt;/li&gt;
&lt;li&gt;[ ] Canary with the same route and workload.&lt;/li&gt;
&lt;li&gt;[ ] Monitor authentication, routing, latency, and application signals separately.&lt;/li&gt;
&lt;li&gt;[ ] Roll out in bounded stages with written stop conditions.&lt;/li&gt;
&lt;li&gt;[ ] Verify that the old version has no readers.&lt;/li&gt;
&lt;li&gt;[ ] Revoke the old credential and run a negative test.&lt;/li&gt;
&lt;li&gt;[ ] Remove temporary access and shorten retained diagnostic data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The operational principle
&lt;/h2&gt;

&lt;p&gt;Credential rotation is successful only when the new secret is accepted, the intended proxy route remains correct, every consumer has migrated, and the old secret is demonstrably unusable.&lt;/p&gt;

&lt;p&gt;That makes rotation a small deployment—not a string replacement.&lt;/p&gt;

&lt;p&gt;Disclosure: I work with 98IP. This article reflects an operator perspective and does not promise that credentials can override a destination's access policies. Use proxy infrastructure only for systems and data you are authorized to access. More English proxy engineering resources are available at &lt;a href="https://en.98ip.com/?k=dev" rel="noopener noreferrer"&gt;https://en.98ip.com/?k=dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>proxy</category>
      <category>devops</category>
      <category>security</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>A Happy Eyeballs Test Can Measure the Wrong Half of Your Proxy Route</title>
      <dc:creator>98IP Proxy</dc:creator>
      <pubDate>Tue, 01 Sep 2026 22:15:07 +0000</pubDate>
      <link>https://dev.to/98ip/a-happy-eyeballs-test-can-measure-the-wrong-half-of-your-proxy-route-2e4o</link>
      <guid>https://dev.to/98ip/a-happy-eyeballs-test-can-measure-the-wrong-half-of-your-proxy-route-2e4o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclosure: I work with 98IP. This is vendor-neutral guidance for authorized connectivity testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Happy Eyeballs reduces the delay users feel when either IPv6 or IPv4 is slow. But once a proxy gateway is added, a single “IPv6 won” metric can describe the wrong connection.&lt;/p&gt;

&lt;p&gt;There are at least two legs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client --(IPv4 or IPv6)--&amp;gt; proxy gateway --(IPv4 or IPv6)--&amp;gt; destination
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client usually races addresses for the gateway. The gateway may resolve and connect to the destination independently. An IPv6 gateway connection can therefore produce an IPv4 exit, and an IPv4 gateway can use an IPv6 target path.&lt;/p&gt;

&lt;p&gt;The IETF HAPPY Working Group's July 2026 Happy Eyeballs v3 draft is still an active Internet-Draft, not a final standard. Its work on scheduling, DNS results and remembered outcomes is a useful reason to make proxy measurement explicit now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observe four layers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DNS
&lt;/h3&gt;

&lt;p&gt;Record who resolves the gateway and destination, the A/AAAA answer sets, order, resolver region, cache state and time. SOCKS hostname mode can move target DNS to the gateway; a client-resolved SOCKS request does not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gateway connection
&lt;/h3&gt;

&lt;p&gt;Record the first and second attempt start times, gateway family, handshake result, TLS, proxy authentication, winner and cancellation delay. This is the leg a local Happy Eyeballs implementation usually controls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tunnel
&lt;/h3&gt;

&lt;p&gt;Record CONNECT/SOCKS status, remote-DNS mode, target family when observable, tunnel time and a structured category such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gateway_unreachable
proxy_auth_failed
target_connect_failed
tunnel_timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Application result
&lt;/h3&gt;

&lt;p&gt;Record first byte, complete response time, useful-result status and bytes. Keep destination policy results such as &lt;code&gt;403&lt;/code&gt; and &lt;code&gt;429&lt;/code&gt; out of the gateway transport score unless evidence connects them to the gateway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a bounded matrix
&lt;/h2&gt;

&lt;p&gt;Use a gateway and destination you own or have explicit permission to test:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;IPv6-only gateway, remote target resolution.&lt;/li&gt;
&lt;li&gt;IPv4-only gateway, same target and request.&lt;/li&gt;
&lt;li&gt;Dual-stack gateway with the production racing logic.&lt;/li&gt;
&lt;li&gt;Dual-stack gateway with a client-resolved destination control.&lt;/li&gt;
&lt;li&gt;Preferred-family delay or loss injected in controlled infrastructure.&lt;/li&gt;
&lt;li&gt;Both families healthy, using cold and warm connection pools.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep credentials, region, target, payload, timeout and workload purpose constant. Change one variable per comparison.&lt;/p&gt;

&lt;p&gt;The useful metrics are not just connection time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;winning gateway family;&lt;/li&gt;
&lt;li&gt;observable target family;&lt;/li&gt;
&lt;li&gt;p50/p95 first byte;&lt;/li&gt;
&lt;li&gt;tunnel and complete-response success;&lt;/li&gt;
&lt;li&gt;mid-session reset rate;&lt;/li&gt;
&lt;li&gt;fallback rate and fallback success;&lt;/li&gt;
&lt;li&gt;extra connection attempts per useful result;&lt;/li&gt;
&lt;li&gt;traffic and cost per authorized useful result.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Failure injection must not duplicate the request
&lt;/h2&gt;

&lt;p&gt;A racing algorithm should race connection setup, not execute a purchase, message, upload or other state-changing action twice. Use an idempotent authorized canary, cap attempts and verify losing sockets, timers, pages and sessions are cleaned up.&lt;/p&gt;

&lt;p&gt;Do not convert a target &lt;code&gt;429&lt;/code&gt; into faster IP rotation. Do not claim end-to-end IPv6 because the client reached an IPv6 gateway. And do not penalize a provider for a destination policy decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Procurement questions
&lt;/h2&gt;

&lt;p&gt;Ask whether the provider offers dual-stack, IPv4-only and IPv6-only gateway controls; where SOCKS hostname DNS runs; whether target family appears in sanitized logs; whether authentication, allowlists, concurrency and pricing differ by family; and whether direct fallback is impossible after gateway failure.&lt;/p&gt;

&lt;p&gt;A public exit-IP check is useful, but it does not explain both routing decisions.&lt;/p&gt;

&lt;p&gt;For more practical proxy and Internet-operations guidance, visit &lt;a href="https://en.98ip.com/?k=dev" rel="noopener noreferrer"&gt;98IP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Operate only on authorized gateways, destinations, accounts and data. Respect website terms, robots directives, privacy obligations and rate limits. Address-family racing is a reliability technique, not a way to bypass a publisher's decision.&lt;br&gt;
``&lt;/p&gt;

</description>
      <category>networking</category>
      <category>devops</category>
      <category>testing</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Stop Rotating Proxies When a Crawl Fails a Content-Use Check</title>
      <dc:creator>98IP Proxy</dc:creator>
      <pubDate>Tue, 01 Sep 2026 20:18:56 +0000</pubDate>
      <link>https://dev.to/98ip/stop-rotating-proxies-when-a-crawl-fails-a-content-use-check-4a4d</link>
      <guid>https://dev.to/98ip/stop-rotating-proxies-when-a-crawl-fails-a-content-use-check-4a4d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclosure: I work with 98IP. This is a vendor-neutral operational pattern for authorized data collection.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cloudflare's Browser Rendering &lt;code&gt;/crawl&lt;/code&gt; endpoint now respects the Content Signals &lt;code&gt;use&lt;/code&gt; directive. The caller can declare &lt;code&gt;contentUse&lt;/code&gt; as &lt;code&gt;reference&lt;/code&gt; or &lt;code&gt;full&lt;/code&gt;; when the target's published ceiling is stricter, the request is rejected.&lt;/p&gt;

&lt;p&gt;The most important engineering consequence is not the new parameter. It is the need to stop treating every rejected crawl as a bad proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a policy result to your taxonomy
&lt;/h2&gt;

&lt;p&gt;A useful classifier should separate at least:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proxy_auth_failed
dns_failed
tunnel_failed
transport_timeout
rate_limited
bot_challenge
content_use_denied
application_not_authorized
render_failed
success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;content_use_denied&lt;/code&gt; is deterministic policy feedback. Rotating the exit, changing the user agent or retrying at higher concurrency does not repair the mismatch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep four layers in every observation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"identity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"crawler"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"catalog-reference-bot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026.09.02"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"approved_purpose"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reference"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"declared_content_use"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reference"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"observed_policy_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-02T03:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-residential-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"family"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ipv4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"session_mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sticky"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"content_use_denied"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"retry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not put proxy passwords, cookies, tokens or signing keys in this record.&lt;/p&gt;

&lt;p&gt;Identity tells you who called. Permission tells you what the caller was allowed to do. Transport tells you how the request traveled. Outcome tells you what happened. Combining them into &lt;code&gt;proxy_failed=true&lt;/code&gt; destroys the evidence needed for a correct response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retry by cause, not by status code
&lt;/h2&gt;

&lt;p&gt;A safe retry function can be boring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;retryDecision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content_use_denied&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application_not_authorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;proxy_auth_failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inspect_config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rate_limited&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;respect_retry_after&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transport_timeout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bounded_retry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact logic depends on your workload, but policy denial should never enter a proxy-rotation loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test with an authorized policy canary
&lt;/h2&gt;

&lt;p&gt;Before production rollout:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use a site you own or have explicit permission to test.&lt;/li&gt;
&lt;li&gt;Publish controlled content-use variants.&lt;/li&gt;
&lt;li&gt;Send bounded &lt;code&gt;reference&lt;/code&gt; and &lt;code&gt;full&lt;/code&gt; jobs.&lt;/li&gt;
&lt;li&gt;Confirm compatible jobs proceed.&lt;/li&gt;
&lt;li&gt;Confirm incompatible jobs produce &lt;code&gt;content_use_denied&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Verify no proxy rotation occurs after that result.&lt;/li&gt;
&lt;li&gt;Check that pages, sessions and timers are cleaned up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This canary validates policy logic without generating unwanted traffic against third-party sites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep provider scoring independent
&lt;/h2&gt;

&lt;p&gt;Measure proxy quality only on permitted workloads and controlled targets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;connect and TLS success;&lt;/li&gt;
&lt;li&gt;time to first byte;&lt;/li&gt;
&lt;li&gt;p50 and p95 latency;&lt;/li&gt;
&lt;li&gt;throughput;&lt;/li&gt;
&lt;li&gt;unexpected rotation;&lt;/li&gt;
&lt;li&gt;address-family behavior;&lt;/li&gt;
&lt;li&gt;ASN and prefix concentration;&lt;/li&gt;
&lt;li&gt;cost per useful authorized result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A destination rejecting a declared purpose is not evidence that the exit is slow, unstable or incorrectly located.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every crawler has a named purpose and owner.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contentUse&lt;/code&gt; is set from approved workload metadata.&lt;/li&gt;
&lt;li&gt;Policy evidence is timestamped.&lt;/li&gt;
&lt;li&gt;Policy denials have a no-retry rule.&lt;/li&gt;
&lt;li&gt;Provider metrics exclude deterministic policy denials.&lt;/li&gt;
&lt;li&gt;Diagnostic records exclude credentials and personal data.&lt;/li&gt;
&lt;li&gt;Changes are tested on an authorized canary.&lt;/li&gt;
&lt;li&gt;Robots directives, terms, privacy and rate limits remain separate gates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What not to do
&lt;/h2&gt;

&lt;p&gt;Do not silently relabel a &lt;code&gt;full&lt;/code&gt; workload as &lt;code&gt;reference&lt;/code&gt;. Do not rotate residential exits to search for a policy inconsistency. Do not report policy denials as an IP-quality defect. And do not assume that one compatible machine-readable signal grants permission for every purpose.&lt;/p&gt;

&lt;p&gt;For more vendor-neutral proxy operations guidance, visit &lt;a href="https://en.98ip.com/?k=dev" rel="noopener noreferrer"&gt;98IP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Proxies are a transport tool for authorized work, not a method for overriding publisher decisions.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>proxy</category>
      <category>devops</category>
    </item>
    <item>
      <title>Validate Residential Proxy Location as a Dataset, Not a Screenshot</title>
      <dc:creator>98IP Proxy</dc:creator>
      <pubDate>Tue, 01 Sep 2026 18:12:31 +0000</pubDate>
      <link>https://dev.to/98ip/validate-residential-proxy-location-as-a-dataset-not-a-screenshot-1dh3</link>
      <guid>https://dev.to/98ip/validate-residential-proxy-location-as-a-dataset-not-a-screenshot-1dh3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclosure: I work with 98IP. This post describes a vendor-neutral test method and does not claim that one lookup service is authoritative.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A proxy gateway accepts &lt;code&gt;country=US&amp;amp;city=New_York&lt;/code&gt;. Your first request returns an IP that one lookup service calls New York. Is the location requirement validated?&lt;/p&gt;

&lt;p&gt;No. You validated one exit against one dataset at one time.&lt;/p&gt;

&lt;p&gt;A production-quality acceptance test needs a row per exit observation and enough dimensions to expose pool-level inconsistencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use an append-only observation schema
&lt;/h2&gt;

&lt;p&gt;Start with a record like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"geo-us-nyc-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"observed_at_utc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-01T00:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requested"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"New York"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gateway"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http-connect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"address_family"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ipv4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"session_mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rotating"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"network"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"exit_ip_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sanitized-value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"asn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;64500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sanitized-prefix"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_observations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"dataset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"New York"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"dataset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NJ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Newark"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"market"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US-NY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"accepted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"attempts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"exit_changes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not overwrite the requested city with an observed city. Do not collapse disagreeing datasets into one value. Preserve the evidence that explains the disagreement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the matrix around production dimensions
&lt;/h2&gt;

&lt;p&gt;At minimum, cross:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every production market;&lt;/li&gt;
&lt;li&gt;every gateway used by the application;&lt;/li&gt;
&lt;li&gt;rotating and sticky session modes;&lt;/li&gt;
&lt;li&gt;IPv4 and IPv6 when both are sold;&lt;/li&gt;
&lt;li&gt;at least three separated time windows;&lt;/li&gt;
&lt;li&gt;multiple unique exits per cell.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a high-value market, 20–30 exits is a useful starting sample. For early provider screening, use a smaller bounded sample and expand only if the decision value justifies the traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disable retry ambiguity
&lt;/h2&gt;

&lt;p&gt;Automatic retries can hide the first failing exit.&lt;/p&gt;

&lt;p&gt;During the acceptance run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;retries = 0
concurrency = 1
redirects = explicit
connection_reuse = documented
session_id = recorded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Capture proxy connections and HTTP attempts separately. If the zero-retry test passes but production does not, investigate retry amplification and session reuse before blaming the location control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test rotation and stickiness separately
&lt;/h2&gt;

&lt;p&gt;For rotating inventory:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;create a new session identifier;&lt;/li&gt;
&lt;li&gt;make one lookup and one authorized destination request;&lt;/li&gt;
&lt;li&gt;close the connection;&lt;/li&gt;
&lt;li&gt;wait for the documented rotation boundary;&lt;/li&gt;
&lt;li&gt;repeat without reusing browser state.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For sticky inventory, retain the identifier and sample over the promised lifetime. Record the exact moment the exit changes or authentication fails.&lt;/p&gt;

&lt;p&gt;An accurate exit that cannot remain stable still fails a sticky-session requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculate nested accuracy
&lt;/h2&gt;

&lt;p&gt;Country, region, and city matches are not independent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;country_rate = country_matches / successful_observations
region_rate  = region_matches / country_matches
city_rate    = city_matches / city_targeted_observations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also calculate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;median and P95 distance from the requested metro center;&lt;/li&gt;
&lt;li&gt;expected network-classification rate;&lt;/li&gt;
&lt;li&gt;destination acceptance rate;&lt;/li&gt;
&lt;li&gt;sticky retention rate;&lt;/li&gt;
&lt;li&gt;unexpected exit-change rate;&lt;/li&gt;
&lt;li&gt;repeat-exit rate;&lt;/li&gt;
&lt;li&gt;ASN and prefix concentration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Always report the denominator, markets, time window, address family, and match definition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Classify disagreement patterns
&lt;/h2&gt;

&lt;p&gt;Useful categories include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one stale dataset;&lt;/li&gt;
&lt;li&gt;administrative-boundary mismatch;&lt;/li&gt;
&lt;li&gt;carrier aggregation with stable country but unstable city;&lt;/li&gt;
&lt;li&gt;recent prefix reassignment;&lt;/li&gt;
&lt;li&gt;gateway or session control defect;&lt;/li&gt;
&lt;li&gt;IPv4/IPv6 location split.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This classification turns “the databases disagree” into a retest or escalation plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the gate executable
&lt;/h2&gt;

&lt;p&gt;A machine-readable gate could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;country_match_min&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.99&lt;/span&gt;
&lt;span class="na"&gt;city_distance_p95_km_max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
&lt;span class="na"&gt;sticky_cross_country_changes_max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="na"&gt;expected_asn_share_min&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.90&lt;/span&gt;
&lt;span class="na"&gt;time_windows_min&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="na"&gt;result_states&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pass&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;conditional_pass&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fail&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;inconclusive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual thresholds depend on the product and workload. The important part is defining them before reviewing results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security and compliance
&lt;/h2&gt;

&lt;p&gt;Hash or truncate exit identifiers when full IP retention is unnecessary. Never store proxy passwords, authorization headers, cookies, or raw session tokens in the evidence bundle. Test only authorized destinations and markets, honor rate limits, and do not use location controls to bypass access restrictions or misrepresent identity.&lt;/p&gt;

&lt;p&gt;For authorized global proxy testing, 98IP is available at &lt;a href="https://en.98ip.com/?k=dev" rel="noopener noreferrer"&gt;https://en.98ip.com/?k=dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
    </item>
    <item>
      <title>Stop Rotating Proxies Until You Classify the Failure</title>
      <dc:creator>98IP Proxy</dc:creator>
      <pubDate>Tue, 01 Sep 2026 15:34:21 +0000</pubDate>
      <link>https://dev.to/98ip/stop-rotating-proxies-until-you-classify-the-failure-nlo</link>
      <guid>https://dev.to/98ip/stop-rotating-proxies-until-you-classify-the-failure-nlo</guid>
      <description>&lt;p&gt;Cloudflare’s new Adaptive Intelligence announcement highlights a reality crawler operators already feel: bot defenses change faster, and a route that succeeded yesterday may be evaluated differently today.&lt;/p&gt;

&lt;p&gt;The dangerous response is to rotate exits immediately. That removes the evidence you need to identify the real layer of failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a failure taxonomy
&lt;/h2&gt;

&lt;p&gt;Classify the event before changing transport:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Typical evidence&lt;/th&gt;
&lt;th&gt;First response&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Network&lt;/td&gt;
&lt;td&gt;timeout, reset, route-specific latency&lt;/td&gt;
&lt;td&gt;compare one known route&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authentication&lt;/td&gt;
&lt;td&gt;expired session, invalid credential state&lt;/td&gt;
&lt;td&gt;refresh through the approved flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application&lt;/td&gt;
&lt;td&gt;schema or page-state change&lt;/td&gt;
&lt;td&gt;validate the parser or workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate&lt;/td&gt;
&lt;td&gt;explicit limit, rising challenge rate&lt;/td&gt;
&lt;td&gt;stop, back off, reduce concurrency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policy&lt;/td&gt;
&lt;td&gt;robots, terms, or access denial&lt;/td&gt;
&lt;td&gt;stop and review authorization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content&lt;/td&gt;
&lt;td&gt;valid response but unusable data&lt;/td&gt;
&lt;td&gt;fix quality checks, not routing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Bound the retry controller
&lt;/h2&gt;

&lt;p&gt;A task should have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exponential backoff with jitter;&lt;/li&gt;
&lt;li&gt;a maximum attempt count;&lt;/li&gt;
&lt;li&gt;a total elapsed-time budget;&lt;/li&gt;
&lt;li&gt;response classes that are never retried;&lt;/li&gt;
&lt;li&gt;a circuit breaker scoped to the target;&lt;/li&gt;
&lt;li&gt;observability by identity, gateway, ASN, region, and session.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core metric is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;retry_amplification = total_attempts / original_tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If 1,000 tasks create 3,800 attempts, the problem is not merely a 26% failure rate. The recovery controller has amplified the workload 3.8 times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Canary before scale
&lt;/h2&gt;

&lt;p&gt;For every planned route:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;use a small approved URL set;&lt;/li&gt;
&lt;li&gt;hold identity and session policy constant;&lt;/li&gt;
&lt;li&gt;measure useful-result rate, challenges, latency, bytes, and cost;&lt;/li&gt;
&lt;li&gt;stop on material deviation;&lt;/li&gt;
&lt;li&gt;change one variable at a time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not optimize for HTTP 200 alone. A route that returns stale, duplicate, wrong-region, or unauthorized results is not healthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identity and transport are different control planes
&lt;/h2&gt;

&lt;p&gt;A proxy chooses where a connection exits. It does not explain who operates the crawler or whether the target permits the use. Keep a stable, truthful identity wherever automation is allowed. Maintain an authorization register for each target. Store references to protected secrets, never credentials in telemetry.&lt;/p&gt;

&lt;p&gt;Residential proxies can support authorized regional testing and session isolation. They are not a bypass mechanism. If a target denies the activity, rotating an exit does not create permission.&lt;/p&gt;

&lt;p&gt;Disclosure: I work with 98IP. This post is about responsible proxy operations, not evasion. More engineering resources: &lt;a href="https://en.98ip.com/?k=dev" rel="noopener noreferrer"&gt;https://en.98ip.com/?k=dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>observability</category>
    </item>
    <item>
      <title>Treat Crawler Identity as a Deployable Control Plane</title>
      <dc:creator>98IP Proxy</dc:creator>
      <pubDate>Mon, 31 Aug 2026 06:19:51 +0000</pubDate>
      <link>https://dev.to/98ip/treat-crawler-identity-as-a-deployable-control-plane-1008</link>
      <guid>https://dev.to/98ip/treat-crawler-identity-as-a-deployable-control-plane-1008</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I work with 98IP. This post is an engineering guide, not a claim that verification grants permission to crawl any site.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A crawler does not have one identity. It has a bundle of assertions: a user agent, an operator, an IP range, a reverse-DNS pattern, a purpose, and a contact path. When those assertions are maintained in different tickets and repositories, they drift. The result is a bot that is legitimate internally but indistinguishable from an impersonator at the edge.&lt;/p&gt;

&lt;p&gt;The fix is to manage crawler identity like a deployable control plane.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with an identity manifest
&lt;/h2&gt;

&lt;p&gt;Keep the public assertions in a versioned manifest owned by the same team that ships the crawler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;schema_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;bot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;catalog-observer&lt;/span&gt;
  &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example-operator&lt;/span&gt;
  &lt;span class="na"&gt;purpose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;market-research&lt;/span&gt;
  &lt;span class="na"&gt;contact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crawler-ops@example.invalid&lt;/span&gt;
&lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;user_agents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CatalogObserver/3.4"&lt;/span&gt;
  &lt;span class="na"&gt;ip_sets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;production-egress-v2&lt;/span&gt;
  &lt;span class="na"&gt;reverse_dns_suffix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crawler.example.invalid&lt;/span&gt;
&lt;span class="na"&gt;authentication&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web_bot_auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;required&lt;/span&gt;
&lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;robots_txt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;enforce&lt;/span&gt;
  &lt;span class="na"&gt;default_rate_per_host_per_minute&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;
  &lt;span class="na"&gt;stop_on_401_403_429&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not put secrets in this file. It should describe claims that a destination can verify, not credentials that let someone impersonate the crawler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate identity before traffic leaves
&lt;/h2&gt;

&lt;p&gt;Add four checks to the release pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User-agent check:&lt;/strong&gt; the runtime string must exactly match the manifest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Egress check:&lt;/strong&gt; every production address must belong to the declared set, and stale addresses must be removed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS check:&lt;/strong&gt; forward and reverse resolution must agree where reverse DNS is part of the identity contract.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptographic check:&lt;/strong&gt; if Web Bot Auth or another signed mechanism is used, verify clock skew, key rotation, and failure behavior in staging.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These tests should fail closed. A release that changes an identity surface without updating the manifest should not ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observe the verifier's view
&lt;/h2&gt;

&lt;p&gt;Internal health checks are insufficient. Run a small canary from each production egress region and record what an external verifier sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eu-west"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"observed_user_agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CatalogObserver/3.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"forward_reverse_dns_consistent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ip_set_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"production-egress-v2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auth_signature_valid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"policy_response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alert on mismatches, not merely on request failure. A &lt;code&gt;200&lt;/code&gt; from one destination does not prove that the crawler is consistently identifiable elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate verification from permission
&lt;/h2&gt;

&lt;p&gt;Verification answers “is this traffic really from the stated operator?” Permission answers “does this site allow this activity?” They are different controls.&lt;/p&gt;

&lt;p&gt;Before collecting data, evaluate the destination's terms, robots instructions, authentication boundaries, applicable law, and rate expectations. Stop or reduce traffic on explicit denial and on repeated &lt;code&gt;401&lt;/code&gt;, &lt;code&gt;403&lt;/code&gt;, or &lt;code&gt;429&lt;/code&gt; responses. A directory listing or verified badge must never become a bypass rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roll changes like network changes
&lt;/h2&gt;

&lt;p&gt;Identity changes deserve staged rollout:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;publish the new assertions first;&lt;/li&gt;
&lt;li&gt;wait for DNS and directory propagation;&lt;/li&gt;
&lt;li&gt;canary from one egress pool;&lt;/li&gt;
&lt;li&gt;compare denial rate, authentication failures, and challenge rate;&lt;/li&gt;
&lt;li&gt;expand only when the verifier's view matches the manifest;&lt;/li&gt;
&lt;li&gt;retain a rollback path for the previous identity version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The useful metrics are identity mismatch rate, verified-request ratio, policy-denial rate, stale-IP count, and time from operator change to external consistency. Raw request volume is not an identity metric.&lt;/p&gt;

&lt;h2&gt;
  
  
  Release checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Manifest reviewed by crawler and security owners&lt;/li&gt;
&lt;li&gt;[ ] User agent and egress sets match production&lt;/li&gt;
&lt;li&gt;[ ] Reverse DNS passes forward-confirmed checks&lt;/li&gt;
&lt;li&gt;[ ] Signing keys rotate without overlap gaps&lt;/li&gt;
&lt;li&gt;[ ] Contact and purpose remain accurate&lt;/li&gt;
&lt;li&gt;[ ] Destination permission checks still run independently&lt;/li&gt;
&lt;li&gt;[ ] Canary evidence is stored with the release&lt;/li&gt;
&lt;li&gt;[ ] Rollback version is known&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Crawler identity becomes reliable when it is testable, observable, and reversible. Treating it as a control plane reduces accidental blocking and makes impersonation easier to detect—without weakening a site owner's control.&lt;/p&gt;

&lt;p&gt;I work with 98IP, where we build proxy infrastructure and operational guidance for compliant data workflows: &lt;a href="https://en.98ip.com/?k=dev" rel="noopener noreferrer"&gt;https://en.98ip.com/?k=dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI-assisted disclosure: AI supported drafting and editing; the operational model and final review were completed for this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>automation</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
