Scraping Central is reader-supported. When you buy through links on our site, we may earn an affiliate commission.

4.28intermediate5 min read

Rotating Proxy Strategies (Per-Request, Per-Session, Sticky)

Three rotation strategies and when to use each. The mismatch between rotation and session state is the #1 source of bans.

What you’ll learn

  • Pick per-request, per-session, or sticky rotation for a given target.
  • Implement each in Python and PHP.
  • Recognize when rotation breaks logged-in flows.

Most providers expose rotation as a configuration knob. Three modes cover ~95% of use cases. Picking wrong is one of the easiest ways to get banned.

The three modes

Mode What happens Best for
Per-request New IP every request Anonymous browsing, listing pages, no state
Per-session (sticky for N minutes) Same IP for the duration of a logical session Logged-in flows, multi-step navigation
Sticky / dedicated Same IP for hours/days Account-bound scraping, captcha-walled sessions

Per-request rotation

The default for most residential providers. Every request goes through a different IP.

import httpx

client = httpx.Client(
  proxies="http://user:pass@gw.provider.com:7777"  # rotating gateway
)
for url in urls:
  r = client.get(url)  # each request gets a fresh IP automatically

When this works:

  • Anonymous public pages (listing, search, product detail).
  • The target doesn't track state across requests.
  • High volume, broad coverage.

When it fails:

  • Logged-in scrapes, the session cookie says "this is user X" but the IP changes constantly. Most sites flag this as suspicious.
  • Multi-step forms, CSRF token issued on IP A; submit from IP B; rejection.
  • Cart/checkout flows, every step looks like a different user.

Per-session rotation

Same IP held for a configurable window (often 10–30 minutes), then released. Providers expose this as a "session" parameter in the proxy URL:

# Smartproxy-style sticky session
client = httpx.Client(
  proxies=f"http://user-session-abc123:pass@gw.provider.com:7777"
)

The session-abc123 token tells the gateway "give me the same IP for this session ID." Different session IDs get different IPs.

# Python: one session per worker
session_id = f"worker-{os.getpid()}"
proxy_url = f"http://user-session-{session_id}:{password}@gw.provider.com:7777"

When this works:

  • Logged-in scrapes (one session = one IP for the entire login flow).
  • Pagination through a search result set (same IP keeps "context").
  • Cart/checkout/account flows.

Coordinating session with cookies: every session ID should map 1:1 to a cookie jar. If you reuse a cookie jar but change the session ID (= IP), the target sees "same user, different IPs", back to the per-request problem.

Sticky / dedicated IPs

Some providers offer truly dedicated IPs, yours for the month. Common with ISP proxies and dedicated datacenter.

http://user:pass@123.45.67.89:7777  # always this IP

When this works:

  • Accounts that the target whitelists or whose history matters (e.g. you've built reputation on this IP).
  • Slow scrapes (one request every few minutes) where a single IP can't trigger volume bans.
  • Compliance scenarios where the target needs to know which IP belongs to you.

Risks: if the IP gets banned, your access stops. With rotating, a ban just means one IP out of millions retired.

Strategy by target type

Target Strategy
Anonymous catalogue scrape Per-request
Search results Per-session (one session per query thread)
Logged-in dashboard Per-session, locked to the same cookie jar
Account-bound API with key Sticky (one dedicated IP per account)
Crawling 100 different domains Per-request, randomized country

Implementing in code

Python with Scrapy

class ProxySessionMiddleware:
  """Map cookiejar -> session ID -> stable IP."""

  def __init__(self):
  self.proxy_template = "http://user-session-{sid}:pass@gw.provider.com:7777"

  def process_request(self, request, spider):
  cookiejar = request.meta.get("cookiejar", "default")
  sid = f"jar-{cookiejar}"
  request.meta["proxy"] = self.proxy_template.format(sid=sid)

One cookie jar = one session ID = one IP. Coherent.

Python with httpx

class StickyClient:
  def __init__(self, base_proxy, password):
  self.clients = {}
  self.base = base_proxy
  self.pw = password

  def for_session(self, session_id):
  if session_id not in self.clients:
  proxy = f"http://user-session-{session_id}:{self.pw}@{self.base}"
  self.clients[session_id] = httpx.Client(proxies=proxy)
  return self.clients[session_id]

sticky = StickyClient("gw.provider.com:7777", "PASSWORD")
client_a = sticky.for_session("user-a")
client_b = sticky.for_session("user-b")
# client_a always gets the same IP; client_b a different consistent IP

PHP / Symfony

$client = HttpClient::create([
  'proxy' => "http://user-session-$sessionId:$password@gw.provider.com:7777",
]);

For scoped per-session clients, instantiate one HttpClient per session ID. Each gets its own connection pool and IP.

Common mistakes

Rotating IPs across cookie state

Symptom: logged-in scrape works for 5 requests then logs out. Cause: cookie says "user X" but IP keeps changing. Fix: use per-session rotation, pin one IP per session.

Sharing sessions across threads

If two threads use the same session ID, they share the IP, fine. But if they also share the cookie jar and one logs out, both lose the session. Either: separate session per thread, or careful coordination of shared state.

Forgetting to rotate the session ID

After enough time on one IP, you accumulate enough request history to look bot-like even on a residential IP. Rotate session IDs every N requests or N minutes to refresh.

# Rotate session every 100 requests
counter = 0
session_id = f"jar-{cookiejar}-{counter // 100}"

Geographic stickiness

When using per-request rotation, you may inadvertently bounce between countries. Most providers let you constrain region:

http://user-country-de-session-abc:pass@gw.provider.com:7777

All Germany-based IPs, all session abc. Bouncing countries mid-scrape can break geo-locked content or trigger anti-fraud rules.

Costs

Per-request rotation usually has no premium. Per-session is sometimes free, sometimes premium. Sticky/dedicated almost always premium. Match the strategy to actual need; don't pay for stickiness when per-request would do.

Hands-on lab

Pick a target with login:

  1. Implement a logged-in scrape with per-request rotation. Watch it fail or log out partway through.
  2. Switch to per-session rotation, mapping session ID to cookie jar. Watch it succeed.
  3. Add session-ID rotation every 50 requests. Confirm continued success.

The lesson lands when you've seen rotation work and fail. After that, picking the strategy becomes intuitive.

Quiz, check your understanding

Pass mark is 70%. Pick the best answer; you’ll see the explanation right after.

Rotating Proxy Strategies (Per-Request, Per-Session, Sticky)1 / 8

A logged-in scraper uses per-request proxy rotation. It logs in successfully but is logged out after a few requests. Most likely cause?

Score so far: 0 / 0