Skip to content
Advanced / 10 min read

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.

Linux SOCKS5 HTTP(S) Other

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 0 disables Privoxy content filtering. A gateway should forward traffic untouched, not rewrite or block it.
  • listen-address 127.0.0.1:8118 keeps the instance private to the host. HAProxy will be the only exposed surface.
  • forward-socks5t sends 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 3128 is the rotating entry point. Each new TCP connection is sent to the next healthy Privoxy instance.
  • Port 3129 is 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 check keyword 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.