PROXY Protocol Support
November 15, 2025 · View on GitHub
This document describes the PROXY protocol implementation in synapse.
Overview
The PROXY protocol is used when running synapse behind a Layer 4 load balancer (like AWS NLB, HAProxy, nginx stream module) to preserve the original client IP address and port information. Without the PROXY protocol, synapse would only see the load balancer's IP address, making it impossible to apply proper security policies based on the true client IP.
Specification
The implementation follows the PROXY protocol specification:
Both PROXY protocol v1 (text-based) and v2 (binary) are supported.
Features
- PROXY v1 Support: Text-based format for IPv4 and IPv6
- PROXY v2 Support: Binary format with support for:
- IPv4 (AF_INET)
- IPv6 (AF_INET6)
- LOCAL command (for health checks)
- PROXY command (for proxied connections)
- HTTP Forwarded Header: Automatically adds the
Forwardedheader with the real client IP to upstream requests - Transparent Integration: Works seamlessly with TLS fingerprinting and security features
Compilation
The PROXY protocol support is behind a feature flag and must be enabled at compile time:
cargo build --release --features proxy_protocol
Configuration
Via Config File (config.yaml)
server:
http_addr: "0.0.0.0:80"
tls_addr: "0.0.0.0:443"
upstream: "http://localhost:8080"
enable_proxy_protocol: true # Enable PROXY protocol
Via Command Line
./synapse --enable-proxy-protocol --upstream http://localhost:8080
Via Environment Variable
export AX_SERVER_ENABLE_PROXY_PROTOCOL=true
./synapse --upstream http://localhost:8080
How It Works
Connection Flow
- Load Balancer receives connection from client (1.2.3.4:56789)
- Load Balancer establishes connection to synapse and sends PROXY protocol header:
PROXY TCP4 1.2.3.4 10.0.0.5 56789 443\r\n - synapse parses the PROXY protocol header, extracts client IP (1.2.3.4)
- synapse uses the real client IP for:
- Security policies and threat intelligence
- WAF rules and access control
- Logging and analytics
- synapse adds
Forwarded: for=1.2.3.4header to upstream request - Upstream server receives request with original client IP information
PROXY Protocol v1 Format
Text-based format:
PROXY TCP4 192.168.1.1 10.0.0.1 56324 443\r\n
Or for unknown connections (health checks):
PROXY UNKNOWN\r\n
PROXY Protocol v2 Format
Binary format with 16-byte header followed by address information:
\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A (signature)
[version_cmd] [family_protocol] [length_hi] [length_lo]
[address data...]
Load Balancer Configuration Examples
AWS Network Load Balancer (NLB)
Enable PROXY protocol v2 on the target group:
aws elbv2 modify-target-group-attributes \
--target-group-arn <target-group-arn> \
--attributes Key=proxy_protocol_v2.enabled,Value=true
HAProxy
frontend https_frontend
bind *:443
mode tcp
default_backend synapse_backend
backend synapse_backend
mode tcp
balance roundrobin
server synapse1 10.0.1.10:443 send-proxy-v2 check
server synapse2 10.0.1.11:443 send-proxy-v2 check
nginx (stream module)
stream {
upstream synapse_backend {
server 10.0.1.10:443;
server 10.0.1.11:443;
}
server {
listen 443;
proxy_pass synapse_backend;
proxy_protocol on;
}
}
Security Considerations
Important: Trusted Networks Only
WARNING: Only enable PROXY protocol when synapse is deployed behind a trusted load balancer in a secure network. If PROXY protocol is enabled and synapse is directly exposed to the internet, attackers can spoof arbitrary source IP addresses by sending crafted PROXY protocol headers.
Best Practices
- Network Isolation: Deploy synapse in a private network, only accessible from the load balancer
- Firewall Rules: Configure firewall to only allow connections from the load balancer IPs
- TLS Termination: Consider whether TLS should be terminated at the load balancer or at synapse
- Health Checks: Use PROXY protocol v2 LOCAL command for health checks
Troubleshooting
Error: "Invalid PROXY protocol header"
This occurs when:
- The load balancer is not configured to send PROXY protocol headers
- The load balancer is sending the wrong version (v1 vs v2)
- Non-load-balanced traffic reaches synapse with PROXY protocol enabled
Solution: Verify load balancer configuration and ensure only proxied traffic reaches synapse.
Client IP Still Shows Load Balancer IP
Check that:
- PROXY protocol is enabled in synapse configuration
- synapse was compiled with
--features proxy_protocol - Load balancer is configured to send PROXY protocol headers
- Check logs for "PROXY protocol enabled" message on startup
Health Checks Failing
Some load balancers send health checks without PROXY protocol headers. Use:
- PROXY protocol v2 LOCAL command
- Separate health check endpoint not behind load balancer
- TCP health checks instead of HTTP
API and Implementation Details
ProxyProtocolStream
The ProxyProtocolStream wrapper reads and parses the PROXY protocol header transparently:
use synapse::http::proxy_protocol::ProxyProtocolStream;
// Wrap a TCP stream
let proxy_stream = ProxyProtocolStream::new(tcp_stream).await?;
// Get real client address
if let Some(info) = proxy_stream.proxy_info() {
println!("Real client: {}", info.src_addr);
println!("Proxy address: {}", info.dst_addr);
}
// Use the stream normally (header is already consumed)
// The stream can now be used for TLS, HTTP, etc.
Forwarded Header
When PROXY protocol is enabled and client information is available, synapse automatically adds the Forwarded HTTP header as specified in RFC 7239:
Forwarded: for=192.168.1.1
This allows upstream applications to access the real client IP address.
Performance
The PROXY protocol adds minimal overhead:
- v1: ~50 bytes text header, simple string parsing
- v2: 16-28 bytes binary header (IPv4) or 16-52 bytes (IPv6), fast binary parsing
- Latency: < 1ms additional latency for header parsing
Compatibility
- PROXY v1: Compatible with HAProxy, AWS NLB, nginx
- PROXY v2: Compatible with HAProxy 1.5+, AWS NLB, nginx 1.13.11+
- IP Versions: IPv4 and IPv6 fully supported
- Protocols: Works with both HTTP and HTTPS (TLS)
Example Deployment Architecture
┌─────────────┐
│ Client │
│ 1.2.3.4 │
└──────┬──────┘
│
│ TCP + TLS
│
▼
┌─────────────────────────┐
│ AWS Network LB │
│ (PROXY v2 enabled) │
└─────────┬───────────────┘
│
│ PROXY TCP4 1.2.3.4 10.0.0.5 56789 443\r\n
│ + TLS handshake
│
▼
┌────────────────┐
│ synapse instance │
│ 10.0.1.10 │
│ (proxy proto) │
└────────┬───────┘
│
│ HTTP + Forwarded: for=1.2.3.4
│
▼
┌─────────────┐
│ Upstream │
│ Server │
└─────────────┘
Testing
To test PROXY protocol locally, you can use HAProxy or send manual PROXY headers:
Using netcat to send PROXY v1
# Start synapse with proxy protocol
./synapse --enable-proxy-protocol --upstream http://localhost:8080 &
# Send PROXY v1 header followed by HTTP request
(echo -ne "PROXY TCP4 1.2.3.4 127.0.0.1 56789 443\r\n"; \
echo -ne "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") | \
nc localhost 443
Using HAProxy locally
# haproxy.cfg
frontend test_fe
bind *:8443
mode tcp
default_backend synapse_be
backend synapse_be
mode tcp
server synapse 127.0.0.1:443 send-proxy-v2
haproxy -f haproxy.cfg
curl -k https://localhost:8443/