Serving Local Apps Securely with Caddy and Authentik: Fixing TLS Warnings in Development
Hi there! I’m Maneshwar. Right now, I’m building LiveAPI, a first-of-its-kind tool that helps you automatically index API endpoints across all your repositories. LiveAPI makes it easier to discover, understand, and interact with APIs in large infrastructures.
When you’re building a full-stack platform with multiple services — like a frontend UI, a backend API (e.g., backend Server), and an auth system (e.g., Authentik) — you often want to wire them together with secure, production-like communication… even in local development.
That’s where Caddy shines. It’s a modern web server that handles automatic HTTPS, reverse proxies, compression, and more — all with a friendly config format.
But when you use:
tls internal
Caddy issues self-signed TLS certificates for *.localhost
domains, which are not trusted by browsers out of the box. This results in:
❗️”Your connection is not private”
❗️”NET::ERR_CERT_AUTHORITY_INVALID”
Even though everything works.
So let’s fix that, and look at use cases for this setup.
Caddy Setup: Serving Authentik, backend, and dev
Here’s what we’re running in Docker:
Service | URL | Port |
---|---|---|
dev UI | https://dev.localhost |
3030 |
backend Server | https://backend.localhost |
1337 |
Authentik | https://auth.localhost |
9000 |
And the Caddy config looks like this:
dev.localhost {
tls internal
encode zstd gzip
reverse_proxy onprem-dev-ui-1:3030
log {
output stdout
format console
}
}
backend.localhost {
tls internal
encode zstd gzip
reverse_proxy onprem-backend-server-1:1337
log {
output stdout
format console
}
}
auth.localhost {
tls internal
encode zstd gzip
reverse_proxy onprem-server-1:9000
log {
output stdout
format console
}
}
http://dev.localhost, http://backend.localhost, http://auth.localhost {
redir https://{host}{uri} permanent
}
🔁 The reverse proxy uses Docker container names like
onprem-backend-server-1
, which are resolvable inside Docker’s internal network.
The Problem: TLS Certificate Warning
When you visit https://auth.localhost
, the app loads, but your browser screams:
- “Not secure”
- “NET::ERR_CERT_AUTHORITY_INVALID”
This happens because Caddy’s internal CA is not trusted by your OS or browser.
The Fix: Trust Caddy’s Internal CA
If you’re on Linux, run:
sudo cp ~/.local/share/caddy/pki/authorities/local/root.crt /usr/local/share/ca-certificates/caddy-root.crt
sudo update-ca-certificates
Then restart your browser.
This installs the root certificate used by Caddy into your system trust store. Now your browser trusts the *.localhost
HTTPS certs Caddy issues.
Why Use This Setup?
Running your stack locally with TLS and auth has huge benefits:
1. Test OAuth / OIDC flows locally
Authentik (or any IdP) will reject http://
callbacks. With Caddy and TLS, you can test these flows safely on https://localhost
.
2. Mirror production setup
If your production setup uses reverse proxies, TLS, and secure cookies, mimicking that in local dev avoids surprises later.
3. Cleaner URLs
Instead of localhost:3000
, localhost:9000
, etc., you get:
https://dev.localhost
https://auth.localhost
https://backend.localhost
4. Secure internal communication
If you’re testing things like JWT, session cookies, or API security policies, HTTPS is often required.
Bonus: Add .localhost
domains
Make sure you add entries to your /etc/hosts
file:
127.0.0.1 dev.localhost
127.0.0.1 auth.localhost
127.0.0.1 backend.localhost
Conclusion
With a little bit of Caddy config and one-time CA installation, you can run a production-grade HTTPS setup on localhost, complete with secure OAuth logins via Authentik and multiple reverse proxied apps.
This gives you confidence in local development and makes debugging identity flows and secure APIs far easier.
LiveAPI helps you get all your backend APIs documented in a few minutes.
With LiveAPI, you can generate interactive API docs that allow users to search and execute endpoints directly from the browser.
If you’re tired of updating Swagger manually or syncing Postman collections, give it a shot.