DEV Community

RoamProxy
RoamProxy

Posted on

How Long Should You Hold a Sticky Session?

The last post argued that a residential IP has to behave like a household — cap the concurrency, pace the rate, jitter the spacing. A natural follow-up question landed in my inbox: "OK, so I'm holding one sticky session and pacing it politely. How long am I supposed to keep it?"

It's the right question, because both of the obvious answers are wrong.

Rotate every request, and you throw away everything a session accumulates: cookies issued against one IP, a TLS session the server has seen before, the slow-built trust of a consistent story. You also walk straight back into the geo-mismatch problem — a login minted in one country and replayed from another is exactly the "mixed-currency" signal I keep writing about.

Hold one session forever, and two clocks are ticking against you. The first is the behavior budget from the last post: every request you send accumulates on that IP, and the longer you hold it, the more implausible its "household" story gets. The second clock isn't yours at all.

The session can die without you

A residential exit IP is a real device on a real home connection. That device goes to sleep. Its DHCP lease renews. Someone reboots the router. When that happens, your "sticky" session doesn't politely tell you — depending on the provider, the session either dies or is quietly re-pinned to a different device.

In practice, most residential sticky sessions are only reliably stable on the order of minutes, not hours. Some survive much longer; you just can't build on it. If your pipeline assumes one IP for a two-hour crawl, you haven't designed a session strategy — you've bought a lottery ticket.

The corollary: verify, don't assume. A cheap periodic origin check (an httpbin-style echo through the same session) tells you whether the IP under your session silently changed. If it did, everything from the last post applies to a new IP now — and the cookies you're carrying were minted on the old one. That mismatch is worth knowing about before the target notices it for you.

Rotate between units of work, not inside them

The useful framing isn't a number of minutes. It's this:

One sticky session should live exactly as long as one unit of work — and rotate at the seam.

A unit of work is one coherent journey a single visitor could plausibly have: log in, walk a pagination trail, extract a batch, leave. Everything inside that journey shares state — cookies, referer chain, server-side session — so it must share an IP. Splitting it across exits is self-sabotage.

Between journeys, there's nothing connecting you. That's the free rotation point. You pay no continuity cost, and you reset the per-IP behavior budget to zero.

This immediately gives you sizing rules that are about your workload, not magic numbers:

  • If your unit of work is 30 seconds of requests, don't hold the session for 10 minutes "to be efficient." You're accumulating budget for nothing.
  • If your unit of work takes 40 minutes, that's longer than a residential session reliably lives. Don't reach for a longer TTL — shrink the unit of work. Checkpoint state so a journey can resume, or split the trail into chunks that each fit comfortably inside a session lifetime.
  • If your units of work are independent, run them on different concurrent sessions rather than queuing them through one. Five journeys through five IPs looks like five visitors. Five journeys back-to-back through one IP looks like a shift worker.

When to rotate early

Holding a session to the end of its unit of work is the default, not a vow. Three signals justify cutting it short:

1. An IP-scoped block. I wrote a whole post on diagnosing block scope; the short version is that rotation only fixes blocks that are actually pinned to the IP. If your probes say the IP itself is burned — fresh session, clean headers, still walled — finish the journey later on a new exit. Retrying through the burned one just documents your persistence.

2. The IP changed under you. Your origin check came back different. The sticky abstraction already broke; carrying on pretends it didn't. Restart the unit of work cleanly on the new session rather than dragging old-IP cookies onto a new-IP story.

3. Degradation, not denial. Responses through this session are getting slower, challenge pages more frequent, while a control request through a fresh session is fine. You're being softly bucketed. The polite exit is to finish the current page, not the current journey, and re-enter from a new IP with the pacing lessons applied.

Note what's not on the list: a timer. "Rotate every N minutes" is the sticky-session equivalent of metronomic request spacing — a rule that ignores what's actually happening. Every rotation decision above is driven by an observable, and all three observables are cheap to collect.

The shape of the code

None of this needs a framework. It's a small wrapper that owns three things — a session name, a birth-time origin check, and a rotate-at-seam method:

import time, uuid, httpx

GATE = "http://user-country-us-session-{sid}:pass@gateway:7777"

class StickyUnit:
    def __init__(self):
        self.sid = uuid.uuid4().hex[:8]
        self.client = httpx.Client(proxy=GATE.format(sid=self.sid))
        self.origin = self._origin()

    def _origin(self):
        return self.client.get("https://httpbin.org/ip", timeout=10).json()["origin"]

    def drifted(self):
        return self._origin() != self.origin

    def close(self):
        self.client.close()

def run_journey(urls):
    unit = StickyUnit()
    try:
        for i, url in enumerate(urls):
            if i and i % 10 == 0 and unit.drifted():
                raise RuntimeError("exit IP changed mid-journey; restart unit")
            yield unit.client.get(url)
    finally:
        unit.close()  # next journey constructs a fresh unit = fresh seam
Enter fullscreen mode Exit fullscreen mode

The session ID is random per unit of work, the drift check is periodic and cheap, and rotation isn't an event you schedule — it's just what happens between constructors.

Full runnable versions of this and the earlier posts' probes live in our examples repo: https://github.com/roamproxy/proxy-examples


I work on Roam, a pay-as-you-go proxy network. Sticky sessions there are named exactly like the snippet above — the session suffix pins the exit until you change it, which makes "rotate at the seam" a one-line decision.

Top comments (0)