SSL Termination vs SSL Passthrough: balance between performance and easy management

Depending on management cost and user experience requirements, it might be more sensible to configure internal service as https or as http.

I give example of 2 backend exposing NodePort on Kubernetes, just to keep proxy concern clearly separated.

SSL Termination at the Proxy (HTTP Mode)

Concept

  • The proxy terminates TLS, handling encryption and certificate validation.
  • Backends receive plain HTTP (or optionally HTTP/2) traffic.
  • The proxy can inspect and modify headers (X-Forwarded-*) and perform routing, load balancing, caching, etc.
Browser <--HTTPS--> Proxy (SSL Termination) <--HTTP--> Backend

HAProxy Example (HTTP/2 Termination)

frontend https_frontend
    bind *:443 ssl crt /etc/haproxy/ssl/wildcard_beerme.pem alpn h2,http/1.1
    mode http
    option httplog

    # Match hostnames
    acl host_redmine hdr(host) -i redmine.beerme
    acl host_odoo    hdr(host) -i odoo.beerme

    # Route to backend
    use_backend redmine_backend if host_redmine
    use_backend odoo_backend    if host_odoo

backend redmine_backend
    mode http
    balance roundrobin
    option forwardfor
    http-request set-header X-Forwarded-Proto https
    http-request set-header X-Forwarded-Port  443
    server r1 10.4.1.11:30080 check
    server r2 10.4.1.12:30080 check

backend odoo_backend
    mode http
    balance roundrobin
    option forwardfor
    http-request set-header X-Forwarded-Proto https
    http-request set-header X-Forwarded-Port  443
    server o1 10.4.1.11:32036 check
    server o2 10.4.1.12:32036 check

Notes:

  • alpn h2,http/1.1 enables HTTP/2 between browser and proxy.
  • Backend sees plain HTTP. Browser multiplexing happens between browser and proxy only.
  • The backend receives headers indicating original HTTPS.

NGINX Example (HTTP/2 Termination)

server {
    listen 443 ssl http2;
    server_name redmine.beerme;

    ssl_certificate     /etc/nginx/ssl/wildcard_beerme.crt;
    ssl_certificate_key /etc/nginx/ssl/wildcard_beerme.key;

    location / {
        proxy_pass http://redmine_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Port 443;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

upstream redmine_backend {
    server 10.4.1.11:30080;
    server 10.4.1.12:30080;
}

Notes:

  • http2 in listen enables browser-side HTTP/2.
  • Backends only receive HTTP/1.1 requests unless proxy_http_version 2 is supported (NGINX default to HTTP/1.1 upstream).
  • Headers inform backend of original HTTPS protocol.

SSL Passthrough (TCP/Stream Mode)

  • Proxy does not terminate TLS; it simply forwards TCP based on SNI.
  • Browser connects directly to the backend TLS session.
  • End-to-end HTTP/2 multiplexing is preserved.

HAProxy TCP Passthrough Example

frontend ssl_passthrough
    bind *:443
    mode tcp
    tcp-request inspect-delay 5s
    tcp-request content accept if { req.ssl_hello_type 1 }

    acl redmine_sni req.ssl_sni -i redmine.beerme
    acl odoo_sni    req.ssl_sni -i odoo.beerme

    use_backend redmine_tcp_backend if redmine_sni
    use_backend odoo_tcp_backend    if odoo_sni

backend redmine_tcp_backend
    mode tcp
    server r1 10.4.1.11:30080 check
    server r2 10.4.1.12:30080 check

backend odoo_tcp_backend
    mode tcp
    server o1 10.4.1.11:32036 check
    server o2 10.4.1.12:32036 check

NGINX Stream Passthrough Example

stream {
    map $ssl_preread_server_name $backend_name {
        redmine.beerme redmine_backend;
        odoo.beerme    odoo_backend;
        default        blackhole;
    }

    upstream redmine_backend {
        server 10.4.1.11:30080;
        server 10.4.1.12:30080;
    }

    upstream odoo_backend {
        server 10.4.1.11:32036;
        server 10.4.1.12:32036;
    }

    server {
        listen 443;
        proxy_pass $backend_name;
        ssl_preread on;
    }
}

Notes:

  • Backend certificates must match domain names (redmine.beerme, odoo.beerme) or use a wildcard certificate.
  • Browser-side HTTP/2 multiplexing is fully preserved.
  • Proxy cannot inspect HTTP headers.

Browser Performance

ModeBrowser HTTP/2 MultiplexingBackend Certificate NeededProsCons
HTTP SSL TerminationOnly browser↔proxyProxy cert onlyHeader manipulation, routing, cachingMultiplexing not end-to-end
SSL PassthroughBrowser↔backend (full)Backend cert validFull HTTP/2 performanceBackend must handle TLS, no header inspection

Key takeaway:

  • For maximum page serving speed and real end-to-end HTTP/2, passthrough is superior.
  • SSL termination simplifies backend management and centralizes certs, but may limit multiplexing performance.

SSL Termination in Proxy, but talking with HTTP/2 backend (Stateless REST)

To take advance from stateless nature of REST requests, the better option is to let the proxy reuse the http/2 backend connection for more clients. This also reduce the total number of connection kept by the backend server, and the total number of TLS handshake required:

  • proxy keep one connection per backend
  • proxy negotiate one TLS per backend
  • proxy reuse the same backend for requests arriving from different clients

For making nginx talk http/2 to the backend:

server {
listen 443 ssl http2;
server_name redmine.beerme;

ssl_certificate /etc/nginx/ssl/self.crt;
ssl_certificate_key /etc/nginx/ssl/self.key;

location / {
    proxy_pass http://backend_redmine;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # Enable HTTP/2 to backend
    proxy_http_version 2;
    proxy_set_header Connection "";
}
}