This guide covers security considerations and best practices when deploying Torrust Tracker using the deployer.
Security is a critical aspect of production deployments. The Torrust Tracker Deployer implements several security measures automatically during the deployment process, with additional considerations for production environments.
CRITICAL: The deployer uses a layered security approach combining UFW firewall and Docker networking to protect your deployment. Understanding how these layers work together is essential for secure deployments.
The deployer implements security at two levels:
-
Instance-Level Security (UFW) - Protects the VM itself
- Denies all incoming traffic by default
- Allows only SSH access for administration
- Does NOT control Docker container ports (Docker bypasses UFW)
-
Service-Level Security (Docker) - Controls service exposure
- Public services have explicit port bindings (Tracker, Grafana)
- Internal services have NO port bindings (MySQL)
- Localhost-only services bind to
127.0.0.1(Prometheus) - Docker network segmentation isolates service communication
Important: Docker manipulates iptables directly and bypasses UFW rules for published container ports. This is documented behavior (see Docker documentation).
# This port binding BYPASSES UFW firewall rules
services:
mysql:
ports:
- "3306:3306" # ⚠️ PUBLICLY ACCESSIBLE despite UFW rules!Docker routes container traffic in the NAT table, meaning packets are diverted before reaching the INPUT and OUTPUT chains that UFW uses. Therefore:
- ✅ UFW protects the VM and SSH access
- ❌ UFW does not protect Docker-published ports
- ✅ Docker port bindings control service exposure
During the configure command, the deployer:
- Installs UFW - Ensures the firewall is available
- Sets restrictive policies - Denies all incoming traffic by default
- Allows SSH access - Preserves SSH connectivity (configured port)
- Enables the firewall - Activates rules to protect SSH access
Note: UFW only controls SSH access. Application ports are controlled by Docker port bindings in the docker-compose configuration.
The Docker Compose configuration (templates/docker-compose/docker-compose.yml.tera) controls which services are accessible from the internet through explicit port bindings:
Service Exposure Levels:
services:
# ✅ PUBLIC SERVICES - Explicit port bindings
tracker:
ports:
- "6969:6969/udp" # Public - UDP tracker
- "7070:7070" # Public - HTTP tracker
- "1212:1212" # Public - REST API
grafana:
ports:
- "3100:3000" # Public - Monitoring UI (authenticated)
# 🔒 LOCALHOST-ONLY SERVICES - Bound to 127.0.0.1
prometheus:
ports:
- "127.0.0.1:9090:9090" # Accessible only from VM host
# 🔒 INTERNAL-ONLY SERVICES - No port bindings
mysql:
# No ports section - completely internal
# Accessed via Docker network: mysql:3306Security Properties:
- Public Services - Have
ports:section binding to0.0.0.0(accessible externally) - Localhost Services - Bind to
127.0.0.1(accessible only from VM host via SSH) - Internal Services - No port bindings (accessible only via Docker internal networks)
The deployer implements three isolated Docker networks for defense-in-depth security:
networks:
database_network: # Tracker ↔ MySQL only
metrics_network: # Tracker ↔ Prometheus only
visualization_network: # Prometheus ↔ Grafana only
services:
tracker:
networks:
- database_network # Can access MySQL
- metrics_network # Can be scraped by Prometheus
mysql:
networks:
- database_network # Isolated - only Tracker can access
prometheus:
networks:
- metrics_network # Can scrape Tracker metrics
- visualization_network # Can be queried by Grafana
grafana:
networks:
- visualization_network # Can query Prometheus onlySecurity Benefits:
- Reduced Attack Surface: MySQL accessible from 1 service (Tracker) instead of 3 services
- Lateral Movement Prevention: Compromised Grafana cannot access MySQL or Tracker
- Principle of Least Privilege: Services can only communicate where necessary
- Compliance: Aligns with PCI DSS, NIST 800-53, CIS Docker Benchmark
Without proper configuration
# INSECURE - All services on one network with public port bindings
services:
mysql:
ports:
- "3306:3306" # ⚠️ MySQL publicly accessible!
networks:
- backend_network- ❌ Internal services exposed to internet
- ❌ All services can communicate (no segmentation)
- ❌ Docker bypasses UFW firewall rules
- ❌ High attack surface
With deployer configuration ✅:
# SECURE - Network segmentation + no public port bindings
services:
mysql:
# No ports section - internal only
networks:
- database_network # Only Tracker can access- ✅ Internal services not publicly accessible
- ✅ Network segmentation limits lateral movement
- ✅ UFW protects SSH access
- ✅ Reduced attack surface
E2E Testing (Docker Containers):
- Uses Docker containers for faster test execution
- Firewall not configured inside containers (container isolation sufficient)
- Services may be exposed for testing purposes
⚠️ NOT production-grade security
Production Deployments (Virtual Machines):
- Uses real VMs (LXD, cloud providers)
- UFW configured automatically for SSH protection
- Docker port bindings control service exposure
- Network segmentation isolates services
- ✅ Production-ready security
The deployer configures these UFW firewall rules during configure:
# SSH Access (required for administration)
ufw allow <ssh-port>/tcp
# Default policies
ufw default deny incoming # Block all incoming traffic
ufw default allow outgoing # Allow outbound connections
ufw enable # Activate firewallNote: Application ports (Tracker, Grafana, Prometheus, MySQL) are not managed by UFW. They are controlled by Docker port bindings in the docker-compose.yml configuration.
DO:
- ✅ Use the deployer's default docker-compose template (network segmentation included)
- ✅ Review port bindings before deploying (
build/{env}/docker-compose/docker-compose.yml) - ✅ Keep internal services without port bindings (MySQL)
- ✅ Use
127.0.0.1bindings for localhost-only access (Prometheus) - ✅ Apply security updates to the VM regularly
- ✅ Use strong SSH keys and disable password authentication
- ✅ Monitor logs for suspicious activity
DON'T:
- ❌ Add port bindings to internal services (e.g.,
3306:3306for MySQL) - ❌ Disable UFW firewall on production VMs
- ❌ Remove network segmentation from docker-compose.yml
- ❌ Assume UFW protects Docker-published ports
- ❌ Expose Prometheus/MySQL publicly
- ❌ Use default passwords for services
After running the configure command, verify firewall rules:
# SSH into your VM
INSTANCE_IP=$(cat data/<env-name>/environment.json | jq -r '.Configured.context.runtime_outputs.instance_ip')
ssh -i <private-key> <username>@$INSTANCE_IP
# Check UFW status
sudo ufw status numbered
# Expected output shows:
# - SSH port allowed
# - Tracker ports allowed (UDP/HTTP/API)
# - Default deny incoming policy
# - All other ports blockedExample output:
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN Anywhere
[ 2] 6969/udp ALLOW IN Anywhere
[ 3] 7070/tcp ALLOW IN Anywhere
[ 4] 1212/tcp ALLOW IN Anywhere
Note that ports 9090 (Prometheus) and 3306 (MySQL) are not in this list, meaning they are blocked from external access.
The deployer requires SSH key-based authentication for VM access:
Best Practices:
-
Use strong SSH keys - Generate RSA keys with at least 4096 bits:
ssh-keygen -t rsa -b 4096 -f ~/.ssh/torrust_deploy -
Protect private keys - Set restrictive permissions:
chmod 600 ~/.ssh/torrust_deploy -
Use dedicated keys - Don't reuse personal SSH keys for deployments
-
Rotate keys regularly - Update SSH keys periodically
The default SSH port (22) is commonly targeted by automated attacks. Consider using a custom port:
{
"ssh_credentials": {
"port": 2222 // Custom SSH port
}
}Trade-offs:
- ✅ Reduces automated attack attempts
- ✅ Adds minimal security through obscurity
⚠️ Must remember custom port for manual access⚠️ Not a substitute for strong authentication
Services run in isolated Docker containers with:
- Network isolation - Backend network for inter-container communication
- Volume mounts - Limited filesystem access with
:ZSELinux labels - Resource limits - Logging limits prevent disk exhaustion
- Restart policies - Automatic recovery from failures
Current Images:
torrust/tracker:develop- Torrust Tracker (development tag)prom/prometheus:v3.0.1- Prometheus (pinned version)mysql:8.0- MySQL (major version pinned)
Recommendations:
- Pin specific versions - Use exact version tags in production
- Scan images regularly - Check for known vulnerabilities
- Update periodically - Apply security patches
- Use official images - Prefer official/verified images
Sensitive configuration is managed via .env files on the VM:
Best Practices:
- Strong passwords - Use complex, randomly generated passwords
- Unique credentials - Different passwords per environment
- Secure storage - Never commit
.envfiles to version control - Rotation policy - Update passwords periodically
Example (DO NOT use these values):
# Bad - Weak passwords
MYSQL_ROOT_PASSWORD=password123
MYSQL_PASSWORD=tracker
# Good - Strong, unique passwords
MYSQL_ROOT_PASSWORD=7k#mP9$vL2@qX5nR8jW
MYSQL_PASSWORD=xF4!hT6@dN9$sK2mQ7wEThe deployer follows the principle of least exposure:
Public Services (accessible externally):
- UDP Tracker - Required for BitTorrent protocol
- HTTP Tracker - Required for HTTP-based tracker operations
- HTTP API - Required for tracker management and metrics
Internal Services (blocked by firewall):
- Prometheus UI - Metrics collection (internal monitoring only)
- MySQL Database - Data storage (internal access only)
Services communicate via Docker's backend_network:
- Container-to-container communication allowed
- Isolated from host network by default
- DNS resolution via container names (e.g.,
tracker,mysql,prometheus)
Before deploying to production, verify:
- Virtual machines used (not Docker containers for testing)
- Firewall configured (
configurecommand completed successfully) - SSH key authentication (password authentication disabled)
- Custom SSH port (optional but recommended)
- Firewall rules verified (
ufw statusshows expected rules)
- Strong SSH keys (4096-bit RSA minimum)
- Strong database passwords (randomly generated, complex)
- Unique API tokens (per environment, rotated regularly)
- No credentials in git (
.envfiles gitignored) - Secure key storage (restricted permissions on private keys)
- Pinned image versions (not using
latestordeveloptags) - Image scanning enabled (vulnerability checks in CI/CD)
- Logging configured (audit trail and debugging)
- Resource limits set (prevent resource exhaustion)
- Regular updates scheduled (security patches applied)
- Prometheus UI not exposed (firewall blocks port 9090)
- Database not exposed (firewall blocks port 3306)
- Access logs reviewed (regular security audits)
- Metrics monitored (unusual patterns detected)
If you suspect a security breach:
- Isolate the system - Disable network access if necessary
- Check logs - Review
data/logs/log.txtand container logs - Review firewall rules - Verify UFW configuration hasn't changed
- Rotate credentials - Update all passwords and keys immediately
- Update software - Apply latest security patches
- Report vulnerabilities - Contact maintainers for Torrust Tracker issues
Planned improvements for future releases:
- TLS/SSL support - HTTPS for HTTP tracker and API
- Certificate management - Automated Let's Encrypt integration
- Rate limiting - Protection against abuse
- Fail2ban integration - Automated IP blocking for failed attempts
- Security scanning - Automated vulnerability detection in CI/CD
- Audit logging - Detailed access logs for compliance
- User Guide - Main deployment guide
- Configuration Guide - Environment configuration details
- Services Guide - Service-specific security considerations
- UFW Documentation - Firewall configuration
- Docker Security Best Practices - Container security
- SSH Hardening Guide - SSH security best practices
- OWASP Top 10 - Web application security risks
Security is an ongoing process. If you have questions or discover security issues:
- Security Issues - Report privately to maintainers (do not open public issues)
- General Questions - GitHub Discussions
- Feature Requests - GitHub Issues
Stay secure! 🔒