← Back to Blog

3 Ways to Handle SSL/TLS Traffic at a Load Balancer or Reverse Proxy

When deploying systems, especially with a Load Balancer or a reverse proxy like Nginx, handling TLS/SSL (HTTPS) traffic is always a headache. Should you decrypt right at the proxy to take load off the backend, or let traffic go straight through to the backend for maximum safety?

Today, let's dissect the 3 most common approaches: TLS Termination, TLS Passthrough, and TLS Re-encrypt. We'll look at how each one works, real Nginx configs, and when you should reach for which.

1. TLS Termination (SSL Offloading)

This is the most common approach used in practice. Here, the Load Balancer/Proxy acts as the endpoint of the SSL connection. It receives HTTPS traffic from the client, decrypts it, then sends traffic as plain HTTP (unencrypted) to the backend over the internal network.

How it works

Client (HTTPS) ----> [ Proxy (SSL Cert) ] ----> Backend (HTTP)
      (Encrypted)         (Decrypted)            (Unencrypted)

Example Nginx config for TLS Termination (SSL Offloading):

http {
    upstream my_app {
        server 10.0.0.10:80; # Backend runs HTTP
    }

    server {
        listen 443 ssl;
        server_name example.com;

        # 1. Certificate configuration
        ssl_certificate /etc/nginx/certs/fullchain.pem;
        ssl_certificate_key /etc/nginx/certs/privkey.pem;

        location / {
            # 2. Forward to the backend over HTTP
            proxy_pass http://my_app;

            # 3. Pass the original protocol info to the backend
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

Config explained:

  • listen 443 ssl: Nginx listens on port 443 and is ready to perform the SSL handshake with the client.
  • ssl_certificate: Path to the SSL certificate. All encryption/decryption happens here.
  • proxy_pass http://my_app: Note it's http. Once decrypted, traffic is forwarded as plain text.
  • X-Forwarded-Proto $scheme: Critical so the backend knows the original client used https, preventing the backend from redirecting back to http in a loop.

Assessment

Pros:

  • Reduces CPU load on the backend (no decryption cost)
  • Centralized certificate management in one place
  • The proxy can read/rewrite headers (cookies, caching, WAF).

Cons:

  • Traffic on the internal network is unencrypted. If an attacker gets into the LAN, they can sniff the data.

Real-world use case:

  • Most typical web applications sitting inside a secure internal network (VPC).
  • Applications that prioritize performance also tend to use this approach.

2. TLS Passthrough

This approach treats the proxy as a pure "pipe." Nginx doesn't intervene or decrypt the data at all — it just forwards the encrypted packets from the client straight to the backend, verbatim. In OSI terms, think of it as only unwrapping down to Layer 4 (TCP) before handing packets off, without needing to unwrap Layer 7 (HTTP).

How it works

Client (HTTPS) ----> [ Proxy (L4) ] ----> Backend (SSL Cert)
      (Encrypted)       (Forwarded)          (Decrypted here)

Nginx config:

To use Passthrough, we have to use the stream module (Layer 4) instead of the http module (Layer 7). Here's a sample Nginx config:

stream {
    upstream backend_ssl {
        server 10.0.0.10:443; # Backend holds its own cert and runs HTTPS
    }

    server {
        listen 443;
        proxy_pass backend_ssl;
        # No ssl_certificate configured here since Nginx doesn't decrypt
    }
}

Config explained:

  • stream { ... }: Runs at the TCP layer (Layer 4). Nginx only sees IP and port, not the URL or headers.
  • proxy_pass backend_ssl: TCP packets are forwarded as-is.

Note: You cannot use features like proxy_set_header or server_name-based routing in this block, because Nginx cannot read the packet contents.

Assessment

Pros:

  • Maximum security (end-to-end encryption)
  • The proxy knows nothing about the customer's data.

Cons:

  • The backend has to spend CPU resources on decryption instead of the proxy.
  • The proxy can't perform Layer 7 features (like URL-based routing, header injection)
  • You can't capture or process Layer 7 (HTTP) logs

Use case:

  • Applications requiring client certificates (mTLS), or extremely sensitive financial/banking services.
  • Databases with their own certificate. Commonly used for cloud databases connected to via a domain name.

3. TLS Re-encryption

This is "best of both worlds." The proxy receives HTTPS traffic (1st layer of encryption), decrypts it to process it (WAF, headers, routing), then re-encrypts it with a different certificate before sending it to the backend.

How it works

Client (HTTPS) ----> [ Proxy (Decrypt & Encrypt) ] ----> Backend (HTTPS)
  (1st encryption)      (Decrypt then re-encrypt)        (Final decryption)

Nginx config

http {
    upstream secure_backend {
        server 10.0.0.10:443; # Backend also runs HTTPS
    }

    server {
        listen 443 ssl;
        ssl_certificate /etc/nginx/certs/public.pem;
        ssl_certificate_key /etc/nginx/certs/public.key;

        location / {
            # 1. Forward to the backend over HTTPS
            proxy_pass https://secure_backend;

            # 2. Verify the backend's certificate
            proxy_ssl_verify on;
            proxy_ssl_trusted_certificate /etc/nginx/certs/backend_ca.crt;
            proxy_ssl_server_name on; # Support SNI for the backend
        }
    }
}

Config explained:

  • listen 443 ssl: Decrypts the connection from the client.
  • proxy_pass https://...: The key difference is https. Nginx acts as a "client" establishing a new encrypted connection to the backend.
  • proxy_ssl_verify on: Makes Nginx verify the backend's certificate is valid, preventing man-in-the-middle attacks even within the internal network.
  • proxy_ssl_trusted_certificate /etc/nginx/certs/backend_ca.crt: Tells the proxy to trust the backend's certificate. This certificate can be public or internal.
  • proxy_ssl_server_name on: Sends SNI (Server Name Indication) so the backend knows which domain it's serving.

Assessment

Pros:

  • End-to-end security across every hop the packet travels
  • Keeps the proxy's ability to intervene at Layer 7.

Cons:

  • The most CPU-intensive option (encrypt/decrypt twice)
  • Request latency will be noticeably higher
  • Configuring and managing certificates on both ends is quite complex.

Use case:

  • Systems that must comply with PCI-DSS, HIPAA, or Zero-Trust architectures.
  • Systems that need end-to-end encryption while still being able to extract data at intermediate layers.

Summing it up

CriteriaTLS TerminationTLS PassthroughTLS Re-encryption
Decryption pointAt the proxyAt the backendBoth proxy and backend
Security levelMedium (internal packets unencrypted)Very high (end-to-end)High
Performance (CPU)Costs resources at the proxyCosts resources at the backendCosts resources at both proxy and backend
Layer 7 interventionYesNoYes
Certificate managementCentralized at the proxyDistributed at the backendComplex (manage both)
Configuration difficultyNormalEasyComplex

Conclusion

Which approach to pick depends entirely on your security requirements and infrastructure architecture. If you prioritize simplicity and speed, Termination is number one. If you need absolute security, consider Passthrough. And if you need both flexibility and security within the LAN, go with Re-encryption.

Hope this post gives you a clearer picture of how to handle TLS traffic. If you have any questions, don't hesitate to leave a comment below! Wishing you smooth infrastructure builds!

If you're running into technical challenges, need help with systems, DevOps tools, or career direction, I'm confident I can help. Get in touch at hoangviet.io.vn — I'd love to chat and collaborate with you.

Have thoughts on this?

I'd love to hear your perspective. Send me a message.

Get in touch