Files
2026-07-26 05:15:08 +04:00

13 KiB

Egress Proxy

Warning

This project was created with the assistance of an AI agent.

Although it has been tested in production, it is provided as-is. I do not accept responsibility for configuration errors, service interruptions, exposed credentials, open proxy abuse, or security vulnerabilities.

A Docker Compose wrapper around tarampampam/3proxy-docker designed to expose local HTTP and SOCKS proxy endpoints and route their traffic through an authenticated upstream HTTP proxy.

The stack preserves the dynamic configuration provided by the original image and adds optional deployment integrations for standalone Docker, Traefik, Nginx, HAProxy, and other TCP reverse proxies.

Requirements

  • Docker Engine
  • Docker Compose v2
  • An upstream HTTP proxy
  • Upstream proxy hostname or IP address
  • Upstream proxy port
  • Upstream proxy username and password
  • Optional external Docker network when integrating with Traefik, Nginx, or HAProxy

The upstream proxy must support the HTTP CONNECT method for HTTPS and SOCKS TCP forwarding.

Features

  • HTTP forward proxy on port 3128
  • SOCKS proxy on port 1080
  • Routes supported traffic through an authenticated upstream HTTP proxy
  • No authentication required from local proxy clients
  • Dynamic configuration through environment variables
  • Preserves native 3proxy-docker configuration options
  • Does not replace the original image entrypoint
  • Does not mount a complete custom 3proxy.cfg
  • Uses the image's native EXTRA_CONFIG mechanism
  • Standalone deployment with directly published ports
  • Traefik TCP router integration
  • Nginx and HAProxy integration through a shared Docker network
  • Optional PROXY protocol support with Traefik
  • Configurable client IP allowlist
  • Configurable DNS resolvers, connection limits, ports, and listener arguments
  • Automatic restart through Docker restart policies
  • Smoke-test suite for HTTP, HTTPS, and SOCKS forwarding
  • Optional verification of the expected upstream exit IP

Repository Structure

.
├── .env.example
├── .gitattributes
├── .gitignore
├── README.md
├── compose.yaml
├── compose.standalone.yaml
├── compose.traefik.yaml
├── compose.external-network.yaml
├── compose.test.yaml
└── tests/
    ├── run.sh
    └── smoke.sh

How It Works

The base image generates the normal 3proxy configuration using its built-in environment-variable support.

This project adds parent routing rules through EXTRA_CONFIG.

The generated configuration is equivalent to:

parentretries 3
auth iponly

allow * * * * HTTP,HTTPS
parent 1000 http "proxy.example.com" 8080 "username" "password"

allow * * * * CONNECT
parent 1000 connect "proxy.example.com" 8080 "username" "password"

deny *

The operation-specific access rules are important:

  • HTTP,HTTPS handles requests received by the HTTP proxy listener.
  • CONNECT handles TCP connections received by the SOCKS listener.
  • http is used as the upstream parent type for the HTTP listener.
  • connect is used to tunnel SOCKS TCP traffic through the upstream HTTP proxy.
  • SOCKS UDP association and bind operations are not supported by an upstream HTTP proxy.

The stack uses:

auth iponly

This does not require clients to provide a username or password. It enables 3proxy ACL and parent-routing processing.

Using:

auth none

would disable the ACL chain and may cause requests to bypass the upstream proxy.

Configuration

Copy the example environment file:

cp .env.example .env

At minimum, configure the upstream proxy:

UPSTREAM_HOST=proxy.example.com
UPSTREAM_PORT=8080
UPSTREAM_USERNAME=proxy-user
UPSTREAM_PASSWORD='proxy-password'

The .env file is ignored by Git and must not be committed.

Credentials with Special Characters

Use single quotes for values containing $, #, spaces, or similar characters:

UPSTREAM_PASSWORD='my$p@ssword#123'

Avoid double quotes and newline characters inside upstream credentials because the values are inserted into quoted 3proxy configuration arguments.

Available Environment Variables

Image and Project

COMPOSE_PROJECT_NAME=egress-proxy
THREEPROXY_IMAGE=ghcr.io/tarampampam/3proxy:2

Upstream Proxy

UPSTREAM_HOST=proxy.example.com
UPSTREAM_PORT=8080
UPSTREAM_USERNAME=proxy-user
UPSTREAM_PASSWORD='proxy-password'

Local Proxy Listeners

PROXY_PORT=3128
SOCKS_PORT=1080

DNS

PRIMARY_RESOLVER=1.1.1.1
SECONDARY_RESOLVER=8.8.8.8
DNS_CACHE_SIZE=65536

Limits

MAX_CONNECTIONS=100
PARENT_RETRIES=3
NOFILE_SOFT=2048
NOFILE_HARD=4096

Client Access

Allow all clients:

CLIENT_ALLOW=*

Allow a private network:

CLIENT_ALLOW=192.168.0.0/16

Allow several addresses or networks:

CLIENT_ALLOW=192.168.0.0/16,203.0.113.10

Listener Arguments

Additional arguments can be appended to the generated listeners:

PROXY_EXTRA_ARGS=
SOCKS_EXTRA_ARGS=

These values preserve the customization options provided by the original 3proxy-docker image.

Validate the Configuration

Before starting the stack, validate the merged Compose configuration.

Standalone deployment:

docker compose \
  -f compose.yaml \
  -f compose.standalone.yaml \
  config

Traefik deployment:

docker compose \
  -f compose.yaml \
  -f compose.traefik.yaml \
  config

External network deployment:

docker compose \
  -f compose.yaml \
  -f compose.external-network.yaml \
  config

Test deployment:

docker compose \
  -f compose.yaml \
  -f compose.test.yaml \
  config

Standalone Deployment

Use the standalone overlay when there is no existing reverse proxy.

By default, the proxy binds only to localhost:

BIND_ADDRESS=127.0.0.1
HTTP_PUBLIC_PORT=3128
SOCKS_PUBLIC_PORT=1080

Start the stack:

docker compose \
  -f compose.yaml \
  -f compose.standalone.yaml \
  up -d

Test the HTTP proxy:

curl \
  --proxy http://127.0.0.1:3128 \
  https://api.ipify.org

Test the SOCKS proxy:

curl \
  --proxy socks5h://127.0.0.1:1080 \
  https://api.ipify.org

View logs:

docker compose \
  -f compose.yaml \
  -f compose.standalone.yaml \
  logs -f proxy

Stop the stack:

docker compose \
  -f compose.yaml \
  -f compose.standalone.yaml \
  down

LAN Access

To expose the proxy on all server interfaces:

BIND_ADDRESS=0.0.0.0

Restrict clients when exposing the service outside localhost:

CLIENT_ALLOW=192.168.0.0/16

Do not expose an unauthenticated proxy to the public internet without an IP allowlist, firewall rules, VPN, private network, or equivalent access control.

Traefik Deployment

3proxy must be exposed through Traefik TCP routers, not normal HTTP routers.

The HTTP proxy and SOCKS listener are separate TCP services and require dedicated Traefik entrypoints.

Add the following static configuration to the existing Traefik service:

services:
  traefik:
    command:
      - --entrypoints.proxy-http.address=:3128/tcp
      - --entrypoints.proxy-socks.address=:1080/tcp

    ports:
      - "3128:3128/tcp"
      - "1080:1080/tcp"

Create the shared network if it does not already exist:

docker network create traefik

Configure the integration:

TRAEFIK_NETWORK=traefik
TRAEFIK_ROUTER_PREFIX=egress-proxy
TRAEFIK_HTTP_ENTRYPOINT=proxy-http
TRAEFIK_SOCKS_ENTRYPOINT=proxy-socks

Start the stack:

docker compose \
  -f compose.yaml \
  -f compose.traefik.yaml \
  up -d

The Traefik overlay:

  • Creates one TCP router and service for the HTTP proxy.
  • Creates one TCP router and service for SOCKS.
  • Uses HostSNI(*) as a catch-all TCP routing rule.
  • Sends PROXY protocol v1 to 3proxy.
  • Enables -H on both 3proxy listeners.
  • Preserves the original client IP.
  • Does not publish ports directly from the 3proxy container.

Do not combine compose.standalone.yaml and compose.traefik.yaml.

In Traefik mode, Traefik should be the only service publishing ports 3128 and 1080.

Existing Nginx or HAProxy

Use the external-network overlay when an existing containerized TCP proxy should connect to 3proxy through Docker networking.

Create the external network:

docker network create edge

Configure it:

EDGE_NETWORK=edge
EDGE_SERVICE_ALIAS=egress-proxy

Start the stack:

docker compose \
  -f compose.yaml \
  -f compose.external-network.yaml \
  up -d

Other containers attached to the same network can reach:

egress-proxy:3128
egress-proxy:1080

Nginx Example

Nginx must use the stream context rather than the regular http context:

stream {
    upstream egress_http_proxy {
        server egress-proxy:3128;
    }

    upstream egress_socks_proxy {
        server egress-proxy:1080;
    }

    server {
        listen 3128;
        proxy_pass egress_http_proxy;
    }

    server {
        listen 1080;
        proxy_pass egress_socks_proxy;
    }
}

Without PROXY protocol, 3proxy sees the Nginx container address instead of the original client address.

In that case, client access restrictions should be applied in Nginx, or PROXY protocol should be configured on both sides.

Smoke Tests

The test suite starts the proxy on an internal Docker network and verifies:

  1. An HTTP request through the HTTP proxy listener.
  2. An HTTPS request through the HTTP proxy listener.
  3. An HTTPS request through the SOCKS listener.
  4. That the requests return a public exit IP.
  5. Optionally, that all listeners use the expected upstream exit IP.
  6. Optionally, that proxied traffic does not use the server's direct public IP.

Run the tests:

sh tests/run.sh

The command exits with:

  • 0 when all checks pass.
  • A non-zero code when a request or assertion fails.

The test stack is removed automatically after completion.

Fixed Exit IP

When the upstream proxy has a fixed public IP, configure:

EXPECTED_EXIT_IP=203.0.113.20

This is the strongest validation method.

All HTTP, HTTPS, and SOCKS requests must return that exact IP.

Rotating Exit IPs

When the upstream proxy uses rotating IP addresses:

EXPECTED_EXIT_IP=
ASSERT_SAME_PROXY_EXIT=false
ASSERT_DIFFERENT_FROM_DIRECT=true

The test verifies that each request succeeds and does not use the server's direct public IP.

No Direct Internet Access

When the test container cannot access the internet without the proxy:

ASSERT_DIFFERENT_FROM_DIRECT=false

Set EXPECTED_EXIT_IP when possible.

Applying Configuration Changes

After modifying .env, recreate the container.

Standalone:

docker compose \
  -f compose.yaml \
  -f compose.standalone.yaml \
  up -d --force-recreate

Traefik:

docker compose \
  -f compose.yaml \
  -f compose.traefik.yaml \
  up -d --force-recreate

External network:

docker compose \
  -f compose.yaml \
  -f compose.external-network.yaml \
  up -d --force-recreate

Diagnostics

View logs:

docker compose logs -f proxy

View service status:

docker compose ps

Inspect the final merged configuration:

docker compose \
  -f compose.yaml \
  -f compose.standalone.yaml \
  config

Inspect the generated 3proxy configuration:

docker compose cp \
  proxy:/etc/3proxy/3proxy.cfg \
  ./generated-3proxy.cfg

The generated configuration contains upstream credentials. Delete the copied file after debugging:

rm -f generated-3proxy.cfg

Security Considerations

  • Local clients are not required to authenticate.
  • Restrict access using CLIENT_ALLOW, firewall rules, a VPN, or a private network.
  • Do not expose an unrestricted proxy to the public internet.
  • Upstream credentials are stored in .env.
  • Docker administrators can inspect container environment variables.
  • Never commit .env.
  • Rotate credentials accidentally committed, logged, or shared publicly.
  • Review generated Compose and 3proxy configuration before deployment.
  • Pin the container image to a specific version when reproducible deployments are required.
  • Keep Docker, Docker Compose, Traefik, Nginx, and the base image updated.
  • Treat access to the Docker socket as administrative access to the host.

Limitations

  • The upstream must be an HTTP proxy.
  • SOCKS UDP forwarding is not supported.
  • SOCKS bind operations are not supported.
  • Client authentication is not enabled.
  • Reverse proxies must support raw TCP forwarding.
  • Normal hostname-based HTTP routing is not applicable to SOCKS traffic.
  • Hostname-based routing is generally not useful for a forward proxy because destination hostnames belong to outbound requests rather than the proxy service itself.

License

Use, modify, and distribute this project under the terms of the repository license.