Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@ TORPROXY_API_PORT=8080

# Tor SOCKS5 listener inside the container and host port mapping.
TORPROXY_SOCKS_PORT=9050

# Optional: Specific exit country code (e.g. DE, AT, US, or comma-separated DE,AT).
# TORPROXY_COUNTRY=DE

# Optional: Enforce strict exit node routing (1=strictly specified country, 0=allow fallback).
# TORPROXY_STRICT_NODES=1
1 change: 0 additions & 1 deletion .github/workflows/publish-dockerhub.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ jobs:

- name: Update Docker Hub Description
uses: peter-evans/dockerhub-description@v4
if: github.ref == 'refs/heads/main'
with:
username: hapheus
password: ${{ secrets.DOCKERHUB_TOKEN }}
Expand Down
23 changes: 21 additions & 2 deletions DOCKERHUB.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Source repository: [https://github.com/hapheus/torproxy](https://github.com/haph
- **Authenticated Tor Control**: Local Tor ControlPort is secured using ephemeral 32-byte filesystem cookie authentication.
- **Non-Root Execution**: Runs under a dedicated, unprivileged system user (`torproxy`), never as root.
- **Multi-Architecture**: Built natively for `linux/amd64`, `linux/arm64`, and `linux/arm/v7`.
- **Country-Specific Exit Nodes**: Route traffic through exit relays in specific countries (e.g. `TORPROXY_COUNTRY=DE` or `DE,AT`) backed by `tor-geoipdb`.
- **Zero Breaking Changes**: Guaranteed backward compatibility across minor releases.

---
Expand Down Expand Up @@ -51,6 +52,19 @@ services:
- "127.0.0.1:8080:8080"
```

### Option 3: Country-Specific Exit Proxy (e.g. Germany or Austria)

```bash
docker run -d \
--name torproxy \
--restart unless-stopped \
-e TORPROXY_COUNTRY=DE \
-p 127.0.0.1:8118:8118 \
-p 127.0.0.1:9050:9050 \
-p 127.0.0.1:8080:8080 \
hapheus/torproxy:latest
```

---

## Verifying Tor Connectivity
Expand All @@ -69,6 +83,9 @@ curl --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip

# 4. Request a new Tor identity / circuit
curl -X POST http://127.0.0.1:8080/reconnect

# 5. Dynamically switch exit country without restart
curl -X POST "http://127.0.0.1:8080/reconnect?country=DE"
```

---
Expand All @@ -84,9 +101,9 @@ The management API listens on port `8080` (HTTP):
| `/health` | `GET` | Multi-subsystem health check (`tor`, `proxies`, `egress`). Returns HTTP 200 when healthy, 503 during bootstrap/errors. |
| `/diagnostics` | `GET` | Uptime, reconnect counters, bootstrap details, and cache status. |
| `/metrics` | `GET` | Prometheus exposition format metrics. |
| `/reconnect` | `POST` (or `GET`) | Triggers `SIGNAL NEWNYM` to establish a new Tor circuit. |
| `/reconnect` | `POST` (or `GET`) | Triggers `SIGNAL NEWNYM` to establish a new circuit. Supports `?country=DE` query or JSON body. |
| `/disconnect` | `POST` (or `GET`) | Disables Tor network traffic (`SETCONF DisableNetwork=1`). |
| `/connect` | `POST` (or `GET`) | Re-enables Tor network traffic (`SETCONF DisableNetwork=0`). |
| `/connect` | `POST` (or `GET`) | Re-enables Tor network traffic (`SETCONF DisableNetwork=0`). Supports `?country=DE` query or JSON body. |

> [!WARNING]
> The management API is unauthenticated by default. Bind it strictly to `127.0.0.1` or place it behind an authenticating reverse proxy if exposed across networks.
Expand All @@ -101,6 +118,8 @@ The management API listens on port `8080` (HTTP):
| `TORPROXY_LISTEN_PORT` | `8118` | Privoxy HTTP proxy port. |
| `TORPROXY_SOCKS_PORT` | `9050` | Tor SOCKS5 proxy port. |
| `TORPROXY_API_PORT` | `8080` | REST API port. |
| `TORPROXY_COUNTRY` | *(unset)* | Comma-separated ISO 3166-1 alpha-2 codes (e.g. `DE`, `AT`, `US`, `DE,AT`) to restrict exit nodes. |
| `TORPROXY_STRICT_NODES` | `0` | If `1`/`true`, refuse circuits if nodes in the requested country/countries are unavailable. |

---

Expand Down
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

[![PR Quality & Smoke Gate](https://github.com/hapheus/torproxy/actions/workflows/pr-gate.yml/badge.svg)](https://github.com/hapheus/torproxy/actions/workflows/pr-gate.yml)
[![Daily Live Image Verification](https://github.com/hapheus/torproxy/actions/workflows/daily-image-verification.yml/badge.svg)](https://github.com/hapheus/torproxy/actions/workflows/daily-image-verification.yml)
[![Docker Pulls](https://img.shields.io/docker/pulls/hapheus/torproxy)](https://hub.docker.com/r/hapheus/torproxy)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

A minimal, secure, and reliable Tor HTTP and SOCKS5 proxy packaged for Docker. `torproxy` routes traffic through the Tor network with fail-closed protection, non-root execution, an authenticated Tor control interface, and a lightweight JSON REST API for circuit rotation and health monitoring.
Expand Down Expand Up @@ -43,6 +44,18 @@ curl -v --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip

# 4. Check status via the local REST API
curl http://127.0.0.1:8080/status

# 5. Optional: Route exit traffic through a specific country (e.g. DE or AT)
TORPROXY_COUNTRY=DE docker compose up -d
# or multiple countries:
TORPROXY_COUNTRY=DE,AT docker compose up -d

# 6. Dynamically switch exit country on reconnect without container restart:
curl -X POST "http://127.0.0.1:8080/reconnect?country=DE"
# or with JSON payload:
curl -X POST http://127.0.0.1:8080/reconnect \
-H "Content-Type: application/json" \
-d '{"country": "AT", "strict_nodes": true}'
```

> **Security Note:** In `docker-compose.yml`, all ports bind strictly to `127.0.0.1` by default. Neither the proxy listeners nor the management API require authentication; do not expose them to untrusted networks.
Expand Down Expand Up @@ -70,9 +83,9 @@ The lightweight API server on port `8080` manages circuits and monitors health w
| `/health`, `/v1/health` | `GET` | `200`, `503` | Structured diagnostic health state across Tor, proxies, and egress. | `{"status":"healthy","tor":{"bootstrapped":true,"progress":100},"proxies":{"socks5":true},"egress":{"verified":true,"ip":"..."}}` |
| `/diagnostics`, `/v1/diagnostics` | `GET` | `200` | Operational telemetry (uptime, reconnect counts, bootstrap progress). | `{"uptime_seconds":120,"reconnect_total":2,"tor":{"bootstrapped":true}}` |
| `/metrics`, `/v1/metrics` | `GET` | `200` | Prometheus exposition format metrics for scrapers. | Plaintext Prometheus metrics. |
| `/reconnect` | `POST` (or `GET`) | `200` | Request a new Tor circuit (`SIGNAL NEWNYM`). Clears IP cache. | `{"action":"reconnect","status":"success"}` |
| `/reconnect` | `POST` (or `GET`) | `200`, `400` | Request new circuit (`SIGNAL NEWNYM`). Supports `?country=DE` or JSON body. Clears IP cache. | `{"action":"reconnect","status":"success","exit_country":"DE"}` |
| `/disconnect` | `POST` (or `GET`) | `200` | Pause Tor network traffic (`DisableNetwork=1`). Fails closed. | `{"action":"disconnect","status":"success"}` |
| `/connect` | `POST` (or `GET`) | `200` | Resume Tor network traffic (`DisableNetwork=0`). | `{"action":"connect","status":"success"}` |
| `/connect` | `POST` (or `GET`) | `200`, `400` | Resume Tor network traffic (`DisableNetwork=0`). Supports `?country=DE` or JSON body. | `{"action":"connect","status":"success"}` |

> **HTTP Methods:** `POST` is recommended for state mutations (`/reconnect`, `/disconnect`, `/connect`). Legacy `GET` calls remain fully supported for backward compatibility.
> Detailed schema definitions are documented in [docs/api.md](docs/api.md).
Expand Down Expand Up @@ -111,6 +124,8 @@ Default settings require no configuration. You can customize listening ports and
| `TORPROXY_HOST_PORT` | `8118` | `1`–`65535` | Host port mapped to HTTP proxy in `docker-compose.yml`. |
| `TORPROXY_API_PORT` | `8080` | `1024`–`65535` | REST API listening port (host and container). |
| `TORPROXY_SOCKS_PORT` | `9050` | `1024`–`65535` | Tor SOCKS5 proxy port (host and container). |
| `TORPROXY_COUNTRY` | *(unset)* | ISO 3166-1 alpha-2 (e.g. `DE`, `AT`, `US`, `DE,AT`) | Route Tor exit circuits through specific countries using `tor-geoipdb`. |
| `TORPROXY_STRICT_NODES` | `0` | `0`, `1`, `true`, `false`, `yes`, `no` | When `1`/`true`, reject circuits if configured country exit nodes are unavailable. |

All variables are strictly validated at startup. Port conflicts or out-of-range values trigger an immediate diagnostic failure. See [docs/configuration.md](docs/configuration.md) for full details.

Expand Down
4 changes: 2 additions & 2 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ FROM debian:bookworm-slim

LABEL org.opencontainers.image.title="torproxy" \
org.opencontainers.image.description="A minimal HTTP proxy routed through Tor" \
org.opencontainers.image.version="1.2.0" \
org.opencontainers.image.version="1.3.0" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.source="https://github.com/hapheus/torproxy" \
org.opencontainers.image.url="https://github.com/hapheus/torproxy"

RUN apt-get update \
&& apt-get install --no-install-recommends -y ca-certificates curl privoxy tini tor python3 \
&& apt-get install --no-install-recommends -y ca-certificates curl privoxy tini tor tor-geoipdb python3 \
&& rm -rf /var/lib/apt/lists/* \
&& useradd --system --home-dir /var/lib/tor --shell /usr/sbin/nologin torproxy \
&& install -d -m 0700 -o torproxy -g torproxy /var/lib/tor \
Expand Down
Loading
Loading