Rotating vs Sticky Proxy Sessions: When to Rotate, When to Hold the IP
By Theo Marsh · Infrastructure & Proxies Writer · 2026-10-27 · 8 min read
Quick answer: Rotate when each request is independent (one-off price checks, SERP snapshots across cities). Go sticky when requests belong to one flow (a login, a checkout test, a multi-page journey). If the flow lasts longer than your provider's sticky limit, use a static ISP IP.
Disclosure: Zitto sells proxies. These patterns work with any provider.
The rule in one line
One logical user journey = one IP. Everything else follows from that.
How session control usually works
Most providers encode options in the proxy username:
user-<account>-country-de-city-berlin-session-a1b2c3-sesstime-10:<password>@gate.example:7000
- Same
session-value → same exit IP until it expires - New value → new IP
- No session value → rotates on every request
When to rotate
- Independent GETs across many locations
- Spreading load from a high-volume monitoring job so you stay within each site's rate limits
- Geo sampling ("what does this page show in 50 cities?")
When to stick
- Login → navigate → act flows on your own staging app
- Checkout and localisation QA (currency, tax, consent banners)
- Anything with cookies, CSRF tokens or server-side sessions
When to use static ISP
- Sessions over ~30 minutes
- Allowlisting a fixed IP on your own firewall or a partner API
- Dashboards you keep open all day
Comparison
| Per-request rotation | Sticky session | Static ISP | |
|---|---|---|---|
| IP lifetime | 1 request | Minutes (plan-dependent) | Indefinite |
| Best for | Independent sampling | Multi-step flows | Long sessions, allowlists |
| Breaks if… | Flow needs state | Session expires mid-flow | Rarely |
| Billing | Usually per GB | Usually per GB | Usually per IP |
Gotchas
- Session expiry mid-test: set the session time longer than your slowest flow, or switch to static.
- Peer drop-off: residential exit devices can go offline, so sticky ≠ guaranteed. Handle reconnects.
- Parallelism: 20 parallel workers sharing one session ID all hit one IP. Give each worker its own ID.
- Polite rates: rotation isn't a licence to hammer a site. Respect robots.txt and the site's terms.
In practice: a Python pattern
import uuid, requests
def session_proxy(country="de"):
sid = uuid.uuid4.hex[:8]
url = f"http://user-ACC-country-{country}-session-{sid}:PASS@gate.example:7000"
return {"http": url, "https": url}
s = requests.Session; s.proxies = session_proxy
s.get("https://staging.yourapp.example/login") # same IP for the whole flow
Decision matrix by workload
| Workload | Session type | Session length | Why |
|---|---|---|---|
| SERP rank snapshot, 50 cities | Rotate per request | — | Each query is independent |
| Price check, 1 page per product | Rotate per request | — | Independent pages |
| Category crawl, paginated | Sticky | 5–10 min | Pagination cookies, consistent results |
| Staging login + 10-step flow | Sticky | ≥ 2× flow time | Session cookies, CSRF |
| Checkout/localisation QA | Sticky | 10–30 min | Cart state, geo consistency |
| Ad verification on a landing page | Sticky | 2–5 min | Redirect chains must share one IP |
| Admin dashboard all day | Static ISP | Indefinite | Stable allowlisted IP |
| Partner API with IP allowlist | Static ISP / DC | Indefinite | Fixed IP required |
The worker-pool pattern
For parallel jobs, give each worker its own session and recycle it after N requests or T minutes:
import uuid, time, requests
class Worker:
def __init__(self, country, max_reqs=50, max_age=600):
self.country, self.max_reqs, self.max_age = country, max_reqs, max_age
self._new
def _new(self):
sid = uuid.uuid4.hex[:8]
url = f"http://user-ACC-country-{self.country}-session-{sid}:PASS@gate.example:7000"
self.s = requests.Session; self.s.proxies = {"http": url, "https": url}
self.n, self.t0 = 0, time.time
def get(self, url, **kw):
if self.n >= self.max_reqs or time.time - self.t0 > self.max_age:
self._new
self.n += 1
r = self.s.get(url, timeout=30, **kw)
if r.status_code in (407, 502, 503):
self._new # exit died or auth issue: fresh session
return r
Why recycle? Residential exits go offline unpredictably. Planned rotation at safe points beats an unplanned IP change in the middle of a flow.
Handling a session that dies mid-flow
- Detect: connection errors, 502/503 from the gateway, or a sudden change in the exit IP (check with an echo endpoint).
- Decide: if the flow is idempotent, restart it on a new session. If not (e.g. checkout QA), mark the run as invalid. Never carry on half-way through on a new IP.
- Log: session ID, exit IP, step number. Patterns show up quickly (some cities or ASNs are less stable).
Measuring session stability yourself
Run 100 sticky sessions at your target length and ping an echo endpoint every 30 seconds:
| Metric | Healthy target (rule of thumb) |
|---|---|
| Sessions keeping the same IP the whole way | > 90% |
| Median time before an unplanned change | > your session length |
| Reconnect success | > 98% |
If you can't reach these, switch that workload to static ISP proxies.
Rate limiting and politeness
Rotation spreads load, but it doesn't change what's acceptable. For any target that isn't yours:
- Read and respect robots.txt and the terms of service
- Keep request rates modest and add jitter
- Identify yourself in the User-Agent where appropriate
- Prefer official APIs and data feeds
- Cache aggressively
For your own systems, add your proxy ranges or session pattern to your WAF allowlist so QA traffic isn't blocked by your own defences.
Cost implications
Sticky and rotating sessions usually cost the same per GB on residential plans. The costs that really differ:
- Retries from dead sessions use extra bandwidth
- Static ISP is per IP: cheap for one long session, expensive for wide geo-coverage
- Mobile sticky sessions may be limited by modem rotation intervals
Configuration recipes for common tools
curl
# Rotating (new IP per request)
curl -x "http://user-ACC-country-us:PASS@gate.example:7000" https://ipinfo.io/ip
# Sticky (same IP for the session ID)
curl -x "http://user-ACC-country-us-session-qa42:PASS@gate.example:7000" https://ipinfo.io/ip
Playwright (Node)
const browser = await chromium.launch({
proxy: { server: "http://gate.example:7000",
username: "user-ACC-country-de-session-run17", password: "PASS" }
});
One browser context = one session ID. Create a new context with a new ID for each test journey.
Scrapy
Set the proxy per request in request.meta["proxy"] and keep a session ID per spider "slot" so paginated crawls stay consistent.
Parameter syntax varies by provider.
Choosing session length
Use this formula as a starting point:
session length = (slowest expected flow duration × 2) + 1 minute
A login → checkout test that takes up to 3 minutes gets a 7-minute session. Too short breaks flows. Too long ties a single IP to unrelated jobs if you reuse IDs by mistake.
Observability: what to log
For every request:
- Timestamp, worker ID, session ID
- Exit IP (from a periodic echo check), country/city requested vs observed
- Status code, latency, bytes transferred
- Retry count
With these you can answer the important questions: which cities are unstable, how much bandwidth goes to retries, and whether a provider's pool got worse this week.
Mobile proxies: special rules
Mobile proxy "rotation" often means the modem reconnecting to the carrier to get a new IP, which takes seconds and may be rate-limited. Patterns:
- Rotation on demand: call an API endpoint to rotate between journeys
- Timed rotation: e.g. every 5–15 minutes
- Sticky by default: because carrier IPs are shared by many real users (CGNAT), a single mobile IP is less "unique" than a residential one
Design tests around rotation windows. Don't rotate in the middle of a journey.
Troubleshooting quick reference
| Symptom | Likely cause | Fix |
|---|---|---|
| Logged out mid-flow | IP changed | Sticky session; longer duration |
| 407 Proxy Auth Required | Wrong credentials/parameters | Check the username format |
| Wrong country | Pool shortage in that geo | Broaden to region or try later |
| Slow first byte | Distant exit or overloaded peer | City targeting closer to the target; new session |
| Many 403s from your own WAF | Your defences blocking QA | Allowlist test sessions |
Budgeting by session type
Different session types change cost in different ways:
| Model | Cost driver | Example estimate |
|---|---|---|
| Rotating residential (per GB) | Total bandwidth | 5 GB/day × 30 × price/GB |
| Sticky residential (per GB) | Bandwidth + retries from dropped sessions | Add 5–15% retry overhead (measure yours) |
| Static ISP (per IP) | Number of IPs × months | 10 IPs × monthly price |
| Mobile (per day/period) | Days of access, sometimes plus bandwidth | Zitto states from $0.60/day [CLIENT-PROVIDED: confirm bandwidth limits and terms] |
To compare a per-day mobile price with per-GB offers, estimate your daily GB, then work out each provider's cost for your workload. Don't compare headline units directly. Any "cheapest" claim needs dated competitor prices for the same workload.
<!-- deepened3 -->Bottom line
Rotate for independent samples, stick for flows, and go static for long sessions. Match the IP lifetime to the length of the journey.
FAQ
Sticky length? Plan-dependent, typically minutes. Static ISP keeps the IP indefinitely. Is per-request rotation more natural? No. Real users keep the same IP for a whole visit. How to control it? With a session ID in the username.
FAQ
- How long does a sticky session last?
- It depends on the provider and plan. Common options range from a few minutes to around 30 minutes or more. Static ISP proxies keep the IP indefinitely.
- Does rotating every request look more natural?
- No. A real user keeps the same IP throughout a visit. Rotating mid-flow breaks logins, carts and CSRF tokens on your own test sites.
- How do I control the session?
- Most providers put a session ID in the proxy username. Same ID = same IP until it expires.
