When deploying self-hosted services, exposing them securely to the public internet or private VPN is your first architectural decision. An ingress layer must handle:
- Automatic SSL/TLS certificate issuance and renewals via Let's Encrypt (ACME)
- Dynamic service discovery (auto-detecting Docker containers without reloading configs)
- Security middleware (rate limiting, CrowdSec/Fail2ban integration, HTTP Basic Auth / OAuth2 / Authentik)
- Performance and protocol support (HTTP/3, WebSockets, gRPC)
In this guide, we benchmark and configure the four most popular open-source reverse proxies:
- Traefik v3: The Docker-native, label-driven microservices reverse proxy.
- Caddy 2: The developer-friendly server with memory safety, zero-config HTTPS, and HTTP/3.
- Nginx Proxy Manager (NPM): The easiest web-UI driven reverse proxy for homelabs.
- Kong Gateway: The enterprise-grade API gateway with Lua/Wasm plugins, rate-limiting, and OAuth2.
1. Feature Matrix & Benchmark
| Feature / Metric | Traefik v3 | Caddy 2 | Nginx Proxy Manager | Kong Gateway |
|---|---|---|---|---|
| Configuration Model | Docker Labels & YAML | Caddyfile / JSON API | Web UI Dashboard | Declarative YAML / REST API |
| Language & Engine | Go | Go | Nginx (C) + Node.js UI | OpenResty (Nginx + Lua) |
| Automatic HTTPS / ACME | Built-in | Built-in (Default) | Built-in (Let's Encrypt) | Via Certbot / Plugins |
| Docker Discovery | Native (Zero-reload) | Via Docker Proxy plugin | Manual via Web UI | Via Ingress Controller / API |
| HTTP/3 (QUIC) | Yes | Yes (Native) | Experimental | Yes |
| Middleware Ecosystem | Chains (RateLimit, Auth) | Directives & Matchers | GUI Toggles + Custom Nginx | Extensive Plugin Hub |
| Memory Consumption | ~35 MB | ~28 MB | ~110 MB | ~180 MB |
| Best For | Multi-container Docker | Minimalist & Static/Apps | Homelab beginners | High-throughput APIs |
2. Choosing the Right Proxy
Use Traefik If:
- You run 10+ Docker containers that spin up and down dynamically.
- You prefer configuring routing rules directly in your
docker-compose.ymlservice labels instead of maintaining central config files. - You want built-in metrics export for Prometheus and Grafana.
Use Caddy If:
- You appreciate clean, readable configuration files (3 lines of Caddyfile replace 20 lines of Nginx).
- You want default HTTPS without having to specify email or ACME providers.
- You host static assets alongside reverse proxies with blazing-fast HTTP/3 support.
Use Nginx Proxy Manager If:
- You prefer managing SSL certificates, redirects, access lists, and domains through a visual web dashboard.
- You don't want to write YAML or Caddyfile syntax.
Use Kong If:
- You are building an API marketplace, microservice cluster, or SaaS backend needing token validation, consumer quotas, and transformations.
3. Production Deployment: Hardened Traefik v3 with Socket Proxy
Exposing /var/run/docker.sock directly to public-facing containers is a security vulnerability. We use tecnativa/docker-socket-proxy to restrict Traefik to read-only container events.
docker-compose.yml
version: '3.8'
services:
docker-proxy:
image: tecnativa/docker-socket-proxy:latest
container_name: docker-socket-proxy
restart: unless-stopped
environment:
CONTAINERS: 1
SERVICES: 1
TASKS: 1
NETWORKS: 1
EVENTS: 1
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- socket-net
traefik:
image: traefik:v3.1
container_name: traefik
restart: unless-stopped
depends_on:
- docker-proxy
ports:
- "80:80"
- "443:443"
volumes:
- ./traefik.yml:/etc/traefik/traefik.yml:ro
- ./acme.json:/acme.json
networks:
- socket-net
- web-public
volumes:
acme-data:
networks:
socket-net:
internal: true
web-public:
external: true
traefik.yml Configuration
api:
dashboard: true
insecure: false
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: ":443"
http:
tls:
certResolver: letsencrypt
providers:
docker:
endpoint: "tcp://docker-proxy:2375"
exposedByDefault: false
network: web-public
certificatesResolvers:
letsencrypt:
acme:
email: admin@yourdomain.com
storage: /acme.json
httpChallenge:
entryPoint: web
log:
level: INFO
Exposing Any Downstream Service with Labels
services:
whoami:
image: traefik/whoami
container_name: whoami-demo
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.yourdomain.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
- "traefik.http.services.whoami.loadbalancer.server.port=80"
networks:
- web-public
4. Alternative: Minimal Caddyfile Setup
If you prefer Caddy's simplicity, here is an equivalent multi-domain production Caddyfile:
{
email admin@yourdomain.com
}
(security-headers) {
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
app.yourdomain.com {
import security-headers
reverse_proxy 127.0.0.1:3000
}
analytics.yourdomain.com {
import security-headers
reverse_proxy 127.0.0.1:8000
}
Summary & Recommendation
- For general Docker self-hosting: Deploy Traefik v3 with Docker Socket Proxy for seamless label-based ingress.
- For simple VPS & static + proxy setups: Deploy Caddy 2 for zero-friction SSL and readable configs.
- For homelabs wanting a UI: Deploy Nginx Proxy Manager.
Need pre-built reverse proxy stacks with CrowdSec automated IP banning and Authentik SSO integration? Check out the SelfHostStack Ingress Catalog and grab the Self-Hosted Starter Stack Pack.
Top comments (0)