How to Protect Self-Hosted Services With mTLS Client Certificates in Caddy
If you run a self-hosted service behind Caddy, you can require each connecting device to prove its identity with a certificate. Mutual…
When you host several services at home, a reverse proxy gives them one front door. It receives requests for names such as photos.example.net and forwards each request to the right service on your network. It can also handle HTTPS, the encrypted connection between a browser and the proxy.
Caddy, Traefik, and Nginx Proxy Manager (NPM) all do this. Choose how you want to manage routes: write them in a configuration file with Caddy, define them alongside Docker services with Traefik, or create them through NPM’s browser interface. Choose Caddy for a small, stable set of routes and straightforward certificate automation; Traefik for Docker-based discovery; or NPM if you prefer to manage proxy hosts through a graphical interface.
A route connects a requested hostname to the service that should receive the request. Each proxy can manage routes and HTTPS certificates, but each expects you to work differently.
| Proxy | How you define routes | A good fit when |
|---|---|---|
| Caddy | Write hostnames and destinations in a Caddyfile or JSON configuration | You want explicit, readable configuration and automatic HTTPS |
| Traefik | Use static settings and provider-specific configuration, often Docker labels | You want routes declared alongside Docker services |
| Nginx Proxy Manager | Create and edit proxy hosts through a web interface | You prefer visual controls for common proxy tasks |
These differences describe the projects’ documented workflows, not measured differences in setup time or performance. No controlled comparison establishes that one is faster or uses fewer resources under the same home-lab conditions.
Caddy works well when you want to see which service each hostname points to in a configuration file. Its reverse proxy quick-start shows a compact configuration, and Caddy can obtain and renew public certificates for configured domain names. It also redirects HTTP requests to HTTPS when automatic HTTPS applies.
In a Docker setup, Caddy can reach another container by its service name and internal listening port when both containers share a network. Avoid pointing Caddy at localhost for another container: inside Caddy’s container, localhost refers to Caddy itself. Caddy’s Docker and running guidance also describes persistent storage for its data and configuration. This storage keeps those files when you replace the container, so include it in backups.
Automatic certificate management still depends on how your domain and network are set up. The ACME protocol automates certificate requests and renewals. A certificate authority, the organization that issues a certificate, checks that you control the domain. The HTTP and TLS-ALPN validation methods check domain control through web or encrypted connections. The public ports those checks use must reach Caddy. DNS validation avoids that inbound-port requirement, but needs access to your DNS provider and a Caddy build that includes the provider’s module. Let’s Encrypt requires DNS validation for wildcard certificates. Caddy’s automatic HTTPS documentation explains these options.
Caddy also supports local certificates for internal names and IP addresses. Browsers and other clients trust those certificates only if they trust Caddy’s local certificate authority. To establish that trust, install Caddy’s root certificate, which tells the devices to trust certificates that Caddy issues.
Choose Caddy when you are comfortable editing a file and want a clear, manageable route list. It is a reasonable starting point for a small home lab with services whose routes do not change constantly.
Traefik’s Docker provider reads container information and labels to configure routes. A label is metadata attached to a container in its Docker configuration. Traefik uses that metadata to decide which requests match a route and where to send them. Its Docker setup demonstrates this approach, including access to the Docker socket, the interface Traefik uses to query Docker.
Keeping routing labels beside each service’s Compose configuration can reduce the need to maintain a separate list of destinations. The trade-off is that you must learn Traefik’s configuration concepts. Entrypoints define where requests enter, and routers match requests. Services identify where Traefik sends requests. Middleware adjusts requests as they pass through. Traefik also recommends disabling exposure by default and protecting its dashboard with authentication; its Docker quick-start marks insecure dashboard mode as suitable for development.
Docker access deserves deliberate review. Traefik’s Docker provider needs access to Docker’s API for discovery, so the socket mount is a meaningful security boundary. Check how your deployment grants that access and keep the dashboard restricted to trusted users.
Traefik uses ACME certificate resolvers, configuration that tells it how to request certificates through ACME. In the documented v3.4 setup, the ACME storage file needs permissions that limit who can access it. Multiple Traefik instances should not share that file. Keep that certificate state persistent and confirm the requirements for the version you deploy in the Traefik v3.4 ACME documentation.
Choose Traefik when Docker is central to your lab and you want route definitions to live with the services they expose. That convenience comes with more configuration concepts and responsibility for Docker access and dashboard security.
NPM lets you create proxy hosts through a web interface rather than editing Nginx configuration for routine tasks. Its project documentation explains that NPM supports both Let’s Encrypt and custom certificates. It also provides access lists and basic HTTP authentication.
The project’s Docker setup maps ports 80 and 443 for web traffic and port 81 for the administration interface in its example. Keep that administrative interface on a trusted management network rather than forwarding it from the internet. NPM also stores application data and certificate files in persistent storage, which keeps them when you replace the container. Include those directories in your backup and recovery plan.
The interface lowers the barrier for operators who prefer forms and visual controls. If you need unusual Nginx behavior, NPM offers an advanced configuration field, but using it requires familiarity with Nginx directives and moves some work beyond the normal host-management screens.
Choose NPM when you expect to add or change routes interactively and value a browser-based interface more than a plain, reviewable route file. The interface is another service to secure and maintain.
A certificate that secures the browser-to-proxy connection does not automatically encrypt the proxy-to-service connection. If you need encryption on that second connection, configure TLS between the proxy and the backend service separately. Caddy documents this as a distinct reverse-proxy setting.
Certificate setup also does not decide whether a service should be reachable from the internet. Before exposing a service, decide whether outside access is intended and restrict access to proxy administration surfaces. For local-only hostnames, account for whether client devices trust the certificate authority that issued the certificate.
Back up more than the container definition. Caddy needs persistent data and configuration; Traefik needs its ACME state; and NPM needs its application data and certificate files. A replacement container does not, by itself, restore those files.
Pick Caddy if you want a concise configuration file, a modest number of stable routes, and automatic HTTPS with little routine certificate work. Pick Traefik if you want Docker labels to define routes alongside container services and are prepared to manage its dashboard and Docker access carefully. Pick NPM if you want to add and edit proxy hosts through a browser.
None has a documented, apples-to-apples performance advantage over the others for a typical home lab. The practical choice comes down to whether you prefer explicit files, Docker-driven discovery, or a graphical interface.
Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.
Take a look at vroni.com