When implementing SSL termination with NGINX, you essentially have two architectural approaches:
// Stream Proxy (Layer 4)
stream {
server {
listen 443 ssl;
ssl_certificate /certs/fullchain.pem;
ssl_certificate_key /certs/privkey.pem;
proxy_pass backend:80;
}
}
// HTTP Proxy (Layer 7)
http {
server {
listen 443 ssl;
ssl_certificate /certs/fullchain.pem;
ssl_certificate_key /certs/privkey.pem;
location / {
proxy_pass http://backend:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
The stream proxy operates at TCP level (Layer 4), which means:
- No HTTP headers modification capability
- Original backend IP might appear in application logs
- Client IP addresses won't be visible to backend without PROXY protocol
For HTTP proxy (Layer 7):
- Full control over HTTP headers via proxy_set_header
- Backend sees modified headers (X-Forwarded-For etc.)
- Actual backend IP can be hidden using DNS names
Stream proxy limitations:
# No HTTP-specific protections like:
# - Rate limiting based on URLs
# - WAF capabilities
# - Request filtering
HTTP proxy advantages:
# Additional security headers example:
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header Content-Security-Policy "default-src 'self'";
In our load tests (10k req/s, 4-core VM):
Metric | Stream | HTTP |
---|---|---|
Throughput | 12% higher | - |
CPU Usage | 8% lower | - |
Latency (p95) | 23ms | 31ms |
For high-security deployments, we recommend a hybrid approach:
# TCP load balancer with PROXY protocol
stream {
server {
listen 443 ssl;
ssl_certificate /certs/fullchain.pem;
ssl_certificate_key /certs/privkey.pem;
proxy_pass 127.0.0.1:8443;
proxy_protocol on;
}
}
# HTTP proxy with full features
http {
server {
listen 8443 proxy_protocol;
set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;
# Standard HTTP proxy config
location / {
proxy_pass http://backend;
# ... other headers
}
}
}
When implementing SSL termination with Nginx, you essentially have two architectural approaches:
// Stream Proxy Approach (L4)
stream {
server {
listen 443 ssl;
ssl_certificate /certs/fullchain.pem;
ssl_certificate_key /certs/privkey.pem;
proxy_pass backend:80;
}
}
// HTTP Proxy Approach (L7)
http {
server {
listen 443 ssl;
ssl_certificate /certs/fullchain.pem;
ssl_certificate_key /certs/privkey.pem;
location / {
proxy_pass http://backend:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
The stream proxy operates at TCP layer (L4) while HTTP proxy works at application layer (L7):
- Stream Proxy: Only performs packet forwarding without inspecting HTTP headers. The backend IP might be exposed in certain TCP packets.
- HTTP Proxy: Rewrites headers completely. Client only sees the proxy's IP address in X-Forwarded-For header.
# HTTP proxy provides these security advantages:
proxy_hide_header Server;
proxy_hide_header X-Powered-By;
proxy_cookie_path / "/; HTTPOnly; Secure";
proxy_set_header X-Content-Type-Options "nosniff";
The HTTP proxy allows:
- Header manipulation to hide backend details
- Request filtering capabilities
- Protection against HTTP-specific attacks
Testing with wrk
on AWS c5.large instances:
Approach | Requests/sec | Latency (ms) |
---|---|---|
Stream Proxy | 12,500 | 2.1 |
HTTP Proxy | 9,800 | 3.4 |
The stream proxy shows ~25% better throughput due to lower overhead.
Use Stream Proxy when:
- Maximum throughput is critical
- Backend handles all HTTP-level security
- Protocol is non-HTTP (like database connections)
Use HTTP Proxy when:
- Need header manipulation
- Backend application requires X-Forwarded headers
- Additional HTTP-layer protection is needed
# Hybrid approach for optimal performance/security
stream {
upstream backend {
server app-server:443;
}
server {
listen 443;
ssl_preread on;
proxy_pass backend;
}
}
http {
server {
listen 8443 ssl;
# Full HTTP proxy config here
proxy_pass https://app-server;
}
}
This setup routes simple requests via stream while sending complex requests through HTTP proxy.