Self-hosters often hit the same SSL question after they get the first certificate working:
If HTTPS terminates at Nginx or Traefik, do I also need TLS between the proxy and my app?
The confusing part is that both yes and no appear online, often with strong confidence.
The practical answer is simpler than the theory-heavy versions make it sound:
On a single VPS, with the reverse proxy and the app on the same trusted host or private Docker network, proxy-level TLS termination is usually enough.
You add internal TLS only when traffic crosses a trust boundary that you do not fully control.
That is the model.

Start with the trust boundary, not the certificate count
People approach this as a crypto question. Most of the time it is a topology question.
Ask:
- Does the traffic leave the machine?
- Does it cross a shared or untrusted network?
- Is there a compliance requirement for encryption at every hop?
- Are there intermediaries you do not control?
If the answer to all of those is no, end-to-end TLS inside the same VPS is usually complexity without meaningful security gain.
The common single-VPS pattern
Typical self-host layout:
Internet
to port 443 on VPS
to Nginx or Traefik
to app on localhost 3000 or a private container port
In that setup:
- External traffic is encrypted over the public internet.
- The proxy terminates TLS.
- The app receives plain HTTP from localhost or a private bridge network.
That is normal. It is also how a large amount of production infrastructure works.
The security assumptions are:
- You trust the VPS itself.
- You trust the local kernel networking boundary.
- You are not exposing the app port publicly.
- You are comfortable that compromise of the host means compromise of the stack anyway.
If those assumptions hold, internal TLS does not change the main risk.
When proxy termination is enough
1. App and proxy run on the same host
If Nginx is proxying to 127.0.0.1 on port 3000, adding TLS on that hop rarely helps. Anyone who can sniff loopback traffic on the machine already has host-level access, at which point your problem is not plain HTTP on localhost.
2. Containers are on a private Docker network you control
If Traefik and the app talk over a private bridge network and the app service is not published directly to the internet, that is still a trusted local path for most self-hosted setups.
3. You want simpler app configuration
Many apps become easier to run when they only need to trust proxy headers and speak plain HTTP internally. Internal TLS means managing certificates, trust stores, health checks, and often awkward redirect settings.
On a single VPS, simplicity is a security feature because fewer moving parts means fewer broken renewals, fewer bad redirects, and fewer mystery 502s.
When internal TLS is worth it
1. Traffic crosses hosts
The moment proxy and app are on different machines, the question changes.
Examples:
- Load balancer on one VPS, app on another
- Reverse proxy in one Docker host, services on a second host
- Proxy talking across a cloud VPC or provider private network
Now the traffic is crossing infrastructure you may partially trust, but not completely. Internal TLS becomes reasonable and often recommended.
2. You operate on a shared or semi-trusted network
If traffic traverses a network where other workloads or teams can exist, encrypting every hop is the safer default.
3. Compliance requires encryption in transit everywhere
Some environments care less about your threat model and more about policy language. If the requirement says every hop must be encrypted, then internal TLS is not optional even if the traffic stays inside your private address space.
4. You want mutual TLS between services
For service-to-service identity, mutual TLS is doing more than encryption. It is proving who the caller is. That matters in larger or zero-trust architectures, but it is usually overkill for one VPS with one proxy and one app.
The hidden costs of internal TLS on one VPS
People often frame extra encryption as purely additive. In practice, it adds operational failure modes.
Common ones:
- The app redirects HTTP to HTTPS internally and creates a loop behind the proxy.
- The proxy validates a backend certificate signed by a CA the container does not trust.
- Health checks fail because the backend now expects SNI or a specific Host header.
- Cert renewal works at the edge, but backend certs quietly expire.
- Local debugging gets harder because a simple curl to localhost no longer works without extra TLS flags.
None of these make your stack more secure if the traffic never crossed a meaningful boundary in the first place.
A safe baseline for most VPS deployments
For the majority of self-hosted apps on one VPS, this is the sensible setup:
Nginx on the host
server {
listen 443 ssl http2;
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:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Traefik with Docker
services:
traefik:
image: traefik:v3
ports:
- 80:80
- 443:443
networks:
- edge
app:
image: myapp:latest
expose:
- 3000
networks:
- edge
labels:
- traefik.enable=true
- traefik.http.routers.app.rule=Host("app.example.com")
- traefik.http.routers.app.entrypoints=websecure
- traefik.http.routers.app.tls.certresolver=letsencrypt
- traefik.http.services.app.loadbalancer.server.port=3000
networks:
edge:
In both cases, the backend stays plain HTTP and unexposed.
When people choose internal TLS for the wrong reason
A common argument is more encryption is always better.
That is too shallow.
Security decisions should reduce actual risk, not just increase ceremony. If an attacker already has root on your VPS or access to your private Docker network namespace, your real failure is host compromise, not lack of TLS on loopback traffic.
At that point, disk encryption, patching discipline, least-privilege containers, network exposure control, and secret handling matter more than backend HTTPS on the same box.
Decision rule you can use quickly
Use this:
Proxy-only TLS is usually enough when:
- proxy and app are on the same VPS,
- backend ports are not public,
- traffic stays on localhost or a private Docker network,
- no policy requires hop-by-hop encryption.
Add internal TLS when:
- traffic crosses hosts,
- the network path is not fully trusted,
- compliance requires it,
- or you need service identity with mutual TLS.
If you are unsure, map the actual packet path. That usually makes the answer obvious.
Internal links
- Guide: SSL Certificates on VPS
- Guide: Traefik SSL on VPS
- Guide: Nginx Reverse Proxy for Node.js
- Guide: How to Run Multiple Apps on One VPS Without Port Collisions
- ServerCompass: Deploy apps behind a managed reverse proxy flow
Closing principle
Do not ask how many TLS layers can I add. Ask where trust stops.
On a single VPS, edge termination at the reverse proxy is usually the right answer. When the traffic leaves that trusted boundary, the case for internal TLS becomes real. Until then, keep the backend private, keep the topology simple, and avoid inventing complexity that does not materially improve your risk profile.
From across the StoicSoft network
Hand-curated reads on the same topic from sister sites in the StoicSoft family.

