Security

TLS termination explained: where HTTPS ends and why it matters

What TLS termination, passthrough and re-encryption mean, how to terminate HTTPS on Nginx or HAProxy in front of backends, and how to pass the original scheme and client IP.

AdvancedLinux VPS3 min read

TLS termination is the point where an encrypted HTTPS connection is decrypted. In simple setups that is the web server itself; in larger ones it is a reverse proxy, load balancer or CDN in front of the application. Choosing where TLS terminates affects certificate management, performance, security and what the backend sees.

Before you start

Three ways to handle TLS at a proxy

ModeProxy decrypts?Proxy → backendTypical use
Termination (offload)YesPlain HTTPBackend on same host or private network
Re-encryption (bridging)YesNew HTTPS connectionBackend across untrusted networks, compliance
PassthroughNo (routes by SNI)Original encrypted streamBackend must own the certificate, end-to-end TLS

Why terminate TLS at the edge

  • One place for certificates. Renew with Certbot or acme.sh on the proxy only.
  • Layer 7 features. Only a proxy that sees the HTTP request can route by path, cache, compress, add headers or apply a WAF.
  • Modern protocols. The proxy can speak HTTP/2 and HTTP/3 to browsers while the backend speaks simple HTTP/1.1.
  • Offload CPU. Handshakes are handled by a process tuned for it.

The trade-off: traffic between proxy and backend is plain text. That is fine over loopback or a private VLAN, not across the internet.

Terminate TLS on Nginx in front of an app

server {
    listen 443 ssl;
    http2 on;
    server_name app.example.com;
    ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Bind the backend to 127.0.0.1 so nobody can bypass the proxy.

Tell the backend the request was HTTPS

After termination the app sees plain HTTP. Without X-Forwarded-Proto: https many apps build http:// links or loop redirecting to HTTPS. Configure the app to trust the header only from the proxy:

  • WordPress: set $_SERVER['HTTPS'] = 'on' in wp-config.php when the header is https.
  • Laravel: configure trusted proxies.
  • Apache backend: mod_remoteip for the IP and SetEnvIf X-Forwarded-Proto https HTTPS=on.

Never trust forwarded headers from arbitrary clients; anyone can send them.

Re-encrypt to the backend

proxy_pass https://10.0.0.20:8443;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /etc/nginx/backend-ca.pem;
proxy_ssl_server_name on;

Without proxy_ssl_verify on the connection is encrypted but not authenticated.

TLS passthrough with HAProxy

frontend tls_in
    bind :443
    mode tcp
    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }
    use_backend app1 if { req_ssl_sni -i app1.example.com }
backend app1
    mode tcp
    server s1 10.0.0.21:443

The proxy never sees HTTP, so no header injection, caching or path routing is possible.

Troubleshooting

  • ERR_TOO_MANY_REDIRECTS: the backend does not know the request was HTTPS. Pass and trust X-Forwarded-Proto. See fixing redirect loops.
  • All logs show 127.0.0.1: restore the client IP from X-Forwarded-For with the real IP module.
  • Mixed content warnings: the app generates http:// asset URLs for the same reason.

FAQ: TLS termination

Is TLS termination secure?

Yes when the proxy-to-backend path is local or private. Use re-encryption otherwise.

Does Cloudflare terminate TLS?

Yes. In "Full (strict)" mode it re-encrypts to your origin with a valid certificate.

Where should HSTS be set?

On the component that talks to browsers, the terminating proxy.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.