# ServicePulse Forward Headers for Reverse Proxy Configuration
> [!NOTE]
> This page is **not** relevant if:
> - [integrated ServicePulse](/servicecontrol/servicecontrol-instances/integrated-servicepulse.md) is used. Integrated ServicePulse uses [the forward header configuration of the ServiceControl Error instance](/servicecontrol/security/configuration/forward-headers.md) that hosts it.
> - the [ServicePulse static files have been extracted](/servicepulse/install-servicepulse-in-iis.md), and is being hosted in anything other than the [Container](/servicepulse/containerization/index.md) or [Windows Service](/servicepulse/installation.md) hosting options provided. Forward header configuration is only required if using a reverse proxy.
When ServicePulse is deployed behind a reverse proxy that terminates SSL/TLS (like nginx, Traefik, or a cloud load balancer), you need to configure forwarded headers so ServicePulse correctly understands the original client request.
## Configuration
There are two hosting options for ServiceControl, [Container](/servicepulse/containerization/index.md) and [Windows Service](/servicepulse/installation.md). The container is configured via environment variables, while the Windows Service is configured using command-line arguments. See the [Hosting Guide](../hosting-guide.md) for example usage of these configuration settings, along with [Authentication](authentication.md) and [TLS](tls.md), in a scenario-based format.
### Container
- [Container forward header settings](/servicepulse/containerization/index.md#settings-forward-headers)
### Windows Service
| Command-Line Argument | Default | Description |
|---------------------------------------|---------|------------------------------------------------------------------|
| `--forwardedheadersenabled=` | `true` | Enable forwarded headers processing |
| `--forwardedheaderstrustallproxies=` | `true` | Trust all proxies (auto-disabled if known proxies/networks set) |
| `--forwardedheadersknownproxies=` | (none) | Comma-separated IP addresses of trusted proxies |
| `--forwardedheadersknownnetworks=` | (none) | Comma-separated CIDR networks (e.g. `10.0.0.0/8,172.16.0.0/12`) |
Example:
```cmd
"C:\Program Files (x86)\Particular Software\ServicePulse\ServicePulse.Host.exe" --forwardedheadersenabled=true --forwardedheaderstrustallproxies=true
```
## What Headers Are Processed
When enabled, ServicePulse processes:
- `X-Forwarded-For` - Original client IP address
- `X-Forwarded-Proto` - Original protocol (http/https)
- `X-Forwarded-Host` - Original host header
When the proxy is trusted:
- `Request.Scheme` will be set from `X-Forwarded-Proto` (e.g. `https`)
- `Request.Host` will be set from `X-Forwarded-Host` (e.g. `servicepulse.example.com`)
- Client IP will be available from `X-Forwarded-For`
When the proxy is **not** trusted (incorrect `KnownProxies`):
- `X-Forwarded-*` headers are **ignored** (not applied to the request)
- `Request.Scheme` remains `http`
- `Request.Host` remains the internal hostname
- The request is still processed (not blocked)
## Interaction with the Container Built-in Reverse Proxy
ServicePulse includes a [built-in YARP reverse proxy with the Container](/servicepulse/containerization/index.md#reverse-proxy) that forwards requests to ServiceControl Primary and Monitoring instances. The forwarded headers configuration does **not** affect this proxy.
```mermaid
sequenceDiagram
participant Client
participant Proxy as Upstream Proxy
participant SP as ServicePulse
participant YARP
participant SC as ServiceControl
Client->>Proxy: Request
Note over Proxy: Sets X-Forwarded-For
Sets X-Forwarded-Proto
Sets X-Forwarded-Host
Proxy->>SP: Request + X-Forwarded-* headers
Note over SP: UseForwardedHeaders
updates request context
SP->>YARP: Proxied request
YARP->>SC: Request to ServiceControl
```
- **UseForwardedHeaders** processes incoming headers from an upstream proxy so ServicePulse understands the original client request (scheme, host, client IP)
- **YARP** independently handles outgoing requests to ServiceControl/Monitoring backends
These operate at different points in the request flow and do not conflict.
## HTTP to HTTPS Redirect
When using a reverse proxy that terminates SSL, you can configure ServicePulse to redirect HTTP requests to HTTPS. This works in combination with forwarded headers:
1. The reverse proxy forwards both HTTP and HTTPS requests to ServicePulse
2. The proxy sets `X-Forwarded-Proto` to indicate the original protocol
3. ServicePulse reads this header (via forwarded headers processing)
4. If the original request was HTTP and redirect is enabled, ServicePulse returns a redirect to HTTPS
```mermaid
sequenceDiagram
participant Client
participant Proxy as Reverse Proxy
participant SP as ServicePulse
Client->>Proxy: HTTP Request
Note over Proxy: Terminates request
Sets X-Forwarded-Proto: http
Proxy->>SP: Request + X-Forwarded-Proto: http
Note over SP: Reads X-Forwarded-Proto
Detects original was HTTP
SP-->>Proxy: 301 Redirect to HTTPS
Proxy-->>Client: 301 Redirect to HTTPS
Client->>Proxy: HTTPS Request
Note over Proxy: Terminates SSL
Sets X-Forwarded-Proto: https
Proxy->>SP: Request + X-Forwarded-Proto: https
SP->>Client: Response (via Proxy)
```
To enable HTTP to HTTPS redirect, see [TLS Configuration](tls.md).
## Proxy Chain Behavior (ForwardLimit)
When processing `X-Forwarded-For` headers with multiple IPs (proxy chains), the behavior depends on trust configuration:
| Configuration | ForwardLimit | Behavior |
|---------------------------|-------------------|-----------------------------------------------|
| `TrustAllProxies = true` | `null` (no limit) | Processes all IPs, returns original client IP |
| `TrustAllProxies = false` | `1` (default) | Processes only the last proxy IP |
For example, with `X-Forwarded-For: 203.0.113.50, 10.0.0.1, 192.168.1.1`:
- **TrustAllProxies = true**: Returns `203.0.113.50` (original client)
- **TrustAllProxies = false**: Returns `192.168.1.1` (last proxy)
## Configuration examples
The following examples show common forward header configurations for different deployment scenarios.
### Single reverse proxy (known IP)
When running behind a single reverse proxy with a known IP address:
**Container:**
```bash
docker run -e SERVICEPULSE_FORWARDEDHEADERS_ENABLED=true \
-e SERVICEPULSE_FORWARDEDHEADERS_TRUSTALLPROXIES=false \
-e SERVICEPULSE_FORWARDEDHEADERS_KNOWNPROXIES=10.0.0.5 \
...
particular/servicepulse:latest
```
**Windows Service:**
```cmd
ServicePulse.Host.exe --forwardedheadersenabled=true --forwardedheaderstrustallproxies=false --forwardedheadersknownproxies=10.0.0.5
```
### Multiple reverse proxies
When running behind multiple proxies (e.g. load balancer and application gateway):
**Container:**
```bash
docker run -e SERVICEPULSE_FORWARDEDHEADERS_ENABLED=true \
-e SERVICEPULSE_FORWARDEDHEADERS_TRUSTALLPROXIES=false \
-e SERVICEPULSE_FORWARDEDHEADERS_KNOWNPROXIES=10.0.0.5,10.0.0.6 \
...
particular/servicepulse:latest
```
**Windows Service:**
```cmd
ServicePulse.Host.exe --forwardedheadersenabled=true --forwardedheaderstrustallproxies=false --forwardedheadersknownproxies=10.0.0.5,10.0.0.6
```
### Container/Kubernetes environment
When running in a container environment where proxy IPs are dynamic, trust a network range:
```bash
docker run -e SERVICEPULSE_FORWARDEDHEADERS_ENABLED=true \
-e SERVICEPULSE_FORWARDEDHEADERS_TRUSTALLPROXIES=false \
-e SERVICEPULSE_FORWARDEDHEADERS_KNOWNNETWORKS=10.0.0.0/8 \
...
particular/servicepulse:latest
```
### Development/trusted environment
For development or fully trusted environments (not recommended for production):
**Container:**
```bash
docker run -e SERVICEPULSE_FORWARDEDHEADERS_ENABLED=true \
-e SERVICEPULSE_FORWARDEDHEADERS_TRUSTALLPROXIES=true \
...
particular/servicepulse:latest
```
**Windows Service:**
```cmd
ServicePulse.Host.exe --forwardedheadersenabled=true --forwardedheaderstrustallproxies=true
```
## Troubleshooting
### HTTPS redirect loops
**Symptom**: Browser shows "too many redirects" or redirect loop errors when accessing the application through a reverse proxy with SSL termination.
**Cause**: The `X-Forwarded-Proto` header is not being processed, so the application thinks the request is HTTP and keeps redirecting to HTTPS.
**Solutions**:
- Verify forwarded headers are enabled
- Check that the proxy IP is in `KnownProxies` or `KnownNetworks`, or enable `TrustAllProxies`
- Confirm the reverse proxy is sending the `X-Forwarded-Proto` header
### Wrong host in generated URLs
**Symptom**: Links or redirects use the internal hostname (e.g. `localhost` or container name) instead of the public hostname.
**Cause**: The `X-Forwarded-Host` header is not being trusted or processed.
**Solutions**:
- Verify the proxy IP is trusted (check `KnownProxies`/`KnownNetworks` configuration)
- Confirm the reverse proxy is sending the `X-Forwarded-Host` header
- In Kubernetes, ensure the correct pod/node network is in `KnownNetworks`
### Client IP shows proxy IP instead of real client
**Symptom**: Logs or audit trails show the proxy's IP address instead of the actual client IP.
**Cause**: The `X-Forwarded-For` header is not being processed, or the wrong IP is being extracted from a proxy chain.
**Solutions**:
- Verify forwarded headers are enabled and the proxy is trusted
- For proxy chains, check the `ForwardLimit` behavior — with `TrustAllProxies=false`, only the last proxy IP is used
- If using multiple proxies, ensure all proxy IPs are listed in `KnownProxies`
### Headers not processed in Kubernetes
**Symptom**: Forward headers work locally but not in Kubernetes, even with headers enabled.
**Cause**: In Kubernetes, the ingress controller or load balancer IP may differ from what's configured in `KnownProxies`.
**Solutions**:
- Use `KnownNetworks` with the pod/service CIDR range instead of specific IPs (e.g. `10.0.0.0/8`)
- Check what IP the request is actually coming from (ingress controller pod IP, node IP, or load balancer IP)
- For development/testing, temporarily enable `TrustAllProxies=true` to confirm headers are the issue
### Verifying header processing
To confirm whether forwarded headers are being processed correctly:
1. Enable verbose logging to see incoming request details
2. Check that `Request.Scheme` shows `https` (not `http`) when accessing via HTTPS-terminating proxy
3. Verify the `Host` header in logs matches the expected public hostname
4. Compare client IP in logs against the expected client IP