Issue: #234 Parent Epic: #1 - Roadmap (Item 6: Add HTTPS support) Related:
- Pingoo Website
- Pingoo GitHub
- Pingoo Documentation
- Current nginx.conf in Torrust Demo
- Split Torrust Demo into Index and Tracker
This issue is a research and experimentation task to evaluate Pingoo as a simpler alternative to nginx+certbot for adding HTTPS termination to deployed Torrust Tracker environments.
The goal is to determine if Pingoo can replace the complex nginx+certbot setup currently used in the Torrust Demo, specifically for:
- Automatic TLS certificate generation (Let's Encrypt)
- Automatic certificate renewal
- HTTPS termination for multiple services
Note: The current Torrust Demo at
torrust-demo.comincludes both the Index and the Tracker in a single deployment:
https://index.torrust-demo.com/- Index UI and APIhttps://tracker.torrust-demo.com/api/v1/stats- Tracker APIhttps://grafana.torrust-demo.com/- Grafana UIHowever, this deployer is focused only on the Tracker. There is a plan to split the demo into two separate independently deployable services (see torrust-demo#79). The nginx configuration referenced below includes Index-related settings that are not relevant to this deployer.
The current approach uses nginx + certbot with significant complexity:
┌─────────────────────────────────────────────────────────────────┐
│ Current Setup (nginx + certbot) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌───────────────────────────────────────┐ │
│ │ Certbot │────▶│ Manual initial certificate generation│ │
│ │ Container │ │ (must run once manually) │ │
│ └─────────────┘ └───────────────────────────────────────┘ │
│ │ │
│ │ Cronjob for renewal │
│ ▼ │
│ ┌─────────────┐ ┌───────────────────────────────────────┐ │
│ │ Nginx │────▶│ Complex configuration with: │ │
│ │ Proxy │ │ - Port 80 for ACME challenge │ │
│ └─────────────┘ │ - Port 443 for HTTPS services │ │
│ │ │ - SSL certificate paths │ │
│ │ │ - Manual header configuration │ │
│ │ │ - WebSocket upgrade for Grafana │ │
│ ▼ └───────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Services (Tracker-only scope for this deployer): │ │
│ │ - Tracker API (port 1212) → https://tracker.domain/api/ │ │
│ │ - HTTP Tracker (port 7070) → https://tracker.domain/ │ │
│ │ - Grafana UI (port 3000) → https://grafana.domain/ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Problems with current approach:
- Complex initial setup: Must manually run certbot to generate first certificate
- Manual renewal: Requires a bash script/cronjob for certificate renewal
- Verbose configuration: ~200+ lines of nginx.conf for SSL, headers, locations
- Special handling for WebSockets: Grafana Live requires specific HTTP upgrade configuration
- Multiple domain certificates: Each subdomain needs separate certificate management
Pingoo promises automatic certificate management with minimal configuration:
# Potential Pingoo configuration
listeners:
https:
address: https://0.0.0.0:443
tls:
acme:
domains:
[
"api.torrust-tracker.com",
"http1.torrust-tracker.com",
"grafana.torrust-tracker.com",
]
services:
tracker_api:
route: host == "api.torrust-tracker.com"
http_proxy: ["http://tracker:1212"]
tracker_http:
route: host == "http1.torrust-tracker.com"
http_proxy: ["http://tracker:7070"]
grafana:
route: host == "grafana.torrust-tracker.com"
http_proxy: ["http://grafana:3000"]The Torrust Demo originally used a single domain (tracker.torrust-demo.com) with path-based routing:
https://tracker.torrust-demo.com/api/→ Tracker API (port 1212)https://tracker.torrust-demo.com/→ HTTP Tracker (port 7070)
Problem: This approach doesn't scale to multiple tracker instances. You can only map port 443 once per subdomain.
For these experiments and future production use, we'll use subdomain-based routing:
| Service | Domain | Internal Port | Protocol |
|---|---|---|---|
| Tracker REST API | api.torrust-tracker.com |
1212 | HTTPS |
| HTTP Tracker 1 | http1.torrust-tracker.com |
7070 | HTTPS |
| UDP Tracker 1 | udp1.torrust-tracker.com |
6969 | UDP (no TLS) |
| Grafana UI | grafana.torrust-tracker.com |
3000 | HTTPS |
Benefits:
- Each service has its own subdomain → independent TLS certificates
- Supports multiple tracker instances (
http1,http2, etc.) - Clear separation of concerns
- Easier firewall rules and monitoring
Note: UDP trackers don't use HTTPS - they use the UDP protocol directly on their designated port.
- Learn: Understand how Pingoo handles automatic TLS certificate management
- Experiment: Test Pingoo with real services in the existing Hetzner environment
- Validate: Determine if Pingoo supports all required features (WebSockets, routing)
- Document: Create decision documentation for future HTTPS implementation
- Compare configuration complexity between Pingoo and nginx+certbot
- Measure certificate renewal reliability
- Evaluate Pingoo's Docker service discovery feature
- Full implementation of HTTPS in the deployer (separate issue)
- Deciding the final proxy architecture (one proxy vs per-service proxies)
- Production deployment configuration
Based on Pingoo documentation:
| Feature | Status | Notes |
|---|---|---|
| ACME/Let's Encrypt | ✅ Supported | Automatic certificate generation |
| Auto Renewal | ✅ Automatic | Managed via acme.json |
| TLS Termination | ✅ Supported | TLS 1.3 only (security focused) |
| HTTP Proxy | ✅ Supported | With load balancing |
| Docker Service Discovery | ✅ Supported | Via pingoo.service label |
| Wildcard Certificates | ❌ Not supported | With ACME |
| WebSocket Support | ❓ Unknown | Needs testing |
From documentation:
Pingoo currently only supports the tls-alpn-01 challenge. It means that one of your TLS listeners must be publicly accessible on the port
443.
Important: This differs from nginx+certbot which uses HTTP-01 challenge (port 80). Pingoo requires port 443 to be publicly accessible.
The current nginx.conf shows:
Active HTTP servers (port 80):
# index.torrust-demo.com - Proxies to index:3001 and index-gui:3000
# tracker.torrust-demo.com - Proxies to tracker:1212 (API) and tracker:7070 (HTTP)
# grafana.torrust-demo.com - Proxies to grafana:3000
# All include /.well-known/acme-challenge for Let's Encrypt HTTP-01 challengeHTTPS configuration (commented out but available):
- Separate SSL certificates per subdomain
- Manual SSL configuration (protocols, ciphers, dhparam)
- WebSocket upgrade handling for Grafana:
# This is required to proxy Grafana Live WebSocket connections.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream grafana {
server grafana:3000;
}
location /api/live/ {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_pass http://grafana;
}Duration: 1-2 hours
- Verify existing Hetzner environment (
docker-hetzner-test) is running - Document current services and their internal ports
- Obtain/configure domain names pointing to the Hetzner server IP
- Ensure ports 443 and 80 are open on the server firewall
Duration: 1-2 hours
Objective: Test Pingoo certificate generation in isolation with a minimal setup
This experiment uses an independent docker-compose configuration with only:
- Pingoo (TLS proxy)
- A simple nginx container serving static "Hello World" content
This isolates the certificate generation testing from any tracker-specific complexity.
- Create a new directory for the experiment (e.g.,
experiments/pingoo-hello-world/) - Create minimal
docker-compose.ymlwith Pingoo + nginx static server - Create
pingoo.ymlconfiguration for a single domain - Create simple
index.htmlwith "Hello World" content - Deploy to the Hetzner server
- Verify automatic certificate generation
- Test HTTPS access to the static page
- Document results and any issues
Directory Structure:
experiments/pingoo-hello-world/
├── docker-compose.yml
├── pingoo/
│ └── pingoo.yml
└── www/
└── index.html
docker-compose.yml:
services:
pingoo:
image: pingooio/pingoo:latest
ports:
- "443:443"
volumes:
- ./pingoo:/etc/pingoo
networks:
- test-network
depends_on:
- webserver
webserver:
image: nginx:alpine
volumes:
- ./www:/usr/share/nginx/html:ro
networks:
- test-network
# No exposed ports - only accessible via Pingoo
networks:
test-network:
driver: bridgepingoo/pingoo.yml:
# pingoo.yml - Experiment 1: Minimal Hello World
listeners:
https:
address: https://0.0.0.0:443
tls:
acme:
domains: ["test.torrust-tracker.com"]
services:
static:
http_proxy: ["http://webserver:80"]www/index.html:
<!DOCTYPE html>
<html>
<head>
<title>Pingoo Test</title>
</head>
<body>
<h1>Hello World!</h1>
<p>If you see this page via HTTPS, Pingoo certificate generation works!</p>
<p>Certificate info: Check browser padlock for details.</p>
</body>
</html>Success Criteria:
-
https://test.torrust-tracker.comshows the Hello World page - Browser shows valid Let's Encrypt certificate
- No manual certificate generation required
Duration: 2-4 hours
Objective: Add Pingoo to the existing tracker stack to serve Tracker API via HTTPS
- Create
pingoo.ymlconfiguration for Tracker API - Add Pingoo service to the existing tracker docker-compose stack
- Configure domain:
api.torrust-tracker.com - Test HTTPS access to Tracker API endpoints
- Verify automatic certificate generation
- Document results and any issues
Expected Configuration:
# pingoo.yml - Experiment 2
listeners:
https:
address: https://0.0.0.0:443
tls:
acme:
domains: ["api.torrust-tracker.com"]
services:
tracker_api:
http_proxy: ["http://tracker:1212"]Docker Compose Addition:
# docker-compose.yml addition
pingoo:
image: pingooio/pingoo:latest
ports:
- "443:443"
volumes:
- ./pingoo:/etc/pingoo
networks:
- tracker-networkDuration: 2-4 hours
Objective: Extend configuration to serve HTTP Tracker via HTTPS
- Update
pingoo.ymlto add HTTP tracker routing - Configure domain:
http1.torrust-tracker.com - Test HTTPS access to announce/scrape endpoints
- Verify BitTorrent clients can use HTTPS tracker URL
- Document results and any issues
Expected Configuration Update:
# pingoo.yml - Experiment 3
listeners:
https:
address: https://0.0.0.0:443
tls:
acme:
domains: ["api.torrust-tracker.com", "http1.torrust-tracker.com"]
services:
tracker_api:
route: http_request.host == "api.torrust-tracker.com"
http_proxy: ["http://tracker:1212"]
tracker_http:
route: http_request.host == "http1.torrust-tracker.com"
http_proxy: ["http://tracker:7070"]Duration: 3-5 hours
Objective: Test Pingoo's WebSocket support for Grafana Live
- Update
pingoo.ymlto add Grafana routing - Configure domain:
grafana.torrust-tracker.com - Test basic Grafana UI access via HTTPS
- Critical: Test Grafana Live WebSocket functionality
- Monitor for connection issues or dropped connections
- Document WebSocket support findings
Expected Configuration Update:
# pingoo.yml - Experiment 4
listeners:
https:
address: https://0.0.0.0:443
tls:
acme:
domains:
[
"api.torrust-tracker.com",
"http1.torrust-tracker.com",
"grafana.torrust-tracker.com",
]
services:
tracker_api:
route: http_request.host == "api.torrust-tracker.com"
http_proxy: ["http://tracker:1212"]
tracker_http:
route: http_request.host == "http1.torrust-tracker.com"
http_proxy: ["http://tracker:7070"]
grafana:
route: http_request.host == "grafana.torrust-tracker.com"
http_proxy: ["http://grafana:3000"]WebSocket Test Procedure:
- Open Grafana dashboard with real-time metrics
- Navigate to a dashboard using Grafana Live (real-time updates)
- Open browser DevTools → Network → WS filter
- Verify WebSocket connection is established
- Monitor for ~5 minutes for stability
- Check for any reconnection attempts or errors
Duration: 2-3 hours
- Create research directory:
docs/research/pingoo-tls-proxy-evaluation/ - Document each experiment's results in separate files
- Write conclusion with recommendation
- If adopting Pingoo: Create ADR in
docs/decisions/ - Document any limitations or workarounds found
- Propose next steps for HTTPS implementation issue
Quality Checks:
- All experiments completed and documented
- Pre-commit checks pass:
./scripts/pre-commit.sh(if code changes are made)
Experiment 1 Criteria (Minimal Hello World):
- Static page accessible via HTTPS
- Certificate automatically generated by Pingoo (no manual steps)
- Browser shows valid Let's Encrypt certificate
- Independent docker-compose setup works without tracker stack
Experiment 2 Criteria (Tracker API):
- Tracker API accessible via HTTPS
- Certificate automatically generated by Pingoo
- API endpoints respond correctly (
/api/v1/stats, etc.)
Experiment 3 Criteria (HTTP Tracker):
- HTTP Tracker accessible via HTTPS
- Announce endpoint works (
/announce) - Scrape endpoint works (
/scrape)
Experiment 4 Criteria (Grafana):
- Grafana UI accessible via HTTPS
- Login and dashboard navigation works
- WebSocket test: Grafana Live functionality verified (or documented as not working)
Documentation Criteria:
- Research directory created:
docs/research/pingoo-tls-proxy-evaluation/ - Each experiment documented with results
- Clear recommendation: proceed with Pingoo, stick with nginx+certbot, or evaluate alternatives
- Known limitations documented
- ADR created if decision is made to adopt Pingoo
- WebSocket Support: Does Pingoo automatically handle HTTP Upgrade headers for WebSocket connections?
- Certificate Storage: How does Pingoo persist certificates across container restarts?
- Multiple Domains: Can a single Pingoo instance handle multiple domains efficiently?
- TLS 1.2 Support: Pingoo only supports TLS 1.3 - is this a concern for older clients?
- Port 443 Requirement: The tls-alpn-01 challenge requires port 443 - does this conflict with existing services?
Since this is an evaluation/research task (not yet a decision), findings will be stored in:
docs/research/pingoo-tls-proxy-evaluation/
├── README.md # Overview and summary of findings
├── experiment-1-hello-world.md # Results from minimal static server test
├── experiment-2-tracker-api.md # Results from Tracker API HTTPS test
├── experiment-3-http-tracker.md # Results from HTTP Tracker HTTPS test
├── experiment-4-grafana.md # Results from Grafana WebSocket test
└── conclusion.md # Final recommendation and next steps
Progression of documentation:
- During research →
docs/research/pingoo-tls-proxy-evaluation/ - If we decide to adopt Pingoo → Create ADR in
docs/decisions/ - If we decide NOT to adopt → Document reasoning in
conclusion.mdand close issue
Existing Hetzner Environment: docker-hetzner-test
Configuration file: envs/docker-hetzner-test.json
Environment: docker-hetzner-test
Provider: Hetzner (ccx23 server, Ubuntu 24.04, nbg1 location)
Services:
- Tracker (UDP: 6969, HTTP: 7070, API: 1212)
- Prometheus (scrape interval: 15s)
- Grafana (admin/admin credentials configured)
After completing this research, a separate implementation issue will be created to address:
- Architecture Decision: Single proxy vs. per-service proxies
- Service Enable/Disable: How to handle optional services (e.g., Grafana disabled)
- Configuration Integration: How to template proxy configuration based on environment settings
- Fallback Strategy: What to do if Pingoo doesn't meet requirements (nginx+certbot, Caddy, Traefik)
- Pingoo Official Documentation
- Pingoo TLS/HTTPS Configuration
- Pingoo Services Configuration
- Let's Encrypt Challenge Types
- Current Torrust Demo nginx.conf
- Grafana WebSocket Requirements
Created: 2026-01-10 Last Updated: 2026-01-10 Status: Planning