This guide explains how to test forwarded headers configuration for ServiceControl instances without using NGINX or Docker. This approach uses curl to manually send X-Forwarded-* headers directly to the instances.
- ServiceControl built locally (see main README for instructions)
- curl (included with Windows 10/11, Git Bash, or WSL)
- (Optional) For formatted JSON output:
npm install -g jsonthen pipe curl output through| json - All commands assume you are in the respective project directory
To enable detailed logging for troubleshooting, set the LogLevel environment variable before starting each instance:
rem ServiceControl Primary
set SERVICECONTROL_LOGLEVEL=Debug
rem ServiceControl.Audit
set SERVICECONTROL_AUDIT_LOGLEVEL=Debug
rem ServiceControl.Monitoring
set MONITORING_LOGLEVEL=DebugValid log levels: Trace, Debug, Information (or Info), Warning (or Warn), Error, Critical (or Fatal), None (or Off)
Debug logs will show detailed forwarded headers processing and trust evaluation information.
| Instance | Project Directory | Default Port | Environment Variable Prefix |
|---|---|---|---|
| ServiceControl (Primary) | src\ServiceControl |
33333 | SERVICECONTROL_ |
| ServiceControl.Audit | src\ServiceControl.Audit |
44444 | SERVICECONTROL_AUDIT_ |
| ServiceControl.Monitoring | src\ServiceControl.Monitoring |
33633 | MONITORING_ |
Note
Environment variables must include the instance prefix (e.g., SERVICECONTROL_FORWARDEDHEADERS_ENABLED for the primary instance).
When a ServiceControl instance is behind a reverse proxy, the proxy sends headers to indicate the original request details:
X-Forwarded-For- Original client IP addressX-Forwarded-Proto- Original protocol (http/https)X-Forwarded-Host- Original host header
Each instance can be configured to trust these headers from specific proxies or trust all proxies.
The middleware determines whether to process forwarded headers based on these rules:
- If
TrustAllProxies= true: All requests are trusted, headers are always processed - If
TrustAllProxies= false: The caller's IP must match either:- KnownProxies: Exact IP address match (e.g.,
127.0.0.1,::1) - KnownNetworks: CIDR range match (e.g.,
127.0.0.0/8,10.0.0.0/8)
- KnownProxies: Exact IP address match (e.g.,
Important
KnownProxies and KnownNetworks use OR logic - a match in either grants trust. The check is against the immediate caller's IP (the proxy connecting to ServiceControl), not the original client IP from X-Forwarded-For.
Settings can be configured via:
- Environment variables (recommended for testing) - Easy to change between scenarios, no file edits needed
- App.config - Persisted settings, requires app restart after changes
Both methods work identically. This guide uses environment variables for convenience during iterative testing.
Important
Set environment variables in the same terminal where you run dotnet run. Environment variables are scoped to the terminal session and won't be seen if you run from Visual Studio or a different terminal.
Check the application startup logs to verify which settings were applied. The forwarded headers configuration is logged at startup.
To minimize service restarts during testing, scenarios are grouped by configuration. Run all tests within a group before changing configuration:
| Configuration Group | Scenarios | Description |
|---|---|---|
| Group A: Default/TrustAllProxies | 0, 1, 2, 8, 11, 13 | Tests with default settings or explicit TrustAllProxies=true |
| Group B: KnownProxies (localhost) | 3, 9, 14 | Tests with KnownProxies=127.0.0.1,::1 |
| Group C: KnownNetworks (localhost) | 4 | Tests with KnownNetworks=127.0.0.0/8,::1/128 |
| Group D: Untrusted Proxy | 5 | Tests with KnownProxies=192.168.1.100 |
| Group E: Untrusted Network | 6 | Tests with KnownNetworks=10.0.0.0/8,192.168.0.0/16 |
| Group F: Disabled | 7 | Tests with Enabled=false |
| Group G: Combined | 10 | Tests with both KnownProxies and KnownNetworks |
| Group H: IPv4 Only | 12 | Tests with KnownProxies=127.0.0.1 (no IPv6) |
Start the instance once, then run all tests in this group (Scenarios 0, 1, 2, 8, 11, 13).
rem Cleanup and start - ServiceControl (Primary)
set SERVICECONTROL_FORWARDEDHEADERS_ENABLED=
set SERVICECONTROL_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNPROXIES=
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNNETWORKS=
rem ServiceControl.Audit
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_ENABLED=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNPROXIES=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNNETWORKS=
rem ServiceControl.Monitoring
set MONITORING_FORWARDEDHEADERS_ENABLED=
set MONITORING_FORWARDEDHEADERS_TRUSTALLPROXIES=
set MONITORING_FORWARDEDHEADERS_KNOWNPROXIES=
set MONITORING_FORWARDEDHEADERS_KNOWNNETWORKS=
dotnet runTest a direct request without any forwarded headers, simulating access without a reverse proxy.
Test with curl (no forwarded headers):
rem ServiceControl (Primary)
curl http://localhost:33333/debug/request-info | json
rem ServiceControl.Audit
curl http://localhost:44444/debug/request-info | json
rem ServiceControl.Monitoring
curl http://localhost:33633/debug/request-info | jsonExpected output:
{
"processed": {
"scheme": "http",
"host": "localhost:33333", // localhost:44444 or localhost:33633
"remoteIpAddress": "::1"
},
"rawHeaders": {
"xForwardedFor": "",
"xForwardedProto": "",
"xForwardedHost": ""
},
"configuration": {
"enabled": true,
"trustAllProxies": true,
"knownProxies": [],
"knownNetworks": []
}
}When no forwarded headers are sent, the request values remain unchanged.
Test the default behavior when no forwarded headers environment variables are set, but headers are sent.
Test with curl (using Group A configuration above):
rem ServiceControl (Primary)
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33333/debug/request-info | json
rem ServiceControl.Audit
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:44444/debug/request-info | json
rem ServiceControl.Monitoring
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33633/debug/request-info | jsonExpected output:
{
"processed": {
"scheme": "https",
"host": "example.com",
"remoteIpAddress": "203.0.113.50"
},
"rawHeaders": {
"xForwardedFor": "",
"xForwardedProto": "",
"xForwardedHost": ""
},
"configuration": {
"enabled": true,
"trustAllProxies": true,
"knownProxies": [],
"knownNetworks": []
}
}By default, forwarded headers are enabled and all proxies are trusted. This means any client can spoof X-Forwarded-* headers. This is suitable for development but should be restricted in production by configuring KnownProxies or KnownNetworks.
Explicitly enable trust all proxies (same as default, but explicit configuration). This scenario can be tested with the same Group A configuration - the behavior is identical.
Test with curl (using Group A configuration above):
rem ServiceControl (Primary)
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33333/debug/request-info | json
rem ServiceControl.Audit
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:44444/debug/request-info | json
rem ServiceControl.Monitoring
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33633/debug/request-info | jsonExpected output:
{
"processed": {
"scheme": "https",
"host": "example.com",
"remoteIpAddress": "203.0.113.50"
},
"rawHeaders": {
"xForwardedFor": "",
"xForwardedProto": "",
"xForwardedHost": ""
},
"configuration": {
"enabled": true,
"trustAllProxies": true,
"knownProxies": [],
"knownNetworks": []
}
}The scheme is https (from X-Forwarded-Proto), host is example.com (from X-Forwarded-Host), and remoteIpAddress is 203.0.113.50 (from X-Forwarded-For) because all proxies are trusted. The rawHeaders are empty because the middleware consumed them.
Test how ServiceControl handles multiple proxies in the chain.
Test with curl (using Group A configuration above, simulating a proxy chain):
rem ServiceControl (Primary)
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50, 10.0.0.1, 192.168.1.1" http://localhost:33333/debug/request-info | json
rem ServiceControl.Audit
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50, 10.0.0.1, 192.168.1.1" http://localhost:44444/debug/request-info | json
rem ServiceControl.Monitoring
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50, 10.0.0.1, 192.168.1.1" http://localhost:33633/debug/request-info | jsonExpected output:
{
"processed": {
"scheme": "https",
"host": "example.com",
"remoteIpAddress": "203.0.113.50"
},
"rawHeaders": {
"xForwardedFor": "",
"xForwardedProto": "",
"xForwardedHost": ""
},
"configuration": {
"enabled": true,
"trustAllProxies": true,
"knownProxies": [],
"knownNetworks": []
}
}The X-Forwarded-For header contains multiple IPs representing the proxy chain. When TrustAllProxies is true, ForwardLimit is set to null (no limit), so the middleware processes all IPs and returns the original client IP (203.0.113.50).
Test that each forwarded header is processed independently. Only sending X-Forwarded-Proto should update the scheme while leaving host and remoteIpAddress unchanged.
Test with curl (using Group A configuration above, only X-Forwarded-Proto):
rem ServiceControl (Primary)
curl -H "X-Forwarded-Proto: https" http://localhost:33333/debug/request-info | json
rem ServiceControl.Audit
curl -H "X-Forwarded-Proto: https" http://localhost:44444/debug/request-info | json
rem ServiceControl.Monitoring
curl -H "X-Forwarded-Proto: https" http://localhost:33633/debug/request-info | jsonExpected output:
{
"processed": {
"scheme": "https",
"host": "localhost:33333",
"remoteIpAddress": "::1"
},
"rawHeaders": {
"xForwardedFor": "",
"xForwardedProto": "",
"xForwardedHost": ""
},
"configuration": {
"enabled": true,
"trustAllProxies": true,
"knownProxies": [],
"knownNetworks": []
}
}Only the scheme changed to https. The host remains localhost:33333 and remoteIpAddress remains ::1 because those headers weren't sent. Each header is processed independently.
Test how ServiceControl handles multiple values in X-Forwarded-Proto and X-Forwarded-Host headers, which can occur in multi-proxy environments where each proxy adds its own values.
Test with curl (using Group A configuration above, simulating multiple proxy values):
rem ServiceControl (Primary)
curl -H "X-Forwarded-Proto: https, http" -H "X-Forwarded-Host: example.com, internal.proxy.local" -H "X-Forwarded-For: 203.0.113.50, 10.0.0.1" http://localhost:33333/debug/request-info | json
rem ServiceControl.Audit
curl -H "X-Forwarded-Proto: https, http" -H "X-Forwarded-Host: example.com, internal.proxy.local" -H "X-Forwarded-For: 203.0.113.50, 10.0.0.1" http://localhost:44444/debug/request-info | json
rem ServiceControl.Monitoring
curl -H "X-Forwarded-Proto: https, http" -H "X-Forwarded-Host: example.com, internal.proxy.local" -H "X-Forwarded-For: 203.0.113.50, 10.0.0.1" http://localhost:33633/debug/request-info | jsonExpected output:
{
"processed": {
"scheme": "https",
"host": "example.com",
"remoteIpAddress": "203.0.113.50"
},
"rawHeaders": {
"xForwardedFor": "",
"xForwardedProto": "",
"xForwardedHost": ""
},
"configuration": {
"enabled": true,
"trustAllProxies": true,
"knownProxies": [],
"knownNetworks": []
}
}When TrustAllProxies is true, ForwardLimit is set to null (no limit), so the middleware processes all values and returns the leftmost (original) values: scheme is https, host is example.com, and remoteIpAddress is 203.0.113.50.
Restart the instance with this configuration, then run all tests in this group (Scenarios 3, 9, 14).
rem ServiceControl (Primary)
set SERVICECONTROL_FORWARDEDHEADERS_ENABLED=true
set SERVICECONTROL_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNPROXIES=127.0.0.1,::1
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNNETWORKS=
rem ServiceControl.Audit
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_ENABLED=true
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNPROXIES=127.0.0.1,::1
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNNETWORKS=
rem ServiceControl.Monitoring
set MONITORING_FORWARDEDHEADERS_ENABLED=true
set MONITORING_FORWARDEDHEADERS_TRUSTALLPROXIES=
set MONITORING_FORWARDEDHEADERS_KNOWNPROXIES=127.0.0.1,::1
set MONITORING_FORWARDEDHEADERS_KNOWNNETWORKS=
dotnet runNote
Setting KNOWNPROXIES automatically disables TRUSTALLPROXIES. Both IPv4 (127.0.0.1) and IPv6 (::1) loopback addresses are included since curl may use either.
Only accept forwarded headers from specific IP addresses.
Test with curl (from localhost - should work):
rem ServiceControl (Primary)
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33333/debug/request-info | json
rem ServiceControl.Audit
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:44444/debug/request-info | json
rem ServiceControl.Monitoring
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33633/debug/request-info | jsonExpected output:
{
"processed": {
"scheme": "https",
"host": "example.com",
"remoteIpAddress": "203.0.113.50"
},
"rawHeaders": {
"xForwardedFor": "",
"xForwardedProto": "",
"xForwardedHost": ""
},
"configuration": {
"enabled": true,
"trustAllProxies": false,
"knownProxies": ["127.0.0.1", "::1"],
"knownNetworks": []
}
}Headers are applied because the request comes from localhost, which is in the known proxies list. The rawHeaders are empty because the middleware consumed them.
Test how ServiceControl handles multiple proxies when TrustAllProxies is false. In this case, ForwardLimit remains at its default of 1, so only the last proxy IP is processed.
Test with curl (using Group B configuration above, simulating a proxy chain):
rem ServiceControl (Primary)
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50, 10.0.0.1, 192.168.1.1" http://localhost:33333/debug/request-info | json
rem ServiceControl.Audit
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50, 10.0.0.1, 192.168.1.1" http://localhost:44444/debug/request-info | json
rem ServiceControl.Monitoring
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50, 10.0.0.1, 192.168.1.1" http://localhost:33633/debug/request-info | jsonExpected output:
{
"processed": {
"scheme": "https",
"host": "example.com",
"remoteIpAddress": "192.168.1.1"
},
"rawHeaders": {
"xForwardedFor": "203.0.113.50, 10.0.0.1",
"xForwardedProto": "",
"xForwardedHost": ""
},
"configuration": {
"enabled": true,
"trustAllProxies": false,
"knownProxies": ["127.0.0.1", "::1"],
"knownNetworks": []
}
}When TrustAllProxies is false, ForwardLimit remains at its default of 1. The middleware only processes the rightmost IP from the chain (192.168.1.1). The remaining IPs (203.0.113.50, 10.0.0.1) stay in the X-Forwarded-For header. Compare this to Scenario 8 where TrustAllProxies = true returns the original client IP.
Test how ServiceControl handles multiple X-Forwarded-Proto and X-Forwarded-Host values when TrustAllProxies is false. In this case, ForwardLimit remains at its default of 1, so only the rightmost value is processed.
Test with curl (using Group B configuration above, simulating multiple proxy values):
rem ServiceControl (Primary)
curl -H "X-Forwarded-Proto: https, http" -H "X-Forwarded-Host: example.com, internal.proxy.local" -H "X-Forwarded-For: 203.0.113.50, 10.0.0.1" http://localhost:33333/debug/request-info | json
rem ServiceControl.Audit
curl -H "X-Forwarded-Proto: https, http" -H "X-Forwarded-Host: example.com, internal.proxy.local" -H "X-Forwarded-For: 203.0.113.50, 10.0.0.1" http://localhost:44444/debug/request-info | json
rem ServiceControl.Monitoring
curl -H "X-Forwarded-Proto: https, http" -H "X-Forwarded-Host: example.com, internal.proxy.local" -H "X-Forwarded-For: 203.0.113.50, 10.0.0.1" http://localhost:33633/debug/request-info | jsonExpected output:
{
"processed": {
"scheme": "http",
"host": "internal.proxy.local",
"remoteIpAddress": "10.0.0.1"
},
"rawHeaders": {
"xForwardedFor": "203.0.113.50",
"xForwardedProto": "https",
"xForwardedHost": "example.com"
},
"configuration": {
"enabled": true,
"trustAllProxies": false,
"knownProxies": ["127.0.0.1", "::1"],
"knownNetworks": []
}
}When TrustAllProxies is false, ForwardLimit remains at its default of 1. The middleware only processes the rightmost value from each header: scheme is http, host is internal.proxy.local, and remoteIpAddress is 10.0.0.1. The remaining values stay in the raw headers. Compare this to Scenario 13 where TrustAllProxies = true returns the original (leftmost) values.
Restart the instance with this configuration (Scenario 4).
rem ServiceControl (Primary)
set SERVICECONTROL_FORWARDEDHEADERS_ENABLED=true
set SERVICECONTROL_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNPROXIES=
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNNETWORKS=127.0.0.0/8,::1/128
rem ServiceControl.Audit
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_ENABLED=true
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNPROXIES=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNNETWORKS=127.0.0.0/8,::1/128
rem ServiceControl.Monitoring
set MONITORING_FORWARDEDHEADERS_ENABLED=true
set MONITORING_FORWARDEDHEADERS_TRUSTALLPROXIES=
set MONITORING_FORWARDEDHEADERS_KNOWNPROXIES=
set MONITORING_FORWARDEDHEADERS_KNOWNNETWORKS=127.0.0.0/8,::1/128
dotnet runNote
Both IPv4 (127.0.0.0/8) and IPv6 (::1/128) loopback networks are included since curl may use either.
Trust all proxies within a network range.
Test with curl:
rem ServiceControl (Primary)
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33333/debug/request-info | json
rem ServiceControl.Audit
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:44444/debug/request-info | json
rem ServiceControl.Monitoring
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33633/debug/request-info | jsonExpected output:
{
"processed": {
"scheme": "https",
"host": "example.com",
"remoteIpAddress": "203.0.113.50"
},
"rawHeaders": {
"xForwardedFor": "",
"xForwardedProto": "",
"xForwardedHost": ""
},
"configuration": {
"enabled": true,
"trustAllProxies": false,
"knownProxies": [],
"knownNetworks": ["127.0.0.0/8", "::1/128"]
}
}Headers are applied because the request comes from localhost, which falls within the known networks. The rawHeaders are empty because the middleware consumed them.
Restart the instance with this configuration (Scenario 5).
rem ServiceControl (Primary)
set SERVICECONTROL_FORWARDEDHEADERS_ENABLED=true
set SERVICECONTROL_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNPROXIES=192.168.1.100
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNNETWORKS=
rem ServiceControl.Audit
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_ENABLED=true
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNPROXIES=192.168.1.100
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNNETWORKS=
rem ServiceControl.Monitoring
set MONITORING_FORWARDEDHEADERS_ENABLED=true
set MONITORING_FORWARDEDHEADERS_TRUSTALLPROXIES=
set MONITORING_FORWARDEDHEADERS_KNOWNPROXIES=192.168.1.100
set MONITORING_FORWARDEDHEADERS_KNOWNNETWORKS=
dotnet runConfigure a known proxy that doesn't match the request source to verify headers are ignored.
Test with curl:
rem ServiceControl (Primary)
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33333/debug/request-info | json
rem ServiceControl.Audit
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:44444/debug/request-info | json
rem ServiceControl.Monitoring
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33633/debug/request-info | jsonExpected output:
{
"processed": {
"scheme": "http",
"host": "localhost:33333",
"remoteIpAddress": "::1"
},
"rawHeaders": {
"xForwardedFor": "203.0.113.50",
"xForwardedProto": "https",
"xForwardedHost": "example.com"
},
"configuration": {
"enabled": true,
"trustAllProxies": false,
"knownProxies": ["192.168.1.100"],
"knownNetworks": []
}
}Headers are ignored because the request comes from localhost (::1), which is NOT in the known proxies list (192.168.1.100). Notice scheme is http (unchanged from original request). The rawHeaders still show the headers that were sent but not applied.
Restart the instance with this configuration (Scenario 6).
rem ServiceControl (Primary)
set SERVICECONTROL_FORWARDEDHEADERS_ENABLED=true
set SERVICECONTROL_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNPROXIES=
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNNETWORKS=10.0.0.0/8,192.168.0.0/16
rem ServiceControl.Audit
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_ENABLED=true
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNPROXIES=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNNETWORKS=10.0.0.0/8,192.168.0.0/16
rem ServiceControl.Monitoring
set MONITORING_FORWARDEDHEADERS_ENABLED=true
set MONITORING_FORWARDEDHEADERS_TRUSTALLPROXIES=
set MONITORING_FORWARDEDHEADERS_KNOWNPROXIES=
set MONITORING_FORWARDEDHEADERS_KNOWNNETWORKS=10.0.0.0/8,192.168.0.0/16
dotnet runConfigure a known network that doesn't match the request source to verify headers are ignored.
Test with curl:
rem ServiceControl (Primary)
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33333/debug/request-info | json
rem ServiceControl.Audit
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:44444/debug/request-info | json
rem ServiceControl.Monitoring
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33633/debug/request-info | jsonExpected output:
{
"processed": {
"scheme": "http",
"host": "localhost:33333",
"remoteIpAddress": "::1"
},
"rawHeaders": {
"xForwardedFor": "203.0.113.50",
"xForwardedProto": "https",
"xForwardedHost": "example.com"
},
"configuration": {
"enabled": true,
"trustAllProxies": false,
"knownProxies": [],
"knownNetworks": ["10.0.0.0/8", "192.168.0.0/16"]
}
}Headers are ignored because the request comes from localhost (::1), which is NOT in the known networks (10.0.0.0/8 or 192.168.0.0/16). Notice scheme is http (unchanged from original request). The rawHeaders still show the headers that were sent but not applied.
Restart the instance with this configuration (Scenario 7).
rem ServiceControl (Primary)
set SERVICECONTROL_FORWARDEDHEADERS_ENABLED=false
set SERVICECONTROL_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNPROXIES=
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNNETWORKS=
rem ServiceControl.Audit
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_ENABLED=false
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNPROXIES=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNNETWORKS=
rem ServiceControl.Monitoring
set MONITORING_FORWARDEDHEADERS_ENABLED=false
set MONITORING_FORWARDEDHEADERS_TRUSTALLPROXIES=
set MONITORING_FORWARDEDHEADERS_KNOWNPROXIES=
set MONITORING_FORWARDEDHEADERS_KNOWNNETWORKS=
dotnet runCompletely disable forwarded headers processing.
Test with curl:
rem ServiceControl (Primary)
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33333/debug/request-info | json
rem ServiceControl.Audit
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:44444/debug/request-info | json
rem ServiceControl.Monitoring
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33633/debug/request-info | jsonExpected output:
{
"processed": {
"scheme": "http",
"host": "localhost:33333",
"remoteIpAddress": "::1"
},
"rawHeaders": {
"xForwardedFor": "203.0.113.50",
"xForwardedProto": "https",
"xForwardedHost": "example.com"
},
"configuration": {
"enabled": false,
"trustAllProxies": false,
"knownProxies": [],
"knownNetworks": []
}
}Headers are ignored because forwarded headers processing is disabled entirely. Notice enabled is false in the configuration.
Restart the instance with this configuration (Scenario 10).
rem ServiceControl (Primary)
set SERVICECONTROL_FORWARDEDHEADERS_ENABLED=true
set SERVICECONTROL_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNPROXIES=192.168.1.100
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNNETWORKS=127.0.0.0/8,::1/128
rem ServiceControl.Audit
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_ENABLED=true
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNPROXIES=192.168.1.100
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNNETWORKS=127.0.0.0/8,::1/128
rem ServiceControl.Monitoring
set MONITORING_FORWARDEDHEADERS_ENABLED=true
set MONITORING_FORWARDEDHEADERS_TRUSTALLPROXIES=
set MONITORING_FORWARDEDHEADERS_KNOWNPROXIES=192.168.1.100
set MONITORING_FORWARDEDHEADERS_KNOWNNETWORKS=127.0.0.0/8,::1/128
dotnet runTest using both KnownProxies and KnownNetworks together.
Test with curl:
rem ServiceControl (Primary)
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33333/debug/request-info | json
rem ServiceControl.Audit
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:44444/debug/request-info | json
rem ServiceControl.Monitoring
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33633/debug/request-info | jsonExpected output:
{
"processed": {
"scheme": "https",
"host": "example.com",
"remoteIpAddress": "203.0.113.50"
},
"rawHeaders": {
"xForwardedFor": "",
"xForwardedProto": "",
"xForwardedHost": ""
},
"configuration": {
"enabled": true,
"trustAllProxies": false,
"knownProxies": ["192.168.1.100"],
"knownNetworks": ["127.0.0.0/8", "::1/128"]
}
}Headers are applied because the request comes from localhost (::1), which falls within the ::1/128 network even though it's not in the knownProxies list.
Restart the instance with this configuration (Scenario 12).
rem ServiceControl (Primary)
set SERVICECONTROL_FORWARDEDHEADERS_ENABLED=true
set SERVICECONTROL_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNPROXIES=127.0.0.1
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNNETWORKS=
rem ServiceControl.Audit
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_ENABLED=true
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNPROXIES=127.0.0.1
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNNETWORKS=
rem ServiceControl.Monitoring
set MONITORING_FORWARDEDHEADERS_ENABLED=true
set MONITORING_FORWARDEDHEADERS_TRUSTALLPROXIES=
set MONITORING_FORWARDEDHEADERS_KNOWNPROXIES=127.0.0.1
set MONITORING_FORWARDEDHEADERS_KNOWNNETWORKS=
dotnet runNote
Only IPv4 127.0.0.1 is configured, not IPv6 ::1.
Demonstrates a common misconfiguration where only IPv4 localhost is configured but curl uses IPv6. This scenario shows why you should include both 127.0.0.1 and ::1 in your configuration.
Test with curl:
rem ServiceControl (Primary)
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33333/debug/request-info | json
rem ServiceControl.Audit
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:44444/debug/request-info | json
rem ServiceControl.Monitoring
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" -H "X-Forwarded-For: 203.0.113.50" http://localhost:33633/debug/request-info | jsonExpected output (if curl uses IPv6):
{
"processed": {
"scheme": "http",
"host": "localhost:33333",
"remoteIpAddress": "::1"
},
"rawHeaders": {
"xForwardedFor": "203.0.113.50",
"xForwardedProto": "https",
"xForwardedHost": "example.com"
},
"configuration": {
"enabled": true,
"trustAllProxies": false,
"knownProxies": ["127.0.0.1"],
"knownNetworks": []
}
}Headers are ignored because the request comes from ::1 (IPv6), but only 127.0.0.1 (IPv4) is in the known proxies list. This is a common gotcha - always include both IPv4 and IPv6 loopback addresses when testing locally, or use CIDR notation like 127.0.0.0/8 and ::1/128.
Note
If your output shows headers were applied, curl is using IPv4. The behavior depends on your system's DNS resolution for localhost.
The /debug/request-info endpoint is only available in Development environment. It returns:
{
"processed": {
"scheme": "https",
"host": "example.com",
"remoteIpAddress": "203.0.113.50"
},
"rawHeaders": {
"xForwardedFor": "",
"xForwardedProto": "",
"xForwardedHost": ""
},
"configuration": {
"enabled": true,
"trustAllProxies": false,
"knownProxies": ["127.0.0.1"],
"knownNetworks": []
}
}| Section | Field | Description |
|---|---|---|
processed |
scheme |
The request scheme after forwarded headers processing |
processed |
host |
The request host after forwarded headers processing |
processed |
remoteIpAddress |
The client IP after forwarded headers processing |
rawHeaders |
xForwardedFor |
Raw X-Forwarded-For header (empty if consumed by middleware) |
rawHeaders |
xForwardedProto |
Raw X-Forwarded-Proto header (empty if consumed by middleware) |
rawHeaders |
xForwardedHost |
Raw X-Forwarded-Host header (empty if consumed by middleware) |
configuration |
enabled |
Whether forwarded headers middleware is enabled |
configuration |
trustAllProxies |
Whether all proxies are trusted (security warning if true) |
configuration |
knownProxies |
List of trusted proxy IP addresses |
configuration |
knownNetworks |
List of trusted CIDR network ranges |
- Were headers applied? - If
rawHeadersare empty butprocessedvalues changed, the middleware consumed and applied them - Why weren't headers applied? - If
rawHeadersstill contain values, the middleware didn't trust the caller. CheckknownProxiesandknownNetworksinconfiguration - Is forwarded headers enabled? - Check
configuration.enabled
After testing, clear the environment variables:
Command Prompt (cmd):
rem ServiceControl (Primary)
set SERVICECONTROL_FORWARDEDHEADERS_ENABLED=
set SERVICECONTROL_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNPROXIES=
set SERVICECONTROL_FORWARDEDHEADERS_KNOWNNETWORKS=
rem ServiceControl.Audit
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_ENABLED=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_TRUSTALLPROXIES=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNPROXIES=
set SERVICECONTROL_AUDIT_FORWARDEDHEADERS_KNOWNNETWORKS=
rem ServiceControl.Monitoring
set MONITORING_FORWARDEDHEADERS_ENABLED=
set MONITORING_FORWARDEDHEADERS_TRUSTALLPROXIES=
set MONITORING_FORWARDEDHEADERS_KNOWNPROXIES=
set MONITORING_FORWARDEDHEADERS_KNOWNNETWORKS=Unit tests for the ForwardedHeadersSettings configuration class are located at:
src/ServiceControl.UnitTests/Infrastructure/Settings/ForwardedHeadersSettingsTests.cs
Acceptance tests for end-to-end forwarded headers behavior are located at:
src/ServiceControl.AcceptanceTests/Security/ForwardedHeaders/
src/ServiceControl.Audit.AcceptanceTests/Security/ForwardedHeaders/
src/ServiceControl.Monitoring.AcceptanceTests/Security/ForwardedHeaders/
Note
Scenario 12 (IPv4/IPv6 Mismatch) is not covered by acceptance tests because the test server's IP address (IPv4 vs IPv6) cannot be controlled reliably. The "untrusted proxy" behavior is already validated by Scenarios 5 and 6.
- Hosting Guide - Configuration reference for forwarded headers
- Reverse Proxy Testing - Testing with a real reverse proxy (NGINX)
- Testing Architecture - Overview of testing patterns in this repository