How to Rotate Squid Proxies Automatically on Ubuntu
Cover Image

To rotate Squid proxies automatically on Ubuntu, choose between mapping authenticated users to local outgoing IPs and distributing connections across parent proxies. For a predictable sequence, have your client alternate between known proxy identities. A single public exit address cannot become a rotating pool through configuration alone.
Quick choice: Use
tcp_outgoing_addressfor multiple addresses assigned to one server. Usecache_peer ... round-robinfor several upstream proxies. Neither changes the exit IP inside an already-established HTTPS tunnel.
This guide builds on the Ubuntu proxy server setup guide. It targets Ubuntu 24.04 LTS with Squid 6, an authenticated loopback listener, and SSH forwarding. The examples were checked against documentation but have not been deployment-tested. Use them for traffic you are authorized to send; rotation does not override a destination's access rules.
Prerequisites and a Baseline Test
Start with a working authenticated Squid proxy and at least two distinct egress addresses. For Method 1, your provider must assign routable addresses to the same VPS. For Method 2, each parent needs its own verified exit address and a trusted private or VPN path from the front proxy.
On the VPS, back up the configuration and inspect available addresses:
sudo cp /etc/squid/squid.conf /etc/squid/squid.conf.before-rotation.bak
ip addr show
squid -vOn your computer, keep the following tunnel open. Replace SSH_USER and YOUR_VPS_IP with your SSH account and server address:
ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:3128:127.0.0.1:3128 SSH_USER@YOUR_VPS_IPIn another local terminal, establish your baseline. Curl prompts for the proxy password; it is not embedded in the command:
curl --noproxy "" --fail --show-error --silent --max-time 20 --proxy http://127.0.0.1:3128 --proxy-user scraper1 https://api.ipify.orgThe returned address is the public exit seen by the test service. Keep proxy ports closed to the public internet. The SSH tunnel protects credentials on the client-to-VPS leg; requesting an HTTPS destination alone would not encrypt credentials sent to a remote plain-HTTP proxy.
Method 1: Map Each User to a Local Outgoing IP
The tcp_outgoing_address reference documents first-match address selection and an important limitation: client-dependent rules are incompatible with server-side persistent connection reuse. Disable that reuse for this configuration.
The example addresses below are documentation placeholders. Replace them with addresses assigned to your VPS and configured using your provider's network instructions. Simply adding an arbitrary address to an interface does not make it routable.
acl exit_user1 proxy_auth scraper1
acl exit_user2 proxy_auth scraper2
tcp_outgoing_address 203.0.113.10 exit_user1
tcp_outgoing_address 203.0.113.11 exit_user2
server_persistent_connections offAdd these directives to the working authenticated configuration. Authentication must already succeed in http_access before address selection runs. Preserve the existing safe-port and private-destination restrictions. If only these two accounts should use this instance, replace its final authenticated allow rule with http_access allow tunnel_clients exit_user1 and http_access allow tunnel_clients exit_user2, followed by http_access deny all. Here tunnel_clients is the loopback source ACL from the setup guide.
Create scraper2 with sudo htpasswd /etc/squid/passwd scraper2; omit -c so existing users remain in the file. Use this method on a direct-egress instance without Method 2's parent configuration. Parse before reloading:
sudo squid -k parse && sudo systemctl reload squidRun the baseline curl command once for each username. Each should report its assigned public exit. This is stable user-to-address mapping, not automatic alternation by itself. Method 3 supplies the client-side alternation.
Watch the network boundary: a cloud NAT gateway may translate both local addresses to the same public IP. IPv4 mappings also do not define an IPv6 source policy. The ipify endpoint used here checks IPv4; verify IPv6 separately if your production destinations support it. Disabling persistent connections can increase connection overhead, so measure it under your workload.
Method 2: Distribute Connections Across Parent Proxies
For addresses on separate servers, add parent proxies to a front Squid instance. In the cache_peer documentation, round-robin selects parents in rotation when ICP queries are absent. Set no-query explicitly:
cache_peer 10.0.0.2 parent 3128 0 no-query round-robin name=exit_a
cache_peer 10.0.0.3 parent 3128 0 no-query round-robin name=exit_b
cache_peer 10.0.0.4 parent 3128 0 no-query round-robin name=exit_c
never_direct allow allReplace these private addresses with reachable parent endpoints. Keep the front proxy's authenticated access controls. The never_direct directive prevents direct-origin fallback when requests must use parents; remove conflicting always_direct allow rules when using this all-parent design. If no eligible parent is reachable, expect a failure rather than silently using the front server's public IP.
Configure the parent listeners too. A proxy bound to 127.0.0.1 cannot receive connections from the front VPS. On the first parent, use http_port 10.0.0.2:3128 on its trusted private or VPN interface. Keep safe-port and destination deny rules, then use a source-restricted final permit block such as:
acl front_proxy src 10.0.0.1/32
http_access allow front_proxy
http_access deny allReplace the parent's previous permit block with this one, after its protective denies. Adjust addresses for each parent and allow inbound TCP 3128 only from the front proxy in host and provider firewalls. This example authorizes the front proxy by its trusted network address; it does not require or forward each client's password to the parents. Use a VPN for untrusted inter-server paths. If you instead require parent authentication, configure compatible peer credentials separately.
Round-robin balances eligible forwarding selections, not necessarily bytes or business-level requests. Existing CONNECT tunnels retain their selected parent; cached HTTP responses may require no upstream connection at all. A parent that still answers requests but receives a destination's 403 response is not the same as an unreachable parent. Track those outcomes in your application rather than expecting parent availability checks to recognize every site-specific block.
Method 3: Alternate Proxy Identities in the Client
An external ACL helper that prints the next IP is not a drop-in rotation interface. tcp_outgoing_address accepts fast ACLs, while an external ACL may require an asynchronous helper lookup. The external ACL protocol returns decisions and optional annotations, not an instruction to bind an arbitrary output IP. A helper-based design would need more machinery than the original idea suggests.
For a simple controlled sequence, use the verified user mappings from Method 1 and alternate credentials in the client. This Python example creates a fresh session for each request, so it does not reuse a previous HTTPS tunnel. Install Requests in a local virtual environment:
python3 -m venv .venv
.venv/bin/python -m pip install requestsSave this example as check_rotation.py. It prompts for passwords and reports only the chosen username and observed IPv4 address:
from collections import Counter
from getpass import getpass
from ipaddress import IPv4Address
from itertools import cycle
from urllib.parse import quote
import requests
users = ("scraper1", "scraper2")
passwords = {user: getpass(f"Proxy password for {user}: ") for user in users}
identities = cycle(users)
counts = Counter()
for request_number in range(1, 11):
user = next(identities)
password = quote(passwords[user], safe="")
proxy = f"http://{quote(user, safe='')}:{password}@127.0.0.1:3128"
try:
with requests.Session() as session:
session.trust_env = False
response = session.get(
"https://api.ipify.org",
proxies={"http": proxy, "https": proxy},
timeout=20,
)
response.raise_for_status()
egress = str(IPv4Address(response.text.strip()))
except (requests.RequestException, ValueError):
raise SystemExit(f"Request {request_number} failed; check the proxy logs.") from None
counts[egress] += 1
print(request_number, user, egress)
print("Observed exits:", dict(counts)).venv/bin/python check_rotation.pyThe Requests proxy documentation describes explicit proxy configuration and authenticated proxy URLs. With two correctly configured Method 1 mappings, the script selects alternating usernames; the distinct public exits still depend on your routing and NAT configuration. For a Method 2-only test, set users = ("scraper1",) and let the parent selector choose the exits.
This is a sequential diagnostic, not a production scheduler. Multiple workers would need shared scheduling and destination rate-limit handling. Keep one identity for workflows that require a stable authenticated session. A long-lived tunnel cannot change its exit IP midway through its connection.
Verify Routing Before Using the Pool
One exit for both users: check ACL ordering, authenticated usernames, interface addresses, NAT, and IPv4 versus IPv6 behavior.
Parent connection failure: check each private listener, the front server's source address, firewall rules, and the parent's access policy. Confirm that each parent has a distinct public exit before testing the combined pool.
Some parents never appear: check eligibility and availability, then test enough fresh connections to distinguish a small sample from a routing error. Do not infer exact traffic shares from ten requests.
Unexpected successful direct routing: inspect
always_directandnever_directrules together. In a test environment, make the parents unavailable and confirm the front proxy fails as intended.Unauthenticated access: repeat a client request without credentials and confirm rejection. Proxy ports should remain unreachable on the public interface for this tunnel-based setup.
Use sudo tail -n 50 /var/log/squid/access.log to inspect requests and forwarding outcomes. The Squid logging reference explains that recorded fields depend on the configured format. A log entry is not automatically proof of the NAT-translated public exit; compare it with the address reported by your test endpoint.
Rotation Does Not Guarantee Access
Several IPs do not guarantee fewer blocks, lower costs, or a fixed share of traffic per address. Destinations may limit accounts, sessions, or total activity regardless of IP. Follow published limits, honor retry guidance, and stop or reduce traffic when access is denied. Do not treat a 403 as proof that adding another address is the right fix.
Choose user mappings when you need stable, explainable exits. Choose parent peers when the exits live on separate servers. Add client-side selection only when a fresh connection and a chosen identity are appropriate for the workflow. Confirm the observed routing before increasing concurrency.
