Where you terminate TLS decides a lot about your deployment: certificate management, how debugging works, and how much the backend has to trust the network.
The most common layout. The proxy holds the certificate and speaks plain HTTP upstream.
client --TLS--> nginx (443) --HTTP--> app (127.0.0.1:8080)
Simple, and the app never needs to care about certificates. The catch is that anything able to reach the upstream port can bypass the proxy entirely — so bind the app to loopback.
Terminate at the edge, then open a second TLS session upstream.
client --TLS--> nginx --TLS--> app (8443)
Useful when the proxy and the app are on different hosts on an untrusted network. Costs one extra handshake per connection, which connection reuse usually absorbs.
The proxy forwards the raw TCP stream and never sees the plaintext.
stream {
server {
listen 443;
proxy_pass 127.0.0.1:8443;
}
}
Good for SNI-based routing across many certificates. Downside: no HTTP-level inspection, no access logs with paths, no header rewriting.
Whatever you pick, verify the upstream port is not reachable from outside. That single check catches the majority of misconfigurations I have run into.