๐ŸŽ‰ Premium Proxies ยท 24-Hour Free TrialClaim Now
Use Cases

Squid cache_peer Upstream Proxy With Authentication

Set up a Squid cache_peer upstream proxy with login=user:pass, never_direct and LAN ACLs so tools without proxy auth can use a paid proxy. Fix TCP_MISS/503.

S SparkProxy 2 13 min read
Share

A squid cache_peer upstream proxy solves a narrow, annoying problem: half the tools on your network can point at a proxy host and port but have no field for a username and password. Put Squid on one box, let it hold the credentials in a cache_peer ... login= line, and every device on the LAN gets the paid proxy without knowing a secret. This guide gives you a squid.conf that works on Squid 6 and 7, then covers how rotation behaves through the extra hop, what happens to HTTPS, and how to read the TCP_MISS/503 lines that show up when the parent stops answering.

The Job: One Front Door for Tools That Can't Authenticate

The usual trigger is a mixed LAN. A smart TV app, an old Java crawler, a monitoring appliance, a Windows system proxy setting that some programs honour and others don't. Each accepts host:port. Few of them can send a Proxy-Authorization header, and the ones that can each store the password in a different place.

Squid fixes this by sitting in the middle. Clients connect to Squid with no credentials. Squid checks that they come from your LAN, then forwards every request to the paid gateway as a parent, attaching the username and password itself. The paid proxy only ever sees Squid.

LAN clients (no auth)        Squid box                 SparkProxy gateway
192.168.1.0/24  --->  192.168.1.10:3128  --->  gateway.sparkproxy.io:11000  --->  target
                      ACL: LAN only             login=USER:PASS sent here         sees the
                      never_direct              rotation happens here             exit IP

If the parent and child terminology is new, the short version lives in what is an upstream proxy. This post assumes you know the idea and need the config to survive real traffic. It is also not a privacy chain. Stacking hops for anonymity is a different goal with different tradeoffs, covered in what is proxy chaining. Here the second hop exists purely to hold credentials and enforce who may use them.

What to Collect Before You Edit squid.conf

Gather these first. Most broken setups we see trace back to one wrong value in this list rather than to Squid itself.

ItemWhere it comes fromExample used in this guide
Gateway hostSparkProxy dashboard`gateway.sparkproxy.io`
Rotating HTTP portDashboard`11000`
Sticky Sessions portDashboard`11002`
Username and passwordDashboard`YOUR_USER` / `YOUR_PASS`
Squid box LAN address`ip -4 addr` on the box`192.168.1.10`
LAN subnet allowed to use itYour router`192.168.1.0/24`
Squid box public IP (whitelist route only)`curl -s https://api.ipify.org` from the box`203.0.113.25`
Squid version`squid -v`6.x or 7.x

On the version: the Squid project lists v7.7, released 2026-08-24, as the current release series on its Getting Squid page (read 2026-10-30), and the latest commit on the GitHub repository was dated 2026-10-23 when we checked. Your distribution may ship an older 6.x package. Every directive below is documented for both v6 and v7 in the cache_peer reference.

Before touching Squid, confirm the Squid box can reach the gateway on its own. If this fails, no squid.conf will help:

# Run on the Squid box, not on a client
curl -s -x http://YOUR_USER:YOUR_PASS@gateway.sparkproxy.io:11000 https://api.ipify.org; echo

You should see an IP that is not the box's own public address. Write it down.

Free trial

Scraping at scale? Skip the blocks.

Fast, unblockable datacentre proxies with unlimited bandwidth.

A Working squid.conf for a cache_peer Upstream Proxy

Install Squid (sudo apt install squid on Debian or Ubuntu), move the stock file aside, and start from this. It is a pure forwarder: no disk cache, LAN-only access, and no way out except through the parent.

# /etc/squid/squid.conf  -- Squid 6.x / 7.x
http_port 3128

# Who may use this proxy. Keep it to your real subnet.
acl localnet src 192.168.1.0/24
acl SSL_ports port 443
acl Safe_ports port 80 443

http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localhost
http_access allow localnet
http_access deny all

# The paid gateway as the only parent, with credentials
cache_peer gateway.sparkproxy.io parent 11000 0 no-query no-digest default name=spark_rotating login=YOUR_USER:YOUR_PASS connect-timeout=10 connect-fail-limit=5

# Never connect to a target directly, even if the parent is down
never_direct allow all

# Don't leak LAN details to the target
forwarded_for delete
via off
httpd_suppress_version_string on

# Forward only, store nothing
cache deny all

Then validate and load it:

sudo squid -k parse          # syntax check, prints the offending line on error
sudo systemctl restart squid
curl -s -x http://192.168.1.10:3128 https://api.ipify.org; echo   # from a LAN client

What each non-obvious piece does:

  • cache_peer ... parent 11000 0: the gateway's HTTP port, then 0 for the ICP port because a commercial gateway does not speak ICP. no-query and no-digest stop Squid from trying cache-sibling protocols against it.
  • default: marks this as the last-resort parent. With a single peer it mostly affects how the hierarchy code appears in the log.
  • name=spark_rotating: the label Squid uses in cache.log and in cache_peer_access. You need it once you add a second peer on the same hostname, so set it from day one.
  • login=YOUR_USER:YOUR_PASS: Squid adds a Basic Proxy-Authorization header on every request it sends to this peer, including CONNECT for HTTPS.
  • connect-timeout=10 overrides the global peer_connect_timeout, which defaults to 30 seconds per the directive docs. Thirty seconds of hanging is a long time for a LAN client.
  • connect-fail-limit=5: how many connect failures mark the peer DEAD. The documented default is 10.
  • never_direct allow all: the line that makes the setup trustworthy. Without it, if the parent fails Squid may quietly fetch from your own IP, and you'd only find out from the target's logs.
  • forwarded_for delete: Squid's default is on, which appends X-Forwarded-For: 192.168.1.x to plain-HTTP requests it forwards. Through a paid exit, that header hands the target your internal address. delete removes it, and via off drops the Via header Squid adds by default.

The ACL order matters. http_access rules are first-match, so deny all must be last, and the localnet range must never be widened to all on a box with a public interface. An open Squid that logs in to a paid gateway is an open proxy billed to you.

Passwords With Special Characters in login=

This one costs people an afternoon. The cache_peer docs state that the login= string "can include URL escapes (i.e. %20 for spaces). This also means % must be written as %%." So Squid decodes the value before sending it, and a password that contains % gets mangled unless you double it.

Character in the real passwordWrite it in login= asWhy
`%``%%`Squid treats `%` as the start of an escape
space`%20`Whitespace ends the directive token
`:` in the usernameavoid itThe colon is what separates user from password

The symptom of getting this wrong is a perfectly reachable parent that answers every request with 407. The quickest fix is boring: generate a gateway password from letters and digits only, and keep the escaping table for credentials you don't control.

Whitelist the Squid Server's IP Instead of login=

If Squid runs on a machine with a stable public IP, usually a small VPS or an office line with a static address, you can skip credentials entirely. Add the box's public IP to the whitelist in the SparkProxy dashboard and delete the login= option:

cache_peer gateway.sparkproxy.io parent 11000 0 no-query no-digest default name=spark_rotating connect-timeout=10
never_direct allow all

The gateway now authorises by source address. Two things change.

First, no password travels between Squid and the gateway. That hop is plain HTTP to port 11000, so a login= header is Base64, not encryption, readable by anything on the path. Whitelisting removes that exposure.

Second, your http_access rules become the only lock. Anyone who can reach port 3128 rides your whitelisted IP. On a VPS, localnet should be your office's public address, not a private range, and the cloud firewall should block 3128 from everywhere else:

# Squid on a VPS, used from one office
acl office src 198.51.100.40/32
http_access allow office
http_access deny all

Plans include a fixed number of whitelist slots; on 2026-10-30 the SparkProxy pricing section listed 5 on the $75/month Starter pack and 10 on the $140/month Core pack (USD, excluding VAT/GST). One Squid box uses one slot, which is the point: dozens of LAN devices, one whitelisted address. Whitelisting fails if the box's IP changes, so don't use it on a residential connection with a dynamic address. The mechanics and the dynamic-IP trap are covered in what is IP whitelisting for proxies.

login= on cache_peerWhitelisted Squid IP
Secret stored on the boxYes, in squid.confNo
Credential on the wire to the gatewayYes, Basic authNo
Survives a public IP changeYesNo
Who controls accessGateway login plus Squid ACLsSquid ACLs alone
Country or session options via usernameYesDepends on the port and dashboard settings

How Rotation Passes Through Squid

Squid does not rotate anything. It forwards; the gateway picks the exit. SparkProxy offers Random Rotation (the site describes it as a fresh IP per request) and Sticky Sessions, which hold one exit for 5 minutes. What Squid controls is how many upstream connections carry your traffic, and that decides how rotation looks from the client side.

Plain HTTP. Squid keeps idle connections to the parent open and reuses them, because server_persistent_connections defaults to on (docs). Several client requests can share one upstream connection. If your measurements show the same exit repeating more than you expect for plain-HTTP targets, force a new upstream connection per request:

server_persistent_connections off

You pay a TCP handshake per request to the gateway in exchange.

HTTPS. Here rotation granularity is the tunnel, and no setting changes that. A CONNECT tunnel is one TCP stream from the exit IP to the target. The gateway cannot swap the exit in the middle of it without breaking TLS. So a browser that opens one tunnel and sends forty requests over HTTP keep-alive presents one IP for all forty. A script that opens a new connection per request presents a new IP per request. Same Squid, same gateway, different client behaviour.

Don't take our word for it. Measure it on your own setup with this script, which runs from any LAN machine:

import requests

SQUID = "http://192.168.1.10:3128"
PROXIES = {"http": SQUID, "https": SQUID}
ECHO = "https://api.ipify.org"


def fresh_connections(n):
    # requests.get() builds a new session each call, so each call opens a new tunnel
    return [requests.get(ECHO, proxies=PROXIES, timeout=20).text.strip() for _ in range(n)]


def one_session(n):
    # one Session reuses the same CONNECT tunnel while the server keeps it alive
    with requests.Session() as s:
        s.proxies.update(PROXIES)
        return [s.get(ECHO, timeout=20).text.strip() for _ in range(n)]


if __name__ == "__main__":
    a = fresh_connections(10)
    b = one_session(10)
    print("new tunnel per request:", len(set(a)), "distinct IPs of", len(a))
    print("one reused session:    ", len(set(b)), "distinct IPs of", len(b))

If both numbers come back the same, the echo service closed the connection between calls and you're seeing per-connection behaviour either way. The lesson for tools you can't modify: a browser-like client through a rotating gateway rotates per tunnel, so if you need one IP across a whole login flow, give that tool the sticky port described next instead of hoping keep-alive holds.

Separate Ports for Rotating, Sticky and Country Exits

Different tools want different exits. A price checker wants a new IP every request. A tool that logs in wants Sticky Sessions. A QA box wants German exits. Since the tools can't send usernames, encode the choice in the Squid port they connect to.

Squid can name each http_port, match on that name with a myportname ACL, and steer requests to a specific peer with cache_peer_access. Three peers on the same hostname need distinct name= values, which the docs require.

http_port 3128 name=rotating
http_port 3129 name=sticky
http_port 3130 name=de

acl port_rotating myportname rotating
acl port_sticky   myportname sticky
acl port_de       myportname de

cache_peer gateway.sparkproxy.io parent 11000 0 no-query no-digest name=rot   login=YOUR_USER:YOUR_PASS
cache_peer gateway.sparkproxy.io parent 11002 0 no-query no-digest name=stick login=YOUR_USER:YOUR_PASS
cache_peer gateway.sparkproxy.io parent 11000 0 no-query no-digest name=de    login=YOUR_USER-country-de:YOUR_PASS

cache_peer_access rot   allow port_rotating
cache_peer_access rot   deny all
cache_peer_access stick allow port_sticky
cache_peer_access stick deny all
cache_peer_access de    allow port_de
cache_peer_access de    deny all

never_direct allow all

Notes on this layout:

  • The country peer uses the country-suffixed username format that the SparkProxy dashboard shows (the homepage widget displays user-country-us). Copy the exact string from your dashboard rather than guessing a format.
  • default is gone. The docs say only the first default parent counts, and a default parent could catch requests from the wrong port. With cache_peer_access and never_direct, each port has exactly one eligible parent.
  • Each cache_peer_access group ends in deny all for that peer, which keeps the routing explicit.

Point the smart TV at :3128, the login-heavy tool at :3129 and the QA browser at :3130. One box, three behaviours, no credentials on any client.

HTTPS and CONNECT Through the Parent

For an https:// URL, the client sends Squid CONNECT target:443. Squid checks it against http_access (the stock deny CONNECT !SSL_ports rule rejects tunnels to anything but 443), then opens its own connection to the parent and sends CONNECT target:443 there, with the login= credentials attached. Once the parent answers 200, Squid shovels bytes both ways. TLS runs end to end between the client and the target; neither Squid nor the gateway sees inside without deliberate TLS interception, which you don't want here.

Practical consequences:

  • The target's TLS fingerprint is the client's. Squid doesn't rewrite it. If a target blocks your old Java crawler on its handshake, Squid can't fix that, and neither can a better exit IP.
  • Non-443 HTTPS fails at Squid. A tool connecting to https://api.sparkproxy.io:8443 gets denied by deny CONNECT !SSL_ports. Add the port to SSL_ports and Safe_ports deliberately if you need it.
  • Parent auth failures look different. On HTTPS, Squid's tunneler relays the parent's error reply. curl reports it as curl: (56) CONNECT tunnel failed, response 407. On plain HTTP you get the 407 response body itself.
  • Caching does nothing for tunnels. Squid can't cache what it can't read, which is one reason this config uses cache deny all.

Reading access.log: Prove Nothing Goes Direct

Squid's default squid log format is %ts.%03tu %6tr %>a %Ss/%03>Hs %un %Sh/% ([cf.data.pre). Two fields matter here: the result code and status (TCP_MISS/200) and the hierarchy code with the server address (FIRSTUP_PARENT/65.109.70.130). The lines below are illustrative; 65.109.70.130 is the address gateway.sparkproxy.io resolved to when we checked, and yours may differ.

1793350211.402    388 192.168.1.42 TCP_MISS/200 1342 GET http://books.sparkproxy.io/ - FIRSTUP_PARENT/65.109.70.130 text/html
1793350215.118   1904 192.168.1.42 TCP_TUNNEL/200 6120 CONNECT www.sparkproxy.io:443 - FIRSTUP_PARENT/65.109.70.130 -
1793350230.770  10012 192.168.1.42 TCP_MISS/503 4012 GET http://books.sparkproxy.io/ - HIER_NONE/- text/html

The rule is simple: with never_direct allow all you should never see HIER_DIRECT. A parent code (DEFAULT_PARENT, FIRSTUP_PARENT, ANY_OLD_PARENT) means the request went through the gateway. HIER_NONE/- means Squid never got an answer from anyone. The Squid log FAQ also lists NO_DIRECT_FAIL: the request was blocked by never_direct and no parent was available. TCP_TUNNEL marks a CONNECT, and the wiki notes the NONE tag can appear on CONNECT responses too.

This script tallies a log and flags any direct fetch:

import sys
from collections import Counter

PARENT_CODES = {"DEFAULT_PARENT", "FIRSTUP_PARENT", "ANY_OLD_PARENT", "SINGLE_PARENT"}


def summarize(path):
    results, hierarchy, direct = Counter(), Counter(), []
    with open(path, encoding="utf-8", errors="replace") as fh:
        for line in fh:
            parts = line.split()
            if len(parts) < 10:
                continue
            result, hier = parts[3], parts[8].split("/")[0]
            results[result] += 1
            hierarchy[hier] += 1
            if hier == "HIER_DIRECT":
                direct.append(parts[6])
    return results, hierarchy, direct


if __name__ == "__main__":
    res, hier, direct = summarize(sys.argv[1] if len(sys.argv) > 1 else "/var/log/squid/access.log")
    print("Top result codes:", res.most_common(8))
    print("Hierarchy:", dict(hier))
    went_up = sum(v for k, v in hier.items() if k in PARENT_CODES)
    print("Through a parent:", went_up)
    if direct:
        print("WARNING: direct fetches found, check never_direct:", direct[:5])

Run it from cron and alert on any WARNING line. A direct fetch means your own IP touched a target.

Fixing TCP_MISS/503 and Other Upstream Errors

The symptom: clients get an error page, and access.log shows TCP_MISS/503. On Squid 6 and later you may see TCP_MISS_ABORTED/503 instead; the log FAQ notes that since v6 the ABORTED tag also appears when Squid cannot talk to a cache_peer, such as when the peer refuses TCP connections. The error page text tells you which template Squid used:

  • Connection to 65.109.70.130 failed. followed by The system returned: ... is ERR_CONNECT_FAIL.
  • Unable to forward this request at this time. This request could not be forwarded to the origin server or to any parent caches. is ERR_CANNOT_FORWARD. Its own text lists "All configured parent caches may be currently unreachable" and "The administrator may not allow this cache to make direct connections". That is never_direct doing its job while the parent is down.

Work through the causes cheapest first.

1. Is the error Squid's or the parent's? Squid stamps its own error pages with an X-Squid-Error header holding the template name and errno:

curl -s -o /dev/null -D - -x http://192.168.1.10:3128 http://books.sparkproxy.io/ | grep -i -E '^HTTP|x-squid-error'
# HTTP/1.1 503 Service Unavailable
# X-Squid-Error: ERR_CONNECT_FAIL 111

Errno 111 is ECONNREFUSED on Linux, 110 is ETIMEDOUT. No X-Squid-Error header and a parent hierarchy code in the log means the gateway itself answered 503, so the problem is upstream of Squid.

2. Can the box reach the gateway without Squid? Repeat the direct curl from the prerequisites section on the Squid box. If it fails too, Squid is innocent: check the host and port, the box's outbound firewall, and DNS for gateway.sparkproxy.io. Port typos are common; 11000 is rotating HTTP and 13000 is SOCKS5, and Squid's cache_peer speaks HTTP to its parent, so pointing it at the SOCKS port fails.

3. Has Squid marked the parent dead? Check cache.log:

sudo grep -E 'Connection to .* failed|Detected (DEAD|REVIVED) Parent' /var/log/squid/cache.log | tail

ERROR: Connection to spark_rotating failed repeated up to connect-fail-limit, then Detected DEAD Parent: spark_rotating, means Squid stopped using the peer. Every request after that hits ERR_CANNOT_FORWARD until Detected REVIVED Parent appears. Fix the connectivity cause, then sudo squid -k reconfigure to retry immediately.

4. Are the credentials right? A 407 rather than a 503 points at login=. Check the escaping rules above, or test with the whitelist route to rule credentials out entirely.

5. Did the target refuse the exit? If the gateway connects but the target blocks or times out, you get whatever the gateway returns, relayed through Squid with a parent hierarchy code. Rotating to a fresh exit, switching country, or slowing request rate can help here. Nothing in squid.conf will.

What you seeMost likely causeProxy-side fix?
`ERR_CONNECT_FAIL 111`Wrong port, gateway unreachable, outbound firewallFix host, port or firewall on the Squid box
`ERR_CONNECT_FAIL 110`Network path drops packets, timeoutCheck routing; lower `connect-timeout` to fail fast
`ERR_CANNOT_FORWARD` after `Detected DEAD Parent`Parent marked dead after repeated failuresRestore connectivity, then `squid -k reconfigure`
407 or `CONNECT tunnel failed, response 407`Bad or mis-escaped `login=`, IP not whitelistedFix credentials or whitelist the box
403 from Squid, `ERR_ACCESS_DENIED`Client outside `localnet`, or non-443 CONNECTAdjust ACLs
503 with parent hierarchy code, no X-Squid-ErrorTarget-side block or gateway answerDifferent exit or country; not a Squid fix
Target rejects the TLS handshakeClient fingerprintNo. Neither Squid nor the exit IP changes it

Squid also retries on its own. forward_max_tries defaults to 25 (docs), while connect_retries defaults to no retries of a failed connection opening. With a single parent and never_direct, retries have nowhere else to go, so they mostly add delay before the error.

Plan Threads, max-conn and Cost

SparkProxy plans are packs priced by concurrency, with unlimited bandwidth included. "Starting from $75/month" buys the Starter pack, which the pricing section listed on 2026-10-30 at 100 threads; Core is $140/month for 250 threads, Boost $240/month for 500, and Plus $440/month for 1,000 (USD, billed every 30 days, excluding VAT/GST). Term discounts are 5% for 3 months, 10% for 6 and 15% for 12. Because traffic volume isn't billed, a Squid front door doesn't change your bill by moving more bytes. What it changes is how many simultaneous connections your LAN opens upstream.

Squid can cap that per peer with max-conn:

cache_peer gateway.sparkproxy.io parent 11000 0 no-query no-digest default name=spark_rotating login=YOUR_USER:YOUR_PASS max-conn=100

Read the documented behaviour carefully before relying on it: "A peer exceeding the limit is not used for new requests unless a standby connection is available." With one parent and never_direct, a request that arrives at the cap has no other path, so expect it to fail with an error instead of waiting in a queue. The docs also warn that max-conn "currently works poorly with idle persistent connections". Treat it as a guard rail you test under load, and set it a little under your plan's thread count. If your LAN's peak legitimately exceeds the plan, the fix is a bigger pack, not a tighter Squid.

Two cheaper habits help more than tuning. Keep long-lived background tools (update checkers, telemetry) off the proxy port so they don't hold threads. And give sticky-session tools their own port, as above, so a five-minute session isn't fighting a high-churn scraper for the same peer.

When the tool on the LAN is a scraper you control, Squid may be the wrong layer altogether. The SparkProxy Scraping API takes the target as a parameter and handles the exit for you; a plain fetch with render_js=false costs 1 credit, and new accounts get 1,000 free credits:

import requests

r = requests.get(
    "https://scrape.sparkproxy.io/api/v1",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"url": "https://www.sparkproxy.io/", "render_js": "false"},
    timeout=60,
)
print(r.status_code, len(r.text))

Keep Squid for the tools you can't rewrite. That is where a single authenticated front door earns its place.

Frequently asked questions

FAQ

Add login=USERNAME:PASSWORD to the cache_peer line, for example cache_peer gateway.sparkproxy.io parent 11000 0 no-query default login=USER:PASS. Squid then sends a Basic Proxy-Authorization header to the parent on every forwarded request, including HTTPS CONNECT, so LAN clients need no credentials.

It usually means Squid could not get a usable response from the parent: the TCP connection failed (ERR_CONNECT_FAIL), or the parent was marked dead and never_direct blocked any fallback (ERR_CANNOT_FORWARD). Check the X-Squid-Error header and cache.log for Detected DEAD Parent to tell which.

It forbids Squid from fetching any request straight from the origin server, so everything must go through a cache_peer parent. If the parent is down, clients get an error instead of a silent direct fetch from your own IP. Without it, a failed parent can leak traffic.

Yes, if the Squid box has a stable public IP. Whitelist that IP in the provider dashboard and remove login= from cache_peer. No credential travels to the gateway, but your Squid http_access ACLs become the only thing stopping strangers from using your plan.

Yes. The gateway rotates, not Squid. For HTTPS the exit is fixed for the life of each CONNECT tunnel, so clients that reuse one connection keep one IP. For plain HTTP, Squid's persistent connections to the parent can be turned off with server_persistent_connections off if you need a new upstream connection per request.

Give Squid several http_port lines with names, match them with myportname ACLs, and bind each port to its own cache_peer (each with a unique name=) using cache_peer_access. Point each tool at the port whose peer uses the rotating port, the Sticky Sessions port, or a country-suffixed username.

Special Discount ยท 20% off

Get 20% off your first month

Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.

Save up to 15% more on quarterly, half-yearly and yearly plans

Claim Discount

About the Author

This guide was written by the SparkProxy Technical Team. SparkProxy runs a proxy network of 1M+ premium IPs across 80+ countries with HTTP, HTTPS and SOCKS5 support, IP whitelist and user:pass authentication, and a Scraping API for teams that would rather send one request than operate proxy infrastructure. Every Squid directive and default in this post was checked against the Squid project's own documentation and v7.7 source on 2026-10-30. Start a 24-hour free trial, no credit card required, at sparkproxy.io.

Keep reading

Related articles