CBTOR - Tor Management API
Anonymous web fetching through a load-balanced Tor worker pool with self-healing recovery.
Overview
CBTOR provides a REST API for routing web requests through the Tor network using 4 dedicated OpenWRT workers. Each worker runs Tor connected via WireGuard tunnels, with automatic health monitoring, circuit rotation, and self-healing recovery.
Live API: https://tor.nominate.ai
| Feature |
Description |
| Anonymous Fetch |
Route any URL through Tor (clearnet or .onion) |
| Load Balancing |
Round-robin, sticky, random, least-connections |
| Self-Healing |
Automatic recovery with exponential backoff |
| Circuit Rotation |
NEWNYM signals for fresh exit IPs |
API Endpoints
Core Operations
| Endpoint |
Method |
Description |
/api/v1/fetch |
POST |
Fetch URL anonymously via Tor |
/api/v1/health |
GET |
Worker pool health status |
/api/v1/mode |
POST |
Set load balancing mode |
/api/v1/endpoints |
GET |
Tor check endpoint statistics |
Worker Management
| Endpoint |
Method |
Description |
/api/v1/tor/workers |
GET |
List all Tor workers |
/api/v1/tor/workers/{n} |
GET |
Get specific worker status |
/api/v1/tor/workers/{n}/tor/start |
POST |
Start Tor service |
/api/v1/tor/workers/{n}/tor/stop |
POST |
Stop Tor service |
/api/v1/tor/workers/{n}/tor/restart |
POST |
Restart Tor service |
/api/v1/tor/status |
GET |
Cluster status summary |
/api/v1/workers/drain |
POST |
Remove worker from pool |
/api/v1/workers/enable |
POST |
Force-enable worker |
Architecture
┌─────────────────────────────────────┐
│ CBTOR API (tor.nominate.ai:32211) │
│ - Load Balancer │
│ - Health Manager │
│ - Recovery Logic │
└───────────────┬─────────────────────┘
│
┌───────────────┬───────────┼───────────┬───────────────┐
│ │ │ │ │
wg-tor01 wg-tor02 wg-tor03 wg-tor04 WireGuard
10.201.1.x 10.201.2.x 10.201.3.x 10.201.4.x Tunnels
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Worker 01 │ │ Worker 02 │ │ Worker 03 │ │ Worker 04 │
│ 17.0.0.134 │ │ 17.0.0.202 │ │ 17.0.0.201 │ │ 17.0.0.120 │
│ │ │ │ │ │ │ │
│ Tor SOCKS │ │ Tor SOCKS │ │ Tor SOCKS │ │ Tor SOCKS │
│ :9050 │ │ :9050 │ │ :9050 │ │ :9050 │
│ │ │ │ │ │ │ │
│ TransPort │ │ TransPort │ │ TransPort │ │ TransPort │
│ :9040 │ │ :9040 │ :9040 │ │ :9040 │
│ │ │ │ │ │ │ │
│ Control │ │ Control │ │ Control │ │ Control │
│ :9051 │ │ :9051 │ │ :9051 │ │ :9051 │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │ │
└───────────────┴───────────────┴───────────────┘
│
▼
┌─────────────────────────────────────┐
│ Tor Network │
│ (Entry → Middle → Exit nodes) │
└─────────────────────────────────────┘
Request Routing
| URL Type |
Routing Method |
Port |
Typical Latency |
| Clearnet (http/https) |
TransPort via WireGuard |
9040 |
1-5 seconds |
| .onion hidden service |
SOCKS5 proxy |
9050 |
10-30 seconds |
Worker Health States
HEALTHY ──(3 failures)──▶ SUSPECT ──(3 more failures)──▶ DEAD
▲ │
│ ▼
└─────────────────────────────────────────────────── RECOVERY
(success)
| State |
Description |
Receives Traffic |
healthy |
All checks passing |
Yes |
suspect |
3+ consecutive failures |
Yes (degraded) |
dead |
6+ failures, entering recovery |
No |
recovery |
Active recovery in progress |
No |
Timeout & Recovery Cycles
Host-Side Health Manager (API Server)
The API server runs continuous health monitoring every 10 seconds:
| Parameter |
Value |
Description |
health_check_interval |
10 sec |
Check all workers |
failure_threshold |
3 |
Failures before SUSPECT |
recovery_backoff |
1, 2, 4, 8, 16, 32 sec |
Exponential retry delays |
reboot_after_attempts |
6 |
Attempts before forcing reboot |
Recovery Sequence:
1. Check SSH reachable
2. Check SOCKS port (9050) responding
3. Restart Tor if needed (/etc/init.d/tor restart)
4. Reconnect WireGuard tunnel
5. Verify Tor circuit via check endpoints
Node-Side Self-Healing (Each Worker)
Each Tor worker runs its own health check every minute via cron:
| Parameter |
Value |
Description |
TEST_TIMEOUT |
30 sec |
.onion page load timeout |
NEWNYM_WAIT |
5 sec |
Wait after circuit rotation |
RESTART_WAIT |
30 sec |
Wait after Tor restart |
RECOVERY_WAIT |
15 min |
Stay offline before retry |
Node Recovery Sequence:
1. Test random .onion site via SOCKS
2. If fail → NEWNYM (new circuit), retry
3. If still fail → Restart Tor, retry
4. If still fail → Mark OFFLINE, stop Tor
5. After 15 min → Attempt recovery
Test Endpoints (.onion sites):
| Site |
URL |
| DuckDuckGo |
duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion |
| ProPublica |
p53lf57qovyuvwsc6xnrppyply3vtqm7l6pcobkmyqsiofyeznfu5uqd.onion |
| BBC News |
bbcnewsd73hkzno2ini43t4gblxvycyac5aw4gnv7t2rccijh7745uqd.onion |
| ProtonMail |
protonmailrmez3lotccipshtkleegetolb73fuirgj7r4o4vfu7ozyd.onion |
| Riseup |
vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion |
| Archive.org |
archiveiya74codqgiixo33q62qlrqtkgmcitqx5u2oeqnmn5bpcbiyd.onion |
Combined Recovery Timeline
0s Host detects failure
10s Host retries (failure_threshold not reached)
20s Host retries
30s Worker marked SUSPECT
40s Host retries
50s Host retries
60s Worker marked DEAD, recovery starts
Meanwhile: Node cron runs, detects issue
Node tries NEWNYM (5s wait)
Node tries Tor restart (30s wait)
If both fail: Node goes OFFLINE for 15 min
61s Host recovery: SSH check, SOCKS check
62s Host: Restart Tor via SSH
92s Host: Verify Tor circuit
If success → HEALTHY
If fail → Wait 1s (backoff[0])
93s Host retry
If fail → Wait 2s (backoff[1])
...
After 6 failed attempts → Host reboots worker
Wait 90s for reboot → Try again
Load Balancing Modes
| Mode |
Algorithm |
Use Case |
round_robin |
Rotate through workers |
Even distribution (default) |
sticky |
Hash-based consistent routing |
Session affinity |
random |
Random selection |
Simple distribution |
least_connections |
Pick least busy |
High throughput |
failover |
Always use first healthy |
Primary/backup pattern |
Quick Start
Basic Fetch
import requests
response = requests.post("https://tor.nominate.ai/api/v1/fetch", json={
"url": "https://check.torproject.org/api/ip"
})
data = response.json()
print(f"Routed via {data['worker']}, exit IP in body")
Sticky Session (Same Exit IP)
response = requests.post("https://tor.nominate.ai/api/v1/fetch", json={
"url": "https://example.com/login",
"mode": "sticky",
"sticky_key": "my-session-123"
})
Check Health
curl https://tor.nominate.ai/api/v1/health
Documentation Links
| Resource |
URL |
| Swagger UI |
https://tor.nominate.ai/docs |
| ReDoc |
https://tor.nominate.ai/redoc |
| OpenAPI Spec |
https://tor.nominate.ai/openapi.json |
Key Files
API Server (Host)
| File |
Purpose |
api/main.py |
FastAPI application |
api/routers/fetch.py |
Fetch and health endpoints |
api/routers/tor.py |
Worker management endpoints |
api/load_balancer/health.py |
Health manager with recovery |
api/load_balancer/pool.py |
Worker pool and load balancing |
scripts/cron/tor-warmup.sh |
Circuit warmup script |
Worker Nodes (OpenWRT)
| File |
Purpose |
/etc/tor/torrc |
Tor daemon configuration |
/etc/tor/onion-test-sites.txt |
Health check test URLs |
/usr/local/bin/tor-health-check.sh |
Self-healing script |
/var/run/tor/health-status |
Current state (online/offline) |
/var/log/tor-health.log |
Health check logs |