Three proxies on one server: HTTP, SOCKS5, and Shadowsocks

Published: 2026-06-03

proxy.antonnovikov.com exposes three different proxy protocols from a single VPS. Each serves a different use case. Here's what runs where, how auth works, and how the FastAPI app monitors their availability.


The three protocols

HTTP proxy — tinyproxy on port 3128

tinyproxy is a minimal HTTP/HTTPS CONNECT proxy. It's a single C binary with almost no configuration surface. The only notable settings are Allow (IP allowlist), BasicAuth, and access log location. Runs as a systemd service on the host, outside the cluster.

# /etc/tinyproxy/tinyproxy.conf
Port 3128
Listen 0.0.0.0
Timeout 600
Allow 0.0.0.0/0
BasicAuth proxyuser secretpassword
LogFile "/var/log/tinyproxy/tinyproxy.log"

Use cases: browser proxy settings, curl --proxy http://user:pass@host:3128, ALL_PROXY env var for CLI tools.

SOCKS5 — microsocks on port 1080

microsocks is a tiny SOCKS5 server, also running on the host. Supports username/password auth. SOCKS5 tunnels any TCP connection, not just HTTP, so it works for any application that speaks the SOCKS protocol — SSH, IMAP, custom apps.

bash# Start microsocks
microsocks -p 1080 -u proxyuser -P secretpassword

Use cases: applications that can't use HTTP proxy but support SOCKS5, SSH with ProxyCommand, Telegram desktop.

Shadowsocks — ss-server on port 8388

Shadowsocks is a SOCKS5-over-encrypted-stream proxy originally designed to be hard to detect by DPI. The encryption method is aes-256-gcm. A shared password is enough — no username needed.

json{
  "server": "0.0.0.0",
  "server_port": 8388,
  "password": "your-secret-password",
  "method": "aes-256-gcm",
  "timeout": 300,
  "mode": "tcp_and_udp"
}

Client apps: shadowsocks-ng (macOS), Shadowsocks for Android, sing-box, Clash. They connect to port 8388 and transparently wrap traffic.

Use cases: situations where plain SOCKS5 or HTTP traffic is throttled or blocked by DPI-based filtering.


Systemd service setup

Each proxy runs as a systemd service:

ini# /etc/systemd/system/microsocks.service
[Unit]
Description=microsocks SOCKS5 proxy
After=network.target

[Service]
ExecStart=/usr/local/bin/microsocks -p 1080 -u proxyuser -P secretpassword
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
bashsystemctl enable --now microsocks
systemctl enable --now shadowsocks-server
systemctl enable --now tinyproxy

Health check endpoints

The proxy router has three TCP probe endpoints that the front-end polls to display live status badges:

python@router.get("/health/http")
async def health_http():
    try:
        _, writer = await asyncio.wait_for(
            asyncio.open_connection(settings.proxy_http_host, settings.proxy_http_port),
            timeout=3.0,
        )
        writer.close()
        await writer.wait_closed()
        return Response(status_code=200)
    except Exception:
        return Response(status_code=502)

All three endpoints (/proxy/health/http, /proxy/health/socks5, /proxy/health/ss) do the same: open a raw TCP connection to the respective port and immediately close it. A successful connect → 200; anything else → 502. This checks whether the port is accepting connections without sending any proxy-specific bytes.

The SOCKS5 health endpoint also returns an x-connect-time header for latency display.


Traffic statistics

The same systemd collector that handles VPN status also tails proxy logs and aggregates request counts. The /proxy/stats endpoint returns whatever the collector last wrote under the "proxy" key in /var/lib/vpn-status/status.json:

python@router.get("/stats")
async def proxy_stats():
    try:
        data = json.loads(_STATUS_FILE.read_text())
        return JSONResponse(data.get("proxy", {}), headers={"Cache-Control": "no-store"})
    except Exception:
        return JSONResponse({}, headers={"Cache-Control": "no-store"})

If the file doesn't exist (local dev, collector not running), the endpoint returns an empty object instead of 500.


Credentials

All three proxies require authentication. Credentials are stored in .env, loaded into a Kubernetes Secret, and injected as environment variables into the pod. The Settings class (pydantic-settings) maps them:

proxy_http_user / proxy_http_pass    → tinyproxy BasicAuth
proxy_socks5_user / proxy_socks5_pass → microsocks -u/-P flags
proxy_ss_pass / proxy_ss_method      → Shadowsocks config

The proxy page renders these values (masked by default, revealed on click) so you can copy-paste connection strings without opening a config file.


Firewall rules

The VPS firewall (ufw or iptables) allows only the three proxy ports from known IPs:

bashufw allow from <my-home-ip> to any port 3128 proto tcp
ufw allow from <my-home-ip> to any port 1080 proto tcp
ufw allow from <my-home-ip> to any port 8388 proto tcp udp

For Shadowsocks specifically, proto tcp udp is needed because it supports both.


Why three protocols instead of one?

Protocol Ease of setup App support DPI resistance
HTTP proxy Browser-native, trivial HTTP/HTTPS only Low (plain text CONNECT)
SOCKS5 Moderate Any TCP app Low
Shadowsocks Requires client app Any TCP/UDP High (encrypted)

Running all three means the right tool is available for each situation — one server, one VPS bill, three options.


What can go wrong

tinyproxy returns 407 even with correct credentials. BasicAuth in tinyproxy requires the client to send Proxy-Authorization: Basic ... headers. Some tools use http_proxy=http://user:pass@host:port which encodes credentials differently. Verify with curl --proxy-user user:pass --proxy http://host:3128 https://ifconfig.me.

microsocks starts but no clients can authenticate. If compiled without PAM support, microsocks requires -u and -P flags. If started without them, any username/password is accepted. Check the systemd unit's ExecStart line.

Shadowsocks connects but traffic is slow. The default cipher aes-256-gcm requires hardware AES-NI. On older VMs without it, switch to chacha20-ietf-poly1305. Check for AES-NI with grep aes /proc/cpuinfo.


Summary

  • Three protocols for three use cases: HTTP proxy (browser-native), SOCKS5 (any TCP app), Shadowsocks (DPI-resistant encrypted)
  • All three run as systemd services on the host — no Kubernetes involvement needed for the proxy processes themselves
  • TCP health checks (/proxy/health/*) verify port reachability without needing proxy-specific protocol knowledge
  • Traffic stats come from the same collector that handles VPN status, written to a shared JSON file
  • Credentials are stored in a Kubernetes Secret and injected as env vars into the FastAPI pod for display on the proxy page