Caddy as Internal CA and Reverse Proxy
I’m using Caddy v2 as my internal Certificate Authority (CA) and as a reverse proxy for my self-hosted services. In this post, I’ll show you how I’ve set up the CA in my HomeLab and rolled out the root CA certificate to my clients.
General
There are a few reasons why Caddy is a great choice for me when it comes to managing my internal web services:
- Automatic HTTPS with Internal CA
- Centralized reverse proxy
- Load balancing and failover support
- Lightweight and resource efficient
- Low maintenance overhead
- HTTP/2 and HTTP/3 support
- Zero runtime dependencies
- Memory safety guarantees
See the full list of Caddy features here
Installation and Configuration
Installing Caddy is straightforward. Just follow the official instructions:
Caddy internal CA
One of its key features is that Caddy automatically handles TLS certificates, including renewal and management. By default, Caddy v2 creates a Let’s Encrypt cert automatically. Since I only need it for internal purposes, I’ll stick with Caddy’s internal CA, which is powered by the open source libraries Smallstep. The trust chain consists of a root certificate and an intermediate certificate. Leaf certificates are signed by the intermediate. They are stored in Caddy’s data directory at pki/authorities/local
.
After configuring the PKI in the Caddyfile, all we need to do is install the root CA certificate on the hosts so that the generated website certificates will be trusted. Once the certificate is installed, the services can be accessed securely without any SSL warnings.
Internal CA configuration
Just define a general pki
module in the Caddyfile /etc/caddy/Caddyfile
. This example shows an Internal CA called homelab_ca
and a host using the reverse proxy module:
{
# Enable caddy internal CA
pki {
ca internal {
name homelab_ca
}
}
}
# Example service
example.internal {
tls internal
reverse_proxy 192.168.0.99:8080
}
The configuration can be checked, and if there are any syntax errors, Caddy can simply format it correctly. Then the Caddy service needs to be restarted:
caddy validate --config /etc/caddy/Caddyfile
caddy fmt --overwrite /etc/caddy/Caddyfile
systemctl restart caddy
Verify certificate
We can quickly check the website’s certificate using openssl
: openssl s_client -connect example.internal:443
Root CA Certificate Rollout
Then we need to deploy the root CA certificate to the clients’ trust stores so that the hosts know that they can trust our newly created CA.
Caddy stores the root certificate of its internal CA in a default location: /var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt
Note: Web browsers may use their own trust store instead of the system’s one. I tested it on Ubuntu 22.04, 24.04 and Windows 10 using Firefox, Chrome, Vivaldi and Brave. And on Android using Firefox and Chrome:
- On Windows, I simply installed the certificate in the system trust store and all the browsers I tested worked out of the box
- On Linux, I had to add the certificate to each browser’s own trust store
- On Android, Chrome used the system trust store, but Firefox did not
Copy root CA certification from your server
To copy a CA certificate to another system, you can use a secure file transfer protocol, such as scp
. You can also simply copy and paste it from the command line to another system, as the certificate is just a text file. Or you can use your preferred and available file sharing system, such as a NAS, SMB, etc.
Linux
Example with scp
:
- Caddy server:
cp /var/lib/caddy/.local/share/caddy/pki/authorities/internal/root.crt / /home/user/caddy_homelab_ca.crt chown user:user /home/user/caddy_homelab_ca.crt
- Debian-based workstation:
- Copy certificate to local system and CA directory
scp caddy-server:caddy_homelab_ca.crt ~/Downloads/caddy_homelab_ca.crt sudo mv ~/Downloads/caddy_homelab_ca.crt /usr/local/share/ca-certificates
- Update the system’s list of trusted certificates
sudo update-ca-certificates
- You can check that the certificate has been installed by checking the output of the update-ca-certificates command, which should show that a new certificate has been added.
- You can also check to see if the file exists:
ls /etc/ssl/certs/caddy_homelab_ca.pem
- And, of course, with OpenSSL to see the details.
openssl x509 -in /etc/ssl/certs/caddy_homelab_ca.pem -text -noout
- Copy certificate to local system and CA directory
- Caddy server, clean up
rm /home/user/caddy_homelab_ca.crt
Depending on your distribution or desktop environment, you may be able to install the CA by double-clicking the caddy_homelab_ca.crt
file and clicking Import
.
Linux - Firefox
- In the menu, go to:
Preferences
>Privacy & Security
>Certificates
>View Certificates
>Authorities
>Import
, and select thecaddy_homelab_ca.crt
file - Mozilla KB - Setting CA
Windows
- Double-click the
caddy_homelab_ca.crt
file and follow the prompts:
Install Certificat
->Local Machine
->Trusted Root Certification Authorities
- You can also use the command line:
certutil -addstore -f "ROOT" caddy_homelab_ca.crt
Android
I’m using a Google Pixel 6 as an example:
- Go to the preferences:
Settings
>Security
>More security settings
>Encryption & credentials
>Install a certificate
>CA certificate
- Then, select the file:
caddy_homelab_ca.crt
- Verify Installation:
- Under
Encryption & credentials
go toTrusted Credentials
- Look under the
User
tab to see the installed certificate
- Under
- Firefox, there is a setting in the Firefox developer mode so that the browser uses the phone trust store:
- Open Firefox
Settings
->About Firefox
- Click on the logo 5 times (until “Debug menu enabled” popup appears)
- Go back to
Settings
- there should be aSecret Settings
option now - In the new appeared setting, enable
Use third party CA certificates
- Open Firefox
Automation
At a later stage, I will be creating an Ansible playbook for the rollout of the root CA certificate.