Tor Hidden Service Docker
A lightweight, secure Docker container for running Tor hidden services. Built on Alpine Linux with a focus on minimal footprint and maximum security.
- π Quick Start
- β¨ Features
- βοΈ How It Works
- π¦ Usage
- π§ Configuration
- π‘οΈ Security
- π Key Persistence
- π Examples
- π Health Checks
- π Troubleshooting
- π€ Contributing
# Run with a simple web service
docker run -d --name my-tor-service \
-e HS_WEB=web:80:80 \
ghcr.io/hundehausen/tor-hidden-service:latest
# View your .onion address
docker logs my-tor-serviceOr use Docker Compose:
git clone https://github.com/hundehausen/tor-hidden-service-docker.git
cd tor-hidden-service-docker
docker compose up -d
docker compose logs -f tor- πͺΆ Minimal footprint - Alpine Linux base (~44MB)
- ποΈ Easy hidden services - Configure via environment variables
- π Security-first - Runs as non-root user with minimal privileges
- π‘οΈ Input validation - Defense-in-depth against path traversal and injection
- π Auto-discovery - Onion addresses displayed in logs
- π Key persistence - Reuse existing keys across restarts
- π Health checks - Built-in container health monitoring
- π Graceful shutdown - Proper SIGTERM/SIGINT handling with 30s timeout
- ποΈ Multi-arch - Supports linux/amd64 and linux/arm64
- The container runs a Tor daemon that registers your services on the Tor network
- Each
HS_*environment variable creates a hidden service with its own.onionaddress - Incoming connections to a
.onionaddress are forwarded to the specified target container and port - A SOCKS proxy on port 9050 is available for outbound Tor connections
docker run -d --name tor-hidden-service \
-e HS_WEB=web-container:80:80 \
ghcr.io/hundehausen/tor-hidden-service:latestservices:
web:
image: nginx:alpine
container_name: web-container
volumes:
- ./example-site:/usr/share/nginx/html
networks:
- tor-network
tor:
image: ghcr.io/hundehausen/tor-hidden-service:latest
container_name: tor-hidden-service
environment:
- HS_WEB=web:80:80
- SOCKS_BIND=127.0.0.1
volumes:
- tor-data:/var/lib/tor
networks:
- tor-network
cap_drop:
- ALL
deploy:
resources:
limits:
memory: 256M
cpus: '0.5'
networks:
tor-network:
driver: bridge
volumes:
tor-data:From container logs:
docker logs tor-hidden-serviceOutput:
======== TOR HIDDEN SERVICES ========
WEB: 2xxiyj6noereyty4xdxjg5akcop7ylnotvf4fqre57g7xuppy4tixvqd.onion
API: 7gtpkyhhhsbowew7zha6z7h7vlffqkn3nbu2f6hgin4xddq22o7yytyd.onion
====================================
From the filesystem:
# Get specific service address
docker exec tor-hidden-service cat /var/lib/tor/WEB/hostname
# List all services
docker exec tor-hidden-service ls /var/lib/tor/Configure hidden services using the format:
HS_[SERVICE_NAME]=[TARGET_HOST]:[TARGET_PORT]:[VIRTUAL_PORT]
| Variable | Description | Example |
|---|---|---|
SERVICE_NAME |
Unique identifier (alphanumeric, hyphens, underscores) | WEB, API, BLOG |
TARGET_HOST |
Hostname or container name | web, api.internal |
TARGET_PORT |
Port the service listens on | 80, 8080 |
VIRTUAL_PORT |
(Optional) Port exposed on .onion address, defaults to TARGET_PORT |
80 |
Examples:
# Single service
HS_WEB=web-container:80:80
# Multiple services (each gets a unique .onion address)
HS_WEB=web:80:80
HS_API=api:8080:80
HS_BLOG=blog:3000:80The container exposes a SOCKS5 proxy on port 9050 for routing traffic through Tor.
By default, the proxy binds to 0.0.0.0 (all interfaces). For production, restrict this to localhost:
environment:
- SOCKS_BIND=127.0.0.1Note: The default will change to
127.0.0.1in a future major version.
You can also pass services as arguments:
docker run -d \
ghcr.io/hundehausen/tor-hidden-service:latest \
web:web-container:80:80 \
api:api-container:8080:80- π« Drop capabilities - Add
cap_drop: [ALL]to your compose file - π Set resource limits - Prevent resource exhaustion (see Usage)
- π Restrict SOCKS proxy - Set
SOCKS_BIND=127.0.0.1in production - πΎ Persist keys securely - Mount
/var/lib/toras a volume, never commit keys to git
The hs_ed25519_secret_key files in /var/lib/tor/[service]/ are the cryptographic identity of your .onion address. If compromised, an attacker can impersonate your service.
The entrypoint script validates all inputs:
- Service names - Alphanumeric, hyphens, and underscores only (max 64 chars)
- Ports - Numeric, range 1-65535
- Hostnames - No shell metacharacters allowed
- Path traversal - Prevented via allowlist and path verification
volumes:
- tor-data:/var/lib/torvolumes:
- tor-data:/var/lib/tor
- ./backup-keys/WEB:/var/lib/tor/WEB # Restore specific service
- ./backup-keys/API:/var/lib/tor/API# Copy keys from running container
docker cp tor-hidden-service:/var/lib/tor/WEB ./backup-keys/
# Or archive the entire volume
docker run --rm -v tor-data:/data -v $(pwd):/backup alpine \
tar czf /backup/tor-keys.tar.gz -C /data .services:
nginx:
image: nginx:alpine
volumes:
- ./website:/usr/share/nginx/html:ro
networks:
- tor
tor:
image: ghcr.io/hundehausen/tor-hidden-service:latest
environment:
- HS_SITE=nginx:80:80
- SOCKS_BIND=127.0.0.1
volumes:
- tor-keys:/var/lib/tor
cap_drop:
- ALL
networks:
- tor
networks:
tor:
volumes:
tor-keys:services:
web:
image: nginx:alpine
networks:
- tor
api:
image: node:18-alpine
networks:
- tor
tor:
image: ghcr.io/hundehausen/tor-hidden-service:latest
environment:
- HS_WEBSITE=web:80:80
- HS_API=api:3000:80
- HS_ADMIN=api:8080:80
- SOCKS_BIND=127.0.0.1
volumes:
- tor-data:/var/lib/tor
cap_drop:
- ALL
networks:
- tor
networks:
tor:
volumes:
tor-data:The container includes a health check that verifies Tor network connectivity:
- Interval: 300 seconds (5 minutes)
- Timeout: 3 seconds
- Test: Connects to
check.torproject.orgvia SOCKS proxy
Check health status:
docker ps --filter name=tor-hidden-service --format "table {{.Names}}\t{{.Status}}"Can't connect to hidden service
- Verify Tor bootstrap:
docker logs tor-hidden-service | grep "Bootstrapped 100%" - Check service configuration:
docker exec tor-hidden-service cat /etc/tor/torrc - Ensure target container is reachable:
docker exec tor-hidden-service ping web
Keys are not being persisted. Add a volume for /var/lib/tor:
volumes:
- tor-data:/var/lib/torSet resource limits in your compose file:
deploy:
resources:
limits:
memory: 256MContributions are welcome! Please open an issue or submit a pull request on GitHub.
Disclaimer: This tool is for legitimate privacy purposes only. Users are responsible for complying with all applicable laws and regulations in their jurisdiction.