OP-Proxy
PricingBlogResellerAPI
Dashboard
← Blog
Troubleshooting·August 17, 2026·4 min read

The IP is not changing on rotation: keep-alive is the cause, not the proxy

The most common complaint about a rotating proxy goes like this: “rotation is per request, yet the address is always the same.” In the overwhelming majority of cases the proxy is working correctly and the address stays put because of how your HTTP client reuses connections. It is a one-line fix, but the mechanism is worth understanding first — otherwise the fix looks like superstition.

In this article

  • When exactly the exit address is chosen
  • requests: a Session reuses connections
  • httpx: empty the idle pool
  • aiohttp: force_close and the reuse timeout
  • What is not the cause
  • Why this cannot be fixed on the proxy side
  • When there is nothing to fix

When exactly the exit address is chosen

The key point: with per-request rotation the exit is chosen when the TCP connection is established — and for HTTPS, when the CONNECT tunnel is opened. Not when you write a request into that connection. After that the tunnel is open, and everything sent through it leaves from the same address.

The consequence: a client that keeps a connection pool reuses one exit for as long as that connection lives in the pool. From the outside this looks exactly like broken rotation, although neither side did anything wrong.

With HTTPS this is especially visible. The client sends the proxy a CONNECT command once, the proxy opens a tunnel to the target host, and from then on an encrypted stream runs inside that tunnel, which the proxy no longer touches. It physically cannot swap the exit address mid-tunnel — not because it will not, but because by that point the tunnel is established and bound to a specific exit.

The check that settles it in a minute: hit an IP-echo endpoint ten times in one session, then ten times in ten fresh sessions. If the addresses vary only in the second case, the cause is connection pooling, not the proxy.

requests: a Session reuses connections

A Session keeps a pool through HTTPAdapter, and in ordinary work that is its main advantage. To get a new exit you either stop reusing the connection or stop reusing the session.

python
import requests

proxies = {"http": PROXY, "https": PROXY}

# Option 1: ask for the connection to be closed after the response
with requests.Session() as s:
    s.headers["Connection"] = "close"
    for url in urls:
        s.get(url, proxies=proxies)

# Option 2: a new session means a new tunnel and a new exit
for url in urls:
    with requests.Session() as s:
        s.get(url, proxies=proxies)

httpx: empty the idle pool

In httpx the pool limits live in a Limits object. Setting max_keepalive_connections=0 means no idle connection is retained, so the next request opens a new tunnel. When a proxy is configured the limits apply to the proxy's connection pool — which is exactly where you want them.

python
import httpx

# max_keepalive_connections=0 -> nothing is kept in reserve
limits = httpx.Limits(max_keepalive_connections=0)

with httpx.Client(proxy=PROXY, limits=limits) as client:
    for url in urls:
        client.get(url)

aiohttp: force_close and the reuse timeout

TCPConnector has a force_close parameter, documented as “close underlying sockets after connection releasing”; it defaults to False. Next to it sits keepalive_timeout at 15 seconds — how long a connection waits to be reused after being released. Those fifteen seconds are why an async scraper holds one address in bursts.

python
import aiohttp

# force_close=True: the socket closes as soon as the connection is released,
# so the next request opens a new tunnel and gets a new exit.
connector = aiohttp.TCPConnector(force_close=True)

async with aiohttp.ClientSession(connector=connector) as session:
    async with session.get(url, proxy=PROXY,
                           proxy_auth=aiohttp.BasicAuth(LOGIN, PASSWORD)) as r:
        await r.text()

What is not the cause

  • Pool size: the address is not staying put because there are few of them, but because no new connection is being opened.
  • The rotation setting on our side: it applies when a connection is established and cannot affect an already-open tunnel — by the design of the protocol.
  • DNS caching: it decides which gateway you connect to, not which exit the gateway supplies.
  • HTTP/2: here it makes things worse, not better — multiplexing exists precisely to keep one connection open.

Why this cannot be fixed on the proxy side

People sometimes ask us to “make the address change anyway”. No provider can: swapping the exit in the middle of an established TCP connection would mean tearing down the connection, losing the TLS session and aborting the request. What looks to you like one logical visit to a site is, to the network, a stateful connection — and the address is part of that state.

So the fix always lives on the client side: ask it to open a new connection. That is not a workaround or a hack — it is the only place where such a decision is made at all.

When there is nothing to fix

It is worth stopping to ask why you want a new address per request. If the job is a chain of steps inside one session — signing in, cookie-based pagination, a multi-step form — then a stable address helps you rather than hinders. That is what timed rotation is for: the address is held for a set interval deliberately, not as a side effect of a connection pool.

The difference matters: a pool gives you a stable address by accident and for an unpredictable stretch; a timer gives it to you on purpose and for a known one. If you need stability, it is safer to ask for it explicitly.

Per-request rotation or timed

Both modes are available on rotating plans and switch in the dashboard. IPv6 from 650 ₽ for 50 threads, IPv4 from 1375 ₽ for 100.

View pricing

Proxies for this job

  • Rotating IPv6 and IPv4
  • For parsing and scraping
  • For multi-threaded software

Check it with our tools

  • My IP address

Read next

  • Per-request or timed rotation: which to chooseThe two modes solve different problems. Where per-request rotation breaks things, and how to work out the interval you need.
  • How to use proxies in Python: requests, Scrapy and SeleniumWorking examples for the three most common cases, the difference between socks5 and socks5h, and why IP authentication is easier in a browser.
  • One gateway instead of a proxy list: configuring software that expects a listScrapers measure scale by proxy count, and a rotating gateway is one line. Program by program: which mode to switch, where to duplicate the line, and where the honest answer is to buy a list.
OP-Proxy
Plans & Information
PricingBlogReseller programAPITerms of ServicePrivacy Policy
Proxies by use case
For parsing and scrapingFor multi-threaded softwareFor marketplacesFor antidetect browsersFor captcha softwareFor SEO and SERP checksRotating IPv6 and IPv4IPv6 proxiesStatic datacenter IPv4
Tools
My IP AddressSpeed TestAnonymity CheckWHOIS LookupDNS Leak TestWebsite Check
ИП Артамонов Анатолий Михайлович ОГРНИП 324700000026212 ИНН 701755408691