# 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](https://github.com/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 ```text . ├── .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: ```text 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: ```text auth iponly ``` This does not require clients to provide a username or password. It enables 3proxy ACL and parent-routing processing. Using: ```text auth none ``` would disable the ACL chain and may cause requests to bypass the upstream proxy. ## Configuration Copy the example environment file: ```bash cp .env.example .env ``` At minimum, configure the upstream proxy: ```dotenv 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: ```dotenv 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 ```dotenv COMPOSE_PROJECT_NAME=egress-proxy THREEPROXY_IMAGE=ghcr.io/tarampampam/3proxy:2 ``` ### Upstream Proxy ```dotenv UPSTREAM_HOST=proxy.example.com UPSTREAM_PORT=8080 UPSTREAM_USERNAME=proxy-user UPSTREAM_PASSWORD='proxy-password' ``` ### Local Proxy Listeners ```dotenv PROXY_PORT=3128 SOCKS_PORT=1080 ``` ### DNS ```dotenv PRIMARY_RESOLVER=1.1.1.1 SECONDARY_RESOLVER=8.8.8.8 DNS_CACHE_SIZE=65536 ``` ### Limits ```dotenv MAX_CONNECTIONS=100 PARENT_RETRIES=3 NOFILE_SOFT=2048 NOFILE_HARD=4096 ``` ### Client Access Allow all clients: ```dotenv CLIENT_ALLOW=* ``` Allow a private network: ```dotenv CLIENT_ALLOW=192.168.0.0/16 ``` Allow several addresses or networks: ```dotenv CLIENT_ALLOW=192.168.0.0/16,203.0.113.10 ``` ### Listener Arguments Additional arguments can be appended to the generated listeners: ```dotenv 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: ```bash docker compose \ -f compose.yaml \ -f compose.standalone.yaml \ config ``` Traefik deployment: ```bash docker compose \ -f compose.yaml \ -f compose.traefik.yaml \ config ``` External network deployment: ```bash docker compose \ -f compose.yaml \ -f compose.external-network.yaml \ config ``` Test deployment: ```bash 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: ```dotenv BIND_ADDRESS=127.0.0.1 HTTP_PUBLIC_PORT=3128 SOCKS_PUBLIC_PORT=1080 ``` Start the stack: ```bash docker compose \ -f compose.yaml \ -f compose.standalone.yaml \ up -d ``` Test the HTTP proxy: ```bash curl \ --proxy http://127.0.0.1:3128 \ https://api.ipify.org ``` Test the SOCKS proxy: ```bash curl \ --proxy socks5h://127.0.0.1:1080 \ https://api.ipify.org ``` View logs: ```bash docker compose \ -f compose.yaml \ -f compose.standalone.yaml \ logs -f proxy ``` Stop the stack: ```bash docker compose \ -f compose.yaml \ -f compose.standalone.yaml \ down ``` ### LAN Access To expose the proxy on all server interfaces: ```dotenv BIND_ADDRESS=0.0.0.0 ``` Restrict clients when exposing the service outside localhost: ```dotenv 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: ```yaml 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: ```bash docker network create traefik ``` Configure the integration: ```dotenv TRAEFIK_NETWORK=traefik TRAEFIK_ROUTER_PREFIX=egress-proxy TRAEFIK_HTTP_ENTRYPOINT=proxy-http TRAEFIK_SOCKS_ENTRYPOINT=proxy-socks ``` Start the stack: ```bash 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: ```bash docker network create edge ``` Configure it: ```dotenv EDGE_NETWORK=edge EDGE_SERVICE_ALIAS=egress-proxy ``` Start the stack: ```bash docker compose \ -f compose.yaml \ -f compose.external-network.yaml \ up -d ``` Other containers attached to the same network can reach: ```text egress-proxy:3128 egress-proxy:1080 ``` ### Nginx Example Nginx must use the `stream` context rather than the regular `http` context: ```nginx 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: ```bash 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: ```dotenv 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: ```dotenv 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: ```dotenv ASSERT_DIFFERENT_FROM_DIRECT=false ``` Set `EXPECTED_EXIT_IP` when possible. ## Applying Configuration Changes After modifying `.env`, recreate the container. Standalone: ```bash docker compose \ -f compose.yaml \ -f compose.standalone.yaml \ up -d --force-recreate ``` Traefik: ```bash docker compose \ -f compose.yaml \ -f compose.traefik.yaml \ up -d --force-recreate ``` External network: ```bash docker compose \ -f compose.yaml \ -f compose.external-network.yaml \ up -d --force-recreate ``` ## Diagnostics View logs: ```bash docker compose logs -f proxy ``` View service status: ```bash docker compose ps ``` Inspect the final merged configuration: ```bash docker compose \ -f compose.yaml \ -f compose.standalone.yaml \ config ``` Inspect the generated 3proxy configuration: ```bash docker compose cp \ proxy:/etc/3proxy/3proxy.cfg \ ./generated-3proxy.cfg ``` The generated configuration contains upstream credentials. Delete the copied file after debugging: ```bash 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.