11 KiB
Tor Support
Routstr Core includes built-in support for Tor hidden services, enabling anonymous access to your API and enhanced privacy for users.
Overview
Tor support provides:
- Anonymous Access: Hidden service (.onion) address
- Enhanced Privacy: No IP address logging
- Censorship Resistance: Accessible from restricted networks
- Optional Usage: Regular HTTP/HTTPS access remains available
Docker Setup
Using Docker Compose
The included compose.yml automatically sets up Tor:
version: '3.8'
services:
routstr:
build: .
environment:
- TOR_PROXY_URL=socks5://tor:9050
ports:
- 8000:8000
tor:
image: ghcr.io/hundehausen/tor-hidden-service:latest
volumes:
- tor-data:/var/lib/tor
environment:
- HS_ROUTER=routstr:8000:80
depends_on:
- routstr
volumes:
tor-data:
Start with:
docker compose up -d
Getting Your Onion Address
After starting, retrieve your hidden service address:
# View Tor logs
docker compose logs tor
# Or directly from the hostname file
docker exec tor cat /var/lib/tor/hidden_service/hostname
Your onion address will look like:
roustrjfsdgfiueghsklchg.onion
Manual Tor Setup
Install Tor
# Ubuntu/Debian
sudo apt-get install tor
# macOS
brew install tor
# Start Tor
sudo systemctl start tor
Configure Hidden Service
Edit /etc/tor/torrc:
# Hidden service configuration
HiddenServiceDir /var/lib/tor/routstr/
HiddenServicePort 80 127.0.0.1:8000
# Optional: Restrict to v3 addresses
HiddenServiceVersion 3
Restart Tor:
sudo systemctl restart tor
Get onion address:
sudo cat /var/lib/tor/routstr/hostname
Client Configuration
Using Tor with Python
import httpx
from openai import OpenAI
# Configure SOCKS proxy
proxies = {
"http://": "socks5://127.0.0.1:9050",
"https://": "socks5://127.0.0.1:9050"
}
# Create client with Tor
http_client = httpx.Client(proxies=proxies)
client = OpenAI(
api_key="rstr_your_key",
base_url="http://roustrjfsdgfiueghsklchg.onion/v1",
http_client=http_client
)
# Use normally
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "Hello via Tor!"}]
)
Using Tor with cURL
# Install torify
sudo apt-get install torsocks
# Make request through Tor
torify curl http://roustrjfsdgfiueghsklchg.onion/v1/models
# Or with explicit proxy
curl --socks5 127.0.0.1:9050 http://roustrjfsdgfiueghsklchg.onion/v1/models
JavaScript/Node.js
import { SocksProxyAgent } from 'socks-proxy-agent';
import OpenAI from 'openai';
// Create SOCKS agent
const agent = new SocksProxyAgent('socks5://127.0.0.1:9050');
// Configure OpenAI client
const openai = new OpenAI({
apiKey: 'rstr_your_key',
baseURL: 'http://roustrjfsdgfiueghsklchg.onion/v1',
httpAgent: agent
});
// Use normally
const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: 'Hello via Tor!' }]
});
Configuration
Environment Variables
Configure Tor proxy for outgoing connections:
# .env
TOR_PROXY_URL=socks5://tor:9050 # Docker
# or
TOR_PROXY_URL=socks5://127.0.0.1:9050 # Local
Publishing Onion Address
Make your onion address discoverable:
# .env
ONION_URL=http://roustrjfsdgfiueghsklchg.onion
This will be included in:
/v1/infoendpoint- Nostr announcements
- Admin dashboard
Security Considerations
Hidden Service Security
-
Keep Private Key Secure
# Backup hidden service keys sudo tar -czf tor-keys-backup.tar.gz /var/lib/tor/routstr/ # Restore to maintain same address sudo tar -xzf tor-keys-backup.tar.gz -C / -
Access Control
# Restrict to authenticated clients HiddenServiceAuthorizeClient stealth client1,client2 -
Rate Limiting
# In torrc HiddenServiceMaxStreams 100 HiddenServiceMaxStreamsCloseCircuit 1
Operational Security
-
Separate Tor Instance
# Use dedicated Tor container tor: image: ghcr.io/hundehausen/tor-hidden-service:latest restart: always networks: - tor_network -
Monitor Tor Health
async def check_tor_connection(): """Verify Tor connectivity.""" try: async with httpx.AsyncClient( proxies={"all://": TOR_PROXY_URL} ) as client: response = await client.get( "https://check.torproject.org/api/ip" ) data = response.json() return data.get("IsTor", False) except Exception: return False -
Logging Considerations
# Don't log .onion addresses with IPs def sanitize_logs(message: str) -> str: # Remove IP addresses when .onion is present if ".onion" in message: message = re.sub(r'\d+\.\d+\.\d+\.\d+', '[IP]', message) return message
Performance Optimization
Connection Pooling
# Reuse Tor circuits
class TorConnectionPool:
def __init__(self, proxy_url: str):
self.proxy_url = proxy_url
self._clients = []
async def get_client(self) -> httpx.AsyncClient:
if not self._clients:
client = httpx.AsyncClient(
proxies={"all://": self.proxy_url},
timeout=httpx.Timeout(30.0),
limits=httpx.Limits(
max_keepalive_connections=5,
max_connections=10
)
)
self._clients.append(client)
return self._clients[0]
Circuit Management
# Rotate Tor circuits periodically
async def rotate_tor_circuit():
"""Signal Tor to create new circuit."""
async with httpx.AsyncClient() as client:
# Tor control port (requires configuration)
response = await client.post(
"http://localhost:9051",
data="AUTHENTICATE\r\nSIGNAL NEWNYM\r\n"
)
Caching Strategies
# Cache responses for Tor users
@lru_cache(maxsize=1000)
def get_cached_response(
endpoint: str,
params_hash: str
) -> Optional[dict]:
"""Cache frequently accessed data."""
# Longer cache for Tor users due to latency
return cache.get(f"tor:{endpoint}:{params_hash}")
Monitoring
Tor Metrics
Track Tor-specific metrics:
class TorMetrics:
def __init__(self):
self.tor_requests = 0
self.tor_errors = 0
self.circuit_builds = 0
self.average_latency = 0
async def record_request(
self,
duration: float,
success: bool
):
self.tor_requests += 1
if not success:
self.tor_errors += 1
# Update average latency
self.average_latency = (
(self.average_latency * (self.tor_requests - 1) + duration)
/ self.tor_requests
)
Health Checks
@router.get("/health/tor")
async def tor_health():
"""Check Tor service health."""
checks = {
"tor_proxy": await check_tor_proxy(),
"hidden_service": await check_hidden_service(),
"circuit_established": await check_circuit()
}
status = "healthy" if all(checks.values()) else "unhealthy"
return {
"status": status,
"checks": checks,
"metrics": {
"tor_requests_total": metrics.tor_requests,
"tor_error_rate": metrics.tor_errors / max(metrics.tor_requests, 1),
"average_latency_ms": metrics.average_latency * 1000
}
}
Troubleshooting
Common Issues
Hidden Service Not Accessible
# Check Tor logs
docker compose logs tor
# or
sudo journalctl -u tor
# Verify service is running
sudo systemctl status tor
# Test locally
curl --socks5 127.0.0.1:9050 http://your-onion.onion/v1/info
Slow Connection
- Tor adds 3+ hops of latency
- Use connection pooling
- Implement aggressive caching
- Consider increasing timeouts
Connection Errors
# Implement Tor-specific retry logic
async def tor_retry(func, max_retries=5):
for attempt in range(max_retries):
try:
return await func()
except httpx.ProxyError:
if attempt < max_retries - 1:
# Exponential backoff for circuit building
await asyncio.sleep(2 ** attempt)
else:
raise
Debugging
Enable Tor debug logging:
# In torrc
Log debug file /var/log/tor/debug.log
# Monitor in real-time
tail -f /var/log/tor/debug.log
Best Practices
For Operators
-
Backup Hidden Service Keys
- Store securely offline
- Enables service recovery
- Maintains same .onion address
-
Monitor Tor Health
- Check circuit establishment
- Track request latency
- Alert on failures
-
Separate Concerns
- Run Tor in separate container
- Isolate from main application
- Use internal networks
For Users
-
Verify Onion Addresses
- Check against multiple sources
- Bookmark verified addresses
- Watch for phishing
-
Handle Higher Latency
- Increase client timeouts
- Implement retries
- Use connection pooling
-
Enhance Privacy
- Use Tor Browser for web access
- Avoid mixing Tor/clearnet
- Don't include identifying info
Advanced Configuration
Multi-Hop Onion Services
For extra security, chain multiple Tor instances:
# compose.yml
services:
tor-entry:
image: tor:latest
command: tor -f /etc/tor/torrc.entry
tor-middle:
image: tor:latest
command: tor -f /etc/tor/torrc.middle
tor-exit:
image: tor:latest
command: tor -f /etc/tor/torrc.exit
Onion Service Authentication
Require client authorization:
# Generate client auth
openssl rand -base64 32 > client_auth_key
# In torrc
HiddenServiceDir /var/lib/tor/routstr/
HiddenServicePort 80 127.0.0.1:8000
HiddenServiceAuthorizeClient stealth payments
Load Balancing
Distribute load across multiple instances:
# Onion service nginx config
upstream routstr_backends {
server routstr1:8000;
server routstr2:8000;
server routstr3:8000;
}
server {
listen 80;
location / {
proxy_pass http://routstr_backends;
}
}
Next Steps
- Nostr Discovery - Announce your onion service
- Security Guide - Security best practices
- Docker Setup - Container configuration