Almost no modern web application responds directly to users. Between the Internet and the code there is usually a reverse proxy: a server that receives requests, decides which application to forward them to, and returns the response. It is a component that is simple to describe but concentrates many important responsibilities, and choosing it well makes life easier.
Proxy and reverse proxy
A traditional proxy acts on behalf of clients: a company's computers go out to the Internet through it. A reverse proxy acts on behalf of servers: users connect to it believing they are talking to the site, and it dispatches requests to the applications behind it, which stay hidden.
What a reverse proxy does
TLS termination
The reverse proxy handles certificates and HTTPS encryption. The applications behind it can receive plain traffic on the internal network, without each one having to manage certificates. Renewals, protocol configuration and redirection from HTTP to HTTPS happen in a single place.
Routing
Many services can coexist on a single IP address. The proxy looks at the requested domain name or path and forwards to the right service: blog.example.com to one application, example.com/api to another.
Load balancing
If an application runs on several instances, the proxy distributes requests among them and stops sending them to those that don't respond.
Other useful functions
- compression of responses to reduce traffic;
- caching of static content or of responses that rarely change;
- rate limiting to protect against abuse and spikes;
- security headers added uniformly to all responses;
- centralized access logs for all services.
A detail not to forget: the client IP
When there is a proxy in between, the application sees every request coming from the proxy's address. The visitor's real address is passed in a header, usually X-Forwarded-For, together with X-Forwarded-Proto to indicate whether the original request was over HTTPS.
The application must be configured to trust these headers only if they come from the proxy. Otherwise anyone could send a fake header and pass themselves off as another address, bypassing limits or falsifying the logs.
The three most widespread tools
Nginx
Nginx is one of the most used web servers in the world, stable and efficient, with vast documentation. Its configuration is explicit and very flexible:
server {
listen 443 ssl;
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:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Certificates are usually obtained with an external tool, such as Certbot, which renews them automatically.
Caddy
Caddy stands out for its automatic HTTPS certificates: just specify the domain name and Caddy obtains and renews the certificate from Let's Encrypt by itself. The configuration equivalent to the previous one is three lines:
app.example.com {
reverse_proxy 127.0.0.1:8000
}
It also sets the most common forwarding headers without you having to write them. It is ideal for small servers and personal projects, but it is suitable for production too.
Traefik
Traefik is designed for container environments. Instead of reading a static configuration file, it watches Docker or Kubernetes and discovers services automatically: when you start a container with the right labels, Traefik creates the route and obtains the certificate. When you stop it, the route disappears.
It is very convenient where services change often, but configuration through labels can feel less intuitive to those coming from a traditional approach.
How to choose
| Need | Recommended choice |
|---|---|
| Maximum flexibility, complex configurations, lots of documentation | Nginx |
| Simplicity and automatic HTTPS with minimum effort | Caddy |
| Many containers coming and going, automatic discovery | Traefik |
There is no absolute winner. In my work I use Caddy when simplicity matters, Nginx when elaborate caching or rewrite rules are needed, and Traefik on servers where many container services managed with Compose live side by side.
Good practices
- Make applications listen only on internal addresses, so they can be reached exclusively through the proxy.
- Set reasonable limits on request size and timeouts.
- Enable automatic certificate renewal and check that it works before the first expiry.
- Keep the proxy configuration under version control, because it is the point where one mistake makes every service unreachable at once.
In short
- A reverse proxy receives users' requests and forwards them to the applications, which stay hidden.
- It centralizes TLS, routing, load balancing, compression, caching and rate limits.
- The application should trust client-IP headers only if they come from the proxy.
- Nginx offers flexibility, Caddy simplicity with automatic HTTPS, Traefik automatic container discovery.
- Expose applications only internally and keep the proxy configuration under version control.