How to Build a Shared ProxyScrape Proxy Gateway on Linux with Privoxy and HAProxy
Turn a ProxyScrape SOCKS5 endpoint into one HTTP proxy address your whole team can use, with round-robin rotation across multiple upstreams, health checks, and private-network access control.
Overview
A per-machine proxy setup does not scale. When several people, a couple of CI runners, and a few containers all need the same proxy pool, credentials end up copied into every launcher and script, and nobody can tell which traffic used which upstream.
This tutorial builds the opposite: one Linux host that holds your ProxyScrape credentials once and exposes a single HTTP proxy address to your private network.
Client apps -> HAProxy (TCP :3128) -> Privoxy instances (127.0.0.1:8118-8120) -> ProxyScrape SOCKS5 -> Internet
The guide uses ProxyScrape SOCKS5 upstreams because the local front end has to pass a username and password upstream, and Privoxy supports credentials for SOCKS5 upstreams but not for HTTP upstreams. If you specifically need to chain through an authenticated HTTP upstream, use a chaining tool that supports HTTP parent authentication instead.
| Layer | Software | What it does |
|---|---|---|
| Client edge | HAProxy (TCP mode) | Gives clients one host and port, spreads new connections across local proxy instances, removes instances that stop responding |
| Local proxy | Privoxy | Accepts HTTP proxy requests and forwards each one to a single ProxyScrape SOCKS5 endpoint |
| Upstream | ProxyScrape SOCKS5 | Resolves and fetches the target URL using a proxy IP from the pool |
One practical consequence of this design: rotation happens per new connection, not per request. A client that reuses a keep-alive connection stays on the same upstream instance until it reconnects.
Prerequisites
- A Linux host (Debian/Ubuntu or RHEL/Fedora family) with a stable address on a private network, VPN, or tailnet. Do not expose the gateway port to the public internet.
- ProxyScrape SOCKS5 endpoint details from your dashboard: host, port, username, and password.
- sudo access, and permission to open one port (3128 in the examples below) on the private network.
- Basic familiarity with systemd units and editing configuration files.
Steps
Step 1: Verify the ProxyScrape SOCKS5 endpoint before building anything
Put your credentials in shell variables so the later tests stay consistent:
export PS_HOST="socks5-host-from-your-dashboard"
export PS_PORT="1080"
export PS_USER="your-username"
export PS_PASS="your-password"
Make one request through the endpoint directly:
curl --silent --show-error \
--proxy "socks5h://${PS_USER}:${PS_PASS}@${PS_HOST}:${PS_PORT}" \
https://api.ipify.org
printf '\n'
The socks5h:// scheme makes curl send DNS lookups through the proxy, which is what you want for a gateway. Run the command two or three times and note the addresses that come back. If the first command fails, fix the credentials or the host and port before continuing, because every layer after this one depends on it.
Step 2: Install Privoxy and HAProxy
Debian and Ubuntu:
sudo apt update
sudo apt install --yes privoxy haproxy
RHEL, Fedora, and compatible systems:
sudo dnf install --yes privoxy haproxy
Confirm the binaries are present and record their paths, since the systemd unit below references the Privoxy binary directly:
command -v privoxy
command -v haproxy
Step 3: Create one Privoxy instance per upstream
Privoxy reads a single flat configuration file, which makes it easy to run several independent instances, one per upstream endpoint. Start with a directory for them:
sudo mkdir -p /etc/privoxy/instances
Create /etc/privoxy/instances/instance-1.conf:
confdir /etc/privoxy
logdir /var/log/privoxy
logfile instance-1.log
toggle 0
listen-address 127.0.0.1:8118
forward-socks5t / USERNAME:PASSWORD@HOST:PORT .
Replace USERNAME, PASSWORD, HOST, and PORT with your ProxyScrape SOCKS5 values.
toggle 0disables Privoxy content filtering. A gateway should forward traffic untouched, not rewrite or block it.listen-address 127.0.0.1:8118keeps the instance private to the host. HAProxy will be the only exposed surface.forward-socks5tsends requests to the SOCKS5 upstream and also resolves DNS remotely, so hostnames are not resolved from your own network.- The leading
/matches every URL and the trailing.is part of the SOCKS forwarding rule form used in the Privoxy documentation for Tor and similar setups.
Lock down the file, because it contains credentials:
sudo chown root:privoxy /etc/privoxy/instances/instance-1.conf
sudo chmod 640 /etc/privoxy/instances/instance-1.conf
If the distribution package already started a Privoxy service on port 8118, stop it first so the port is free:
sudo systemctl disable --now privoxy
Test the instance in the foreground:
sudo privoxy --no-daemon /etc/privoxy/instances/instance-1.conf
In a second terminal, send a request through it:
curl --silent --proxy http://127.0.0.1:8118 https://api.ipify.org
printf '\n'
If that returns an address, the hardest part is done. Stop the foreground process with Ctrl+C and repeat the config file for each additional ProxyScrape endpoint you want to use, changing only the log file name and the listen port.
Step 4: Run the instances as a systemd template service
Create /etc/systemd/system/[email protected]:
[Unit]
Description=Privoxy proxy gateway instance %i
Documentation=man:privoxy(8)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=privoxy
Group=privoxy
ExecStart=/usr/sbin/privoxy --no-daemon /etc/privoxy/instances/instance-%i.conf
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
If command -v privoxy printed a different path, adjust ExecStart to match. Then enable the instances:
sudo systemctl daemon-reload
sudo systemctl enable --now privoxy-gateway@1 privoxy-gateway@2 privoxy-gateway@3
systemctl list-units 'privoxy-gateway@*' --no-pager
Confirm each instance is listening on its own loopback port:
for port in 8118 8119 8120; do
printf '%s -> ' "$port"
curl --silent --proxy "http://127.0.0.1:${port}" https://api.ipify.org
printf '\n'
done
Each line should return an address, and they will not necessarily be the same address, because each instance talks to its own upstream.
Step 5: Put HAProxy in front as a TCP load balancer
The front end does not need to understand HTTP. It only has to relay TCP connections, which makes HAProxy's TCP mode a good fit for an HTTP proxy in front of SOCKS5 upstreams.
Back up the packaged config and replace /etc/haproxy/haproxy.cfg with the following. Change 10.8.0.1 to the private address of this host.
global
log /dev/log local0
maxconn 4000
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
defaults
mode tcp
log global
option tcplog
retries 2
timeout connect 10s
timeout client 5m
timeout server 5m
frontend proxy_gateway
bind 10.8.0.1:3128
default_backend privoxy_instances
frontend proxy_gateway_sticky
bind 10.8.0.1:3129
default_backend privoxy_instance_1
backend privoxy_instances
balance roundrobin
server privoxy1 127.0.0.1:8118 check
server privoxy2 127.0.0.1:8119 check
server privoxy3 127.0.0.1:8120 check
backend privoxy_instance_1
server privoxy1 127.0.0.1:8118 check
listen stats
bind 127.0.0.1:8404
mode http
stats enable
stats uri /stats
stats refresh 10s
A few notes on the choices here:
- Port
3128is the rotating entry point. Each new TCP connection is sent to the next healthy Privoxy instance. - Port
3129is pinned to a single instance for jobs where you want every request to leave through the same upstream for the duration of a run. - The plain
checkkeyword performs a TCP connect check, so an instance that stops accepting connections is taken out of rotation automatically. - The stats page is bound to loopback only. Reach it through an SSH tunnel or an internal jump host rather than opening port 8404 to the network.
Validate and reload:
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl restart haproxy
systemctl --no-pager --lines=20 status haproxy
Step 6: Restrict access to your private network
Binding HAProxy to a private, VPN, or tailnet address means the gateway is only reachable from inside that network. If the host also has a public interface, add a firewall rule so nothing on that interface can reach the proxy ports.
The following nftables example is optional and shown for illustration. Apply it from a second terminal session so a mistake cannot lock you out of the machine, and adapt the source range to your own network:
sudo nft add table inet proxygw
sudo nft add chain inet proxygw input '{ type filter hook input priority 0; }'
sudo nft add rule inet proxygw input tcp dport { 3128, 3129 } ip saddr != 10.8.0.0/24 drop
This stack does not authenticate clients; access control comes from the network the gateway is bound to. If you need per-person credentials, terminate user access on a VPN or tailnet in front of the host.
Step 7: Verify rotation end to end
Send a series of separate requests to the rotating port:
for i in $(seq 1 6); do
curl --silent --proxy http://10.8.0.1:3128 https://api.ipify.org
printf '\n'
done
Each curl invocation opens a new connection, so you should see a spread of addresses rather than a single repeated one. Then open the stats page (through a tunnel if needed) and confirm that every backend is marked UP:
ssh -L 8404:127.0.0.1:8404 user@gateway-host
# in the browser on your workstation: http://127.0.0.1:8404/stats
Finally, point a real client at the gateway, for example a shell session that uses the proxy for command-line tools:
export http_proxy="http://10.8.0.1:3128"
export https_proxy="http://10.8.0.1:3128"
curl --silent https://api.ipify.org
printf '\n'
unset http_proxy https_proxy
Rotating versus sticky behaviour
Choose the front-end port that matches the job. Both options are already configured above.
- Rotating (
3128): new connections are distributed across all healthy instances. Good default for browsers, general browsing, and jobs that open fresh connections frequently. - Sticky (
3129): all traffic goes to one instance. Good for workflows that must present a consistent source address for the length of a session. - Per-group isolation: create a second backend with a subset of the instances and point a new front-end port at it, so one team never consumes the whole pool.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Privoxy returns a 403 response | Content filtering is enabled and a filter rule matched the request | Confirm toggle 0 is present in the instance config and restart that instance |
| Privoxy returns a 502 or upstream connection error | Wrong SOCKS5 host, port, or credentials | Re-run the direct curl --proxy "socks5h://..." test from Step 1 |
All backends show as DOWN in the stats page |
Privoxy instances are not running or not listening where HAProxy expects | Run systemctl list-units 'privoxy-gateway@*' and `ss -ltnp |
| Every request exits the same address | The client is reusing a keep-alive connection, or only one instance is up | Force new connections per request, and confirm all backends report UP |
| Hostnames resolve from your own network | The instance uses forward-socks5 instead of forward-socks5t |
Switch to forward-socks5t so DNS is resolved through the proxy |
HAProxy fails to start with a /dev/log error |
No syslog socket on the host | Remove the log /dev/log local0 line, or install and enable a syslog service |
| Large downloads time out mid-transfer | Default HAProxy timeouts are too low for the workload | Raise timeout client and timeout server in the defaults section |
Summary
You now have a single HTTP proxy address that fronts several ProxyScrape SOCKS5 endpoints, with automatic health checking on the HAProxy side and per-connection rotation on port 3128. Credentials live in one set of root-owned config files instead of being copied into every tool on every machine.
Natural next steps: add an instance and a server line whenever you want more upstream capacity, keep the Privoxy config files out of version control, and rotate credentials by editing the instance configs and restarting the template units one at a time so the gateway never goes fully offline.