Before this:Services & systemd
Reverse proxies & TLS
Key takeaways A reverse proxy (nginx or Caddy) sits in front of your app, taking public traffic and forwarding it to the app on localhost. It terminates TLS — holding the certificate and speaking HTTPS to the world — so the app behind it stays plain HTTP. It routes by host so one server can front many apps. Tools like Caddy fetch and renew Let’s Encrypt certificates automatically over ACME.
The Compose lesson bound GopherTrunk’s API to
127.0.0.1:8080 — reachable only from the host. A reverse proxy is how you safely
expose that to the world with HTTPS, without the app itself dealing in certificates. This
builds on TLS & HTTPS and
proxies & load balancers from the
networking module.
What a reverse proxy does
A reverse proxy accepts every incoming connection and forwards it to a backend. That one choke point becomes the natural home for the cross-cutting concerns you don’t want each app to reinvent:
- TLS termination — decrypt HTTPS here, forward plain HTTP inward.
- Host / path routing — send
scanner.example.comto one app,blog.example.comto another. - Rate limiting, compression, caching, auth — applied once, in front of everything.
Why the app stays plain HTTP
Terminating TLS at the proxy means the certificate lives in exactly one place. One
process holds the private key, renews it before it expires, and keeps the TLS protocol
versions and ciphers current. The app behind it just serves plain HTTP on localhost and
never touches a certificate — simpler app, one thing to get right instead of many. This
is exactly why GopherTrunk binds to 127.0.0.1: the loopback hop is unencrypted but
never leaves the host, and the proxy adds HTTPS on the public side.
nginx: explicit and everywhere
nginx is the classic choice — a config block forwards a hostname to the backend:
server {
listen 443 ssl;
server_name scanner.example.com;
ssl_certificate /etc/letsencrypt/live/scanner.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/scanner.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080; # GopherTrunk's local API
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
}
proxy_pass is the forward; the X-Forwarded-* headers tell the app the original
client’s address and that the outside hop was HTTPS. You get the certificate from
Let’s Encrypt with certbot, which also installs a renewal
timer.
Caddy: automatic HTTPS
Caddy does the same job but fetches and renews certificates for you automatically over ACME — the protocol Let’s Encrypt uses to prove you control a domain. The entire config is often two lines:
scanner.example.com {
reverse_proxy 127.0.0.1:8080
}
Point that hostname’s DNS at your server, start Caddy, and it obtains a valid
certificate on first request and renews it forever — no certbot, no cron job. For a
new deployment that’s the lowest-friction path to real HTTPS.
The key idea
The proxy owns the edge; the app owns its logic. TLS, routing, and rate limiting live in front, in one place, on one team’s checklist. Add a second app later and you route it with a few more lines — the pattern scales into the load balancer you’ll meet in scaling.
Quick check: why let the reverse proxy handle TLS instead of the app?
Recap
- A reverse proxy (nginx, Caddy) fronts your app and forwards requests to it.
- It terminates TLS — the certificate lives in one place, and the app behind speaks plain HTTP on localhost.
- It routes by host/path so one server can front many apps, with
X-Forwarded-*headers passing the real client info through. - Caddy automates Let’s Encrypt certificates over ACME; nginx + certbot does the same with a bit more config.
Next up: how a running service tells you it’s healthy — logging and health checks.
Frequently asked questions
What is a reverse proxy?
A reverse proxy is a server that sits in front of one or more backend applications and forwards client requests to them. To the outside world it looks like the whole site; behind it, it routes each request to the right backend by hostname or path. It’s where you centralise HTTPS, compression, rate limiting, and routing so each app doesn’t reimplement them.
Why does the proxy handle TLS instead of the app?
Terminating TLS at the proxy means one place holds the certificates, one place renews them, and one place gets the ciphers and protocol versions right. The app behind it can speak plain HTTP on localhost, staying simpler. GopherTrunk binds its API to 127.0.0.1 and lets a proxy add HTTPS in front — the app never has to know about certificates.