NGINX SSL Termination: Stream vs HTTP Proxy Performance and Security Comparison


2 views

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.