torrust-tracker-deployer 0.1.0

Torrust Tracker Deployer - Deployment Infrastructure with Ansible and OpenTofu
Documentation
# Tracker `on_reverse_proxy` is Global Instead of Per-HTTP-Tracker

**Issue Date**: January 16, 2026
**Affected Component**: Torrust Tracker Configuration
**Status**: Documented - Issue filed in tracker repository
**Upstream Issue**: [torrust/torrust-tracker#1640](https://github.com/torrust/torrust-tracker/issues/1640)

## Problem Description

The Torrust Tracker has a configuration option `[core.net].on_reverse_proxy` that controls whether the tracker expects `X-Forwarded-For` HTTP headers to determine the real client IP address. This setting is **global** and applies to **all HTTP trackers** in the deployment.

This creates a limitation: you cannot have some HTTP trackers behind a reverse proxy while others are accessed directly in the same deployment.

## How We Discovered This

While implementing HTTPS support with Caddy as a TLS-terminating reverse proxy in the [Torrust Tracker Deployer](https://github.com/torrust/torrust-tracker-deployer), we needed to configure the tracker to work behind Caddy.

Our use case:

- Multiple HTTP trackers on different ports (e.g., 7070, 7071, 7072)
- Some trackers exposed via HTTPS through Caddy (TLS termination)
- Some trackers exposed directly via HTTP (no proxy)

**Example configuration intent**:

```json
{
  "http_trackers": [
    {
      "bind_address": "0.0.0.0:7070",
      "domain": "http1.tracker.local",
      "use_tls_proxy": true
    },
    {
      "bind_address": "0.0.0.0:7071",
      "domain": "http2.tracker.local",
      "use_tls_proxy": true
    },
    {
      "bind_address": "0.0.0.0:7072"
    }
  ]
}
```

In this scenario:

- Trackers on ports 7070 and 7071 are behind Caddy (need `on_reverse_proxy = true`)
- Tracker on port 7072 is direct (needs `on_reverse_proxy = false`)

However, the current tracker configuration only allows:

```toml
[core.net]
on_reverse_proxy = true  # Applies to ALL HTTP trackers
```

## Root Cause

The `on_reverse_proxy` setting is defined in `[core.net]` which is a global network configuration section, not per-tracker. Looking at the tracker's network configuration structure:

**Reference**: [Torrust Tracker Network Configuration](https://docs.rs/torrust-tracker-configuration/latest/torrust_tracker_configuration/v2_0_0/network/struct.Network.html)

```rust
pub struct Network {
    // ...
    pub on_reverse_proxy: bool,  // Global setting
    // ...
}
```

Each HTTP tracker configuration does not have its own `on_reverse_proxy` field.

## Impact

### For Deployer Users

When `on_reverse_proxy = true` is set globally:

1. **All HTTP trackers expect `X-Forwarded-For` headers**
2. Trackers accessed directly (without proxy) will **fail to identify client IPs correctly**
3. The tracker will see the absence of `X-Forwarded-For` and may log warnings or behave unexpectedly

When `on_reverse_proxy = false` is set globally:

1. **All HTTP trackers ignore `X-Forwarded-For` headers**
2. Trackers behind a reverse proxy will **see the proxy's IP as the client IP**
3. All peers from different clients will appear to come from the same IP (the proxy)
4. This breaks peer identification in swarms

### Current Workaround in Deployer

We enforce a rule in the deployer:

> **If ANY HTTP tracker uses a TLS proxy, ALL HTTP trackers must use the TLS proxy.**

This is documented as a known limitation and validated during environment creation:

```text
Known Limitation (due to tracker's global setting):

If you have multiple HTTP trackers where some use use_tls_proxy and others don't,
the ones without it will still receive the global on_reverse_proxy = true setting
and may fail if they receive direct requests without X-Forwarded-For headers.

Workaround: Ensure all HTTP trackers in a deployment either ALL use the TLS proxy
or NONE use it.
```

This limitation reduces deployment flexibility and forces users into an all-or-nothing approach.

## Recommended Solution

Add an optional `on_reverse_proxy` field to each HTTP tracker configuration, allowing per-tracker control:

### Proposed Configuration Structure

```toml
[core.net]
on_reverse_proxy = false  # Default for trackers without explicit setting

[[http_trackers]]
bind_address = "0.0.0.0:7070"
on_reverse_proxy = true  # Override: this tracker is behind a proxy

[[http_trackers]]
bind_address = "0.0.0.0:7071"
on_reverse_proxy = true  # Override: this tracker is behind a proxy

[[http_trackers]]
bind_address = "0.0.0.0:7072"
# No override: uses global default (false) - direct access
```

### Behavior

1. If `on_reverse_proxy` is specified on an HTTP tracker, use that value
2. If not specified, fall back to `[core.net].on_reverse_proxy` (backward compatible)
3. Each HTTP tracker independently decides whether to read `X-Forwarded-For`

### Implementation Considerations

The HTTP tracker request handler would need to check its own `on_reverse_proxy` setting when extracting the client IP, rather than checking the global setting.

**Pseudocode change**:

```rust
// Before (global check)
fn get_client_ip(request: &Request, config: &Config) -> IpAddr {
    if config.core.net.on_reverse_proxy {
        extract_from_x_forwarded_for(request)
    } else {
        request.peer_addr()
    }
}

// After (per-tracker check)
fn get_client_ip(request: &Request, tracker_config: &HttpTrackerConfig) -> IpAddr {
    let on_reverse_proxy = tracker_config.on_reverse_proxy
        .unwrap_or(config.core.net.on_reverse_proxy);

    if on_reverse_proxy {
        extract_from_x_forwarded_for(request)
    } else {
        request.peer_addr()
    }
}
```

## Benefits of This Change

1. **Flexible deployments**: Mix proxied and direct HTTP trackers in one deployment
2. **Backward compatible**: Global setting remains the default
3. **Clearer intent**: Each tracker explicitly declares its network topology
4. **Better for edge cases**: Internal trackers (localhost) vs external (behind proxy)

## Use Cases Enabled

1. **Mixed TLS/non-TLS deployment**: Some trackers via HTTPS (Caddy), some via direct HTTP
2. **Internal monitoring**: Direct localhost tracker for Prometheus, proxied trackers for public access
3. **Gradual migration**: Move trackers behind proxy one at a time during migration
4. **Multi-tenant**: Different trackers for different networks with different proxy configurations

## References

- [Torrust Tracker Network Configuration Docs]https://docs.rs/torrust-tracker-configuration/latest/torrust_tracker_configuration/v2_0_0/network/struct.Network.html
- [Torrust Tracker Repository]https://github.com/torrust/torrust-tracker
- [Deployer Issue #272 - Add HTTPS Support]https://github.com/torrust/torrust-tracker-deployer/issues/272
- [Deployer PR #273 - HTTPS Implementation]https://github.com/torrust/torrust-tracker-deployer/pull/273