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