A lightweight, production-ready Tor SOCKS proxy container with a Go health/metrics sidecar. Designed for the *arr stack (Sonarr, Radarr, Prowlarr, etc.).
- Tor SOCKS Proxy: Exposes SOCKS5 on port 9050
- Health & Readiness: Kubernetes-compatible endpoints for liveness/readiness
- Tor Egress Verification: Optional external checks via
/ready - Circuit Renewal:
POST /renewsendsNEWNYMto request a new circuit - Prometheus Metrics:
/metricsendpoint + included Grafana dashboard - Multi-Architecture: Supports
linux/amd64andlinux/arm64 - Non-Root Runtime: Runs as a dedicated
toruser in the container
See docs/docker-compose.example.yml.
docker run -d \
--name torarr \
-p 127.0.0.1:9050:9050 \
-p 127.0.0.1:9091:9091 \
-e TZ=America/New_York \
-v tor-data:/var/lib/tor \
--restart unless-stopped \
ghcr.io/eslutz/torarr:latestAll application configuration is done via environment variables. Below is a quick reference of key settings. For a complete configuration template with all options, see docs/.env.example.
| Variable | Default | Description |
|---|---|---|
TZ |
UTC |
Container timezone |
LOG_LEVEL |
INFO |
Logging level (DEBUG, INFO, WARN, ERROR) |
| Variable | Default | Description |
|---|---|---|
HEALTH_PORT |
9091 |
HTTP server port for health/metrics |
HEALTH_EXTERNAL_TIMEOUT |
15 |
Timeout (seconds) for external Tor egress checks |
| Variable | Default | Description |
|---|---|---|
TOR_CONTROL_ADDRESS |
127.0.0.1:9051 |
Tor control port address |
TOR_CONTROL_PASSWORD |
(auto-generated) | Tor control password; auto-generated if unset |
TOR_EXIT_NODES |
(none) | Exit node selector (e.g. {us},{ca}) |
| Variable | Default | Description |
|---|---|---|
WEBHOOK_URL |
(none) | Webhook endpoint URL (Discord, Slack, etc.) |
WEBHOOK_TEMPLATE |
discord |
Webhook format: discord, slack, gotify, json |
WEBHOOK_EVENTS |
circuit_renewed,bootstrap_failed,health_changed |
Events to notify on (comma-separated) |
📝 Full Configuration: See docs/.env.example for all available options with detailed comments and examples.
┌────────────────────────────────────────────────────────────┐
│ Torarr │
├────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ control port ┌───────────────┐ │
│ │ Tor │◄──────────────────────►│ Healthserver │ │
│ │ SOCKS :9050 │ :9051 │ HTTP :9091 │ │
│ └──────┬───────┘ └───────────────┘ │
│ │ │
│ ▼ │
│ /var/lib/tor (mount as volume for faster restarts) │
└────────────────────────────────────────────────────────────┘How it works:
- The entrypoint hashes
TOR_CONTROL_PASSWORD(or generates one) and updates/etc/tor/torrc - Tor runs as the main process and exposes SOCKS5 on
:9050 - The Go health server queries Tor via the control port and exposes HTTP endpoints on
:${HEALTH_PORT} /readyverifies Tor egress by calling external endpoints through the SOCKS proxy
Tor uses the torrc file in the repository root (copied into the image at /etc/tor/torrc). The entrypoint modifies it at startup (control password hashing, optional exit nodes).
If you want to customize Tor settings, mount your own torrc as writable (the entrypoint needs to update HashedControlPassword).
| Endpoint | Purpose | Response |
|---|---|---|
GET /ping |
Liveness probe | 200 OK if running |
GET /health |
Tor bootstrap readiness | 200 OK when bootstrap is 100% |
GET /ready |
Tor egress verification | 200 OK if external check succeeds and IsTor=true |
GET /status |
Diagnostics | JSON status snapshot |
GET /metrics |
Prometheus metrics | OpenMetrics/Prometheus format |
POST /renew |
Request a new circuit | 200 OK if NEWNYM was sent |
- /ping: Liveness probe (restart container if it fails)
- /health: Readiness probe for Tor bootstrap
- /ready: Readiness probe when you need confirmed Tor egress (makes outbound requests)
- /status: Manual debugging/monitoring snapshot
- /metrics: Prometheus scraping target
| Metric | Type | Description |
|---|---|---|
torarr_info |
Gauge | Build information (version, commit, date, go_version) |
torarr_http_requests_total |
Counter | Total HTTP requests (labels: path, method, code) |
torarr_http_request_duration_seconds |
Histogram | HTTP request durations (labels: path, method, code) |
torarr_tor_bootstrap_percent |
Gauge | Tor bootstrap percent |
torarr_tor_circuit_established |
Gauge | Circuit established (1/0) |
torarr_tor_ready |
Gauge | Readiness derived from circuit state (1/0) |
torarr_tor_bytes_read |
Gauge | Bytes read (Tor traffic stats) |
torarr_tor_bytes_written |
Gauge | Bytes written (Tor traffic stats) |
torarr_external_check_total |
Counter | External check attempts (labels: endpoint, success, is_tor) |
torarr_webhook_requests_total |
Counter | Webhook notification attempts (labels: event, status) |
torarr_webhook_duration_seconds |
Histogram | Webhook notification duration (labels: event) |
Import docs/torarr-grafana-dashboard.json into Grafana.
Torarr can send webhook notifications for various events. Configure webhooks using the environment variables listed in the Configuration section.
- Discord: Rich embeds with color-coded events
- Slack: Attachments with formatted fields
- Gotify: Priority-based notifications
- JSON: Plain JSON payloads for custom integrations
| Event | Description |
|---|---|
circuit_renewed |
Triggered when POST /renew successfully sends NEWNYM |
bootstrap_failed |
Tor bootstrap is below 100%; fired on every /health check while unhealthy (can be very frequent) |
health_changed |
Health status changed (state transition only) |
Note:
bootstrap_failedis evaluated on each health check. In environments like Kubernetes where probes may run every few seconds, this can generate many webhook calls during bootstrap or outages. Consider:
- Using
health_changedfor primary alerting on state transitions- Enabling
bootstrap_failedonly when per-check visibility is required- Implementing rate limiting / deduplication on the webhook receiver to avoid notification spam
docker run -d \
--name torarr \
-p 127.0.0.1:9050:9050 \
-p 127.0.0.1:9091:9091 \
-e WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_ID \
-e WEBHOOK_TEMPLATE=discord \
-e WEBHOOK_EVENTS=circuit_renewed,bootstrap_failed \
ghcr.io/eslutz/torarr:latest-e WEBHOOK_URL=https://hooks.slack.com/services/YOUR_WEBHOOK_PATH \
-e WEBHOOK_TEMPLATE=slackReleases are driven by the VERSION file:
- CI runs on PRs and pushes to
main - After CI succeeds on
main, the release workflow readsVERSION - If the corresponding tag (e.g.
v0.1.0) doesn't exist, it creates the tag, pushes a multi-arch image, and creates a GitHub Release
To cut a new release: update VERSION and merge to main.
Contributions are welcome! Please follow these guidelines when submitting changes.
# Clone the repository
git clone https://github.com/eslutz/torarr.git
cd torarr
# Install dependencies
go mod download
# Build binary
go build -o healthserver ./cmd/healthserver
# Build Docker image
docker build -t torarr .# Run tests
go test ./...
# Run tests with race detector and coverage
go test -race -coverprofile=coverage.out -covermode=atomic ./...
# View coverage report
go tool cover -func=coverage.out
# Run linter
golangci-lint run
# Run locally (requires Tor installation)
export HEALTH_PORT=8085
export TOR_CONTROL_ADDRESS=127.0.0.1:9051
go run ./cmd/healthserverBefore submitting a pull request:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Run linters and tests
- Submit a pull request
See our Pull Request Template for more details.
Security is a top priority for this project. If you discover a security vulnerability, please follow responsible disclosure practices.
Reporting Vulnerabilities:
Please report security vulnerabilities through GitHub Security Advisories: https://github.com/eslutz/torarr/security/advisories/new
Alternatively, you can view our Security Policy for additional contact methods and guidelines.
Security Best Practices:
- Keep your installation up to date with the latest releases
- Never expose the SOCKS proxy port publicly; bind to localhost or private networks only
- Treat container logs as sensitive if using auto-generated control passwords
- Use strong, unique control passwords in production environments
- Regularly monitor logs for suspicious activity
- Consider using exit node restrictions for additional privacy
This project is licensed under the MIT License. See the LICENSE file for details.
You are free to use, modify, and distribute this software under the terms of the MIT License.
This project is built with and inspired by excellent open-source software:
- Tor Project - Anonymous communication network
- Prometheus - Monitoring system and time series database
- fsnotify - Cross-platform file system notifications for Go
Special thanks to the open-source community for their contributions and support.
- Forwardarr - Automatic port forwarding sync from Gluetun VPN to qBittorrent
- Unpackarr - Container-native archive extraction service for Sonarr, Radarr, and more