-
Notifications
You must be signed in to change notification settings - Fork 0
Sticky Sessions and Rotation
The single most misunderstood part of mobile proxies. Read this before answering your first "the IP keeps changing" support ticket.
There are two completely separate layers of IP change at play. Mixing them up causes every conversation about sticky sessions to go in circles. This page draws the line clearly.
┌────────────────────────────────────────────────────────────────┐
│ LAYER 1 — Gateway routing │
│ "Which modem does my request go to?" │
│ Controlled by: rotation token (sticky / auto5 / auto20 / hard) │
│ Controlled by: us │
└────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ LAYER 2 — Carrier / ISP │
│ "What public IP does that modem expose right now?" │
│ Controlled by: T-Mobile / Verizon / EE / etc. (carrier CGNAT) │
│ Controlled by: NOT us │
└────────────────────────────────────────────────────────────────┘
When a customer says "sticky isn't sticky, the IP changed", what they almost always mean is Layer 2 changed. Layer 1 did its job — they kept talking to the same modem the whole time — but the carrier rotated that modem's public IP out from under us.
The username DSL has these rotation modes:
| Token | What the gateway does |
|---|---|
rot-sticky |
Pin every request with the same sid to the same modem for the session |
rot-sticky-strict |
Like sticky, but on failover re-pins only within the same node instead of re-picking freely (SDK 0.7.0+) |
rot-auto5 / auto10 / auto20 / auto60
|
Pin to one modem for N minutes, then soft-rotate you to a new one. auto10 is the default when no -rot- token is given (auto30 is a legacy alias that snaps to auto20) |
rot-ondemand |
New endpoint on a new connection (no auto-interval; reusing the same sid keeps the pin) |
rot-hard |
Pins like sticky. It does NOT pick a new modem per request. (A true carrier-IP reset only happens via the explicit /rotate action, and isn't available for residential peers.) |
⚠️ stickyandauto*need asid. Stickiness lives in the session, and a session is named by itssid. Without a-sid-token, every connection starts a fresh, un-referenceable session — so there is no cross-connection stickiness, even with-rot-sticky. Always pass-sid-<name>when you want a connection to "stick".
The sid-<id> token names your session ("port name"). Different sid values get different modems; the same sid repeated keeps you on the same modem (in sticky/auto modes). Drop the sid and each connection is independent. Format: lowercase letters, digits, and underscores, 1-64 chars - no dashes, since the dash delimits tokens. The SDK's buildProxyUrl validates this since 0.8.1 (short sids like t1 are fine; the old 8-char minimum was removed).
Example:
# Two terminals using the SAME sid will share a modem
curl -x 'http://psx_xxx-mbl-us-sid-job1-rot-sticky:pak_xxx@gw.proxies.sx:7000' https://api.ipify.org
curl -x 'http://psx_xxx-mbl-us-sid-job1-rot-sticky:pak_xxx@gw.proxies.sx:7000' https://api.ipify.org
# A different sid starts on a different modem
curl -x 'http://psx_xxx-mbl-us-sid-job2-rot-sticky:pak_xxx@gw.proxies.sx:7000' https://api.ipify.orgThis is the only part we control directly.
Every mobile carrier in the world puts millions of devices behind a shared NAT pool — Carrier-Grade NAT, or CGNAT. Your modem doesn't actually own a unique public IPv4. It borrows one from the carrier's pool. The carrier reassigns IPs out of that pool whenever it wants — sometimes per-second on busy networks, sometimes per-hour on calm ones, sometimes per-TCP-flow.
T-Mobile US, for example, has a ~50K-address /16 pool (172.56.0.0/16 is one of theirs). A single modem sitting still on a desk will see its public IP cycle through dozens of those addresses over a single afternoon. The modem isn't moving. The IP is being recycled around the modem by the carrier.
We can pin you to a modem (Layer 1). We cannot pin the carrier (Layer 2).
t= 0s 172.56.169.18 ←─┐
t= 12s 172.56.168.58 │ Same modem
t= 23s 172.56.168.166 │ (gateway sticky worked)
t= 35s 172.56.168.224 │
… │ Carrier rotated the IP
t=275s 172.56.168.148 │ 24+ times during 5 minutes
t=287s 172.56.168.148 │
t=298s 172.56.168.148 │
… │
t=344s 172.56.169.44 ←─┘
27 unique IPs in 31 samples. All in the same T-Mobile /23. The session never hopped modems — it just hopped IPs inside the carrier's CGNAT pool. That's normal mobile carrier behavior.
Most use cases don't care about per-second IP changes:
- Scraping where each request is independent → fine, even prefer rotation
- Account warming on a single service → service tracks you by cookie, not IP
- Geo-checking where you only need country accuracy → fine
- General-purpose anti-block where any mobile IP works → fine
A small number of workflows fail catastrophically if the IP shifts even once:
-
cf_clearancecookies (Cloudflare) — the cookie is bound to the IP that solved the challenge. CapSolver needs ~10–30s to solve, then your browser must reload using the same IP, or the cookie is rejected. - Banking / fintech sessions — many bind the session token to the source IP and 401 you on any change.
- Single-IP TLS / mTLS to a peer — if you re-handshake from a different IP, the peer might revoke.
- TCP-level sticky load balancers on the target side — they fingerprint by IP.
For these, sticky-on-shared-mobile isn't enough. You need an IP-stable solution.
You have three options, ordered by what most people want first:
http://psx_xxx-peer-us-sid-job1-rot-sticky:pak_xxx@gw.proxies.sx:7000
The peer pool is the community network - largely residential ISP connections (real Comcast / Spectrum / Deutsche Telekom subscribers), with some mobile peers mixed in. Residential ISPs change WAN IPs much less aggressively than mobile carriers — typically hours to days, sometimes never until reboot. Sticky + peer + a session of < 1 hour is IP-stable in practice ~95% of the time.
Trade-off: peer pool quality is more variable than mobile — it's a community network growing daily. Use for IP stability when you can tolerate occasional slower endpoints. Since SDK 0.8.x you can also hard-target a specific carrier inside the pool with the asn / isp options on buildProxyUrl (check availability first with pool.getCarrierStock()).
Ask us to provision a dedicated modem with rotation disabled at the modem level. This is the only way to get a mobile IP that doesn't shift mid-session. The modem holds a single carrier IP for as long as the carrier allows (often 24h+ on the same TCP flow).
How to request:
- Email us with your country + carrier preference + expected concurrency + estimated daily GB
- We provision a modem with
rotation=offand assign it a highMAXCONN - You get a dedicated
host:port:user:pass(not the shared gateway) - You charge a higher price to your customer — typically 2–4× the shared rate
This is the solution for cf_clearance, mTLS, and IP-bound banking workflows. There is no cheaper way to get an IP-stable mobile exit.
If you're using shared sticky mobile and your workflow can tolerate occasional re-solves (e.g. you have unlimited CapSolver budget), have your code:
- Record
currentIpafter every page load (usehttps://api.ipify.org) - If
currentIpchanged since the last load, drop your cookie and re-solve - Otherwise continue
This is operationally fine but expensive. Most resellers prefer Option 2 for any volume workload.
When a customer asks "should I expect IP stability on sticky shared mobile?":
| Time window | Typical IP-stable probability |
|---|---|
| Single request (< 1s) | ~100% (IP doesn't shift mid-request) |
| 10 seconds | ~50% on T-Mobile, ~80% on Verizon, varies by carrier |
| 60 seconds | ~20% |
| 5 minutes | < 5% |
| 1 hour | ~0% on any major US carrier |
These are empirical and carrier-dependent. EU carriers (EE UK, Vodafone DE, Orange FR) tend to hold IPs longer than US carriers in our experience.
If your customer's workflow needs more than ~10 seconds of IP stability, point them at Option 1 or Option 2 above. Don't try to make shared sticky do something it can't.
There is a separate, real bug class where Layer 1 itself fails — the gateway round-robins you across different modems even with rot-sticky. You can detect it by:
- Running 30+ requests over 5 minutes with the same
sid - Inspecting the IPs (
https://api.ipify.org) -
Look at the carrier
/24(first 3 octets) — if every IP is in the same/23or/24, that's one modem with carrier CGNAT (normal). If you see IPs from totally different carrier ranges (different/16s, different ASNs), that's the gateway hopping modems.
If you see modem-hopping (different ASNs / carrier ranges) with rot-sticky, file an issue at the main repo with:
- The full proxy URL you used (redact the pak)
- The list of IPs you observed
- The timestamp range
We treat this as a P0.
For full transparency, here's what the gateway does on every request that carries a sid:
- Look up the pinned modem in Redis.
- If the modem is currently
online→ reuse it. Outcome:reused. - If the modem is currently
offlinebut was last seen within the last 60s and rotation isstickyorhard→ reuse it anyway (probe blip tolerance). Outcome:reused_blip. - Else → pick a fresh modem and update the pin. Outcomes:
dropped_offlineordropped_missing.
The 60s grace window was added because the gateway's pool-scanner runs */2 minutes and can briefly flip a modem to offline on a single missed probe. Before the grace was added, a customer arriving during that ~1s window would silently lose their sticky pin.
Operators can watch the prom metric gateway_sticky_outcome_total{rotation="sticky",outcome} to see Layer 1 health at fleet scale. If dropped_* ÷ (reused + reused_blip) exceeds ~5% it indicates a real Layer 1 problem worth investigating; below 5% means the gateway is doing its job and any customer complaint about IP changes is Layer 2 (carrier CGNAT — Options 1/2/3 above are the answer).
When picking a modem for a sticky/hard session (no existing pin yet), the gateway now biases toward modems whose carrier holds the IP best:
- The pool-scanner records every observed IP change per modem (sliding 24h sorted set).
- Each scan,
ipStabilityScore(0–100) is computed and written to the modem's Redis hash: 100 = IP held across all polls this hour, 0 = IP changed on every poll. - The Lua selector's scoring formula is rotation-aware:
- For
sticky/hard:score = load*0.3 + health*0.2 + stability*0.5 - For
auto*/ondemand:score = load*0.8 + health*0.2(legacy — soft rotation wants diversity)
- For
A T-Mobile US modem that's been rotating IPs every poll scores ~0 on stability and effectively only gets picked for sticky when nothing better is available. A Verizon line that's held a stable IP for an hour scores ~100 and wins decisively. Customers don't change anything — sticky just routes to better modems automatically.
This is Phase 1 — the signal is coarse (2-min poll cadence means we can't distinguish modems that rotate every 8s from modems that rotate every 30s). Phase 2 will add a higher-frequency IP probe for finer-grained stability scoring. Phase 3 is procuring SIM-side static-IP modems for premium tiers.
Tightened from maxCustomers=3 to maxCustomers=2 per modem (and maxConnections=6 → 3). Why: with the old cap, three different sticky customers could pile up on the same modem and (a) share its single CGNAT IP pool which kills the IP diversity those customers came to mobile proxies for, and (b) compete for the modem's uplink bandwidth. The tighter cap spreads sticky load across more modems so each customer has more of "their own" modem.
Returning customers (same pakKeyId, multiple parallel sockets) bypass the cap — only NEW customers count toward the HLEN check.
| If the IPs are… | Then… |
|---|---|
Always in the same /24 (same carrier subnet) |
Sticky is working — carrier is rotating IP inside its CGNAT |
Same ASN but different /16s |
Carrier is using a wider pool — still Layer 2, still normal |
| Different ASNs (e.g. T-Mobile then AT&T then Verizon) | Gateway sticky bug — report it |
| You need IP stability over > 10s on mobile | Use peer pool or dedicated port — not shared sticky |
- Pak-Key-Lifecycle — how paks behave on top of sticky sessions
- Troubleshooting — full error catalog including session edge cases
- Glossary — formal definitions of sticky/sticky-strict/auto*/hard
Wiki for bolivian-peru/proxy-reseller-kit · Built by Proxies.sx · Edit on GitHub