1. The problem I’m having:
I’m trying to setup a Caddy web-server with client authentication (mTLS).
From the client machine, I can take a generated client P12 file debian-pc.p12, pass it into a cURL command with the self-signed root cert root.crt that i used to sign it, and I’m able to connect just fine.
Full cURL output:
curl -vL --cert-type P12 --cert debian-pc.p12:<password> --cacert root.crt https://10.0.0.2
* Trying 10.0.0.2:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: root.crt
* CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_CHACHA20_POLY1305_SHA256 / X25519MLKEM768 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
* subject:
* start date: Feb 20 13:16:05 2026 GMT
* expire date: Feb 21 01:16:05 2026 GMT
* subjectAltName: host "10.0.0.2" matched cert's IP address!
* issuer: CN=Caddy Local Authority - ECC Intermediate
* SSL certificate verify ok.
* Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* Certificate level 2: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* Connected to 10.0.0.2 (10.0.0.2) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://10.0.0.2/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: 10.0.0.2]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.14.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: 10.0.0.2
> User-Agent: curl/8.14.1
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Request completely sent off
< HTTP/2 421
< alt-svc: h3=":443"; ma=2592000
< server: Caddy
< content-length: 0
< date: Fri, 20 Feb 2026 19:02:23 GMT
<
* Connection #0 to host 10.0.0.2 left intact
However, once I import the P12 file into the Firefox Certificate Manager and try to connect via browser, it throws Error code: 421 Misdirected Request.
Things I’ve tried:
- Clearing browser cache
- Clearing DNS cache
- Running
caddy truston the client machine - Disabling HTTP/2 in the browser
- Using Chrome (same error)
- Starting over and completely regenerating the root CA and all keys
- Restarting caddy
- Restarting the server and client machines
- Reinstalling Firefox
This has been giving me a lot of trouble over the past few days. I’m new to Caddy and not a pro at any of this stuff, so do bear with me, any help is much appreciated ![]()
2. Error messages and/or full log output:
journalctl -u caddy -f
Feb 20 13:36:53 home-pi caddy[4082]: {"level":"info","ts":1771612613.8604417,"msg":"define JAVA_HOME environment variable to use the Java trust"}
Feb 20 13:36:53 home-pi caddy[4082]: {"level":"info","ts":1771612613.872483,"logger":"tls","msg":"storage cleaning happened too recently; skipping for now","storage":"FileStorage:/var/lib/caddy/.local/share/caddy","instance":"b9500594-97dd-4a23-b613-b5476e5f3957","try_again":1771699013.8724763,"try_again_in":86399.999997656}
Feb 20 13:36:53 home-pi caddy[4082]: {"level":"info","ts":1771612613.872899,"logger":"tls","msg":"finished cleaning storage units"}
Feb 20 13:36:53 home-pi sudo[4096]: pam_unix(sudo:auth): conversation failed
Feb 20 13:36:53 home-pi sudo[4096]: pam_unix(sudo:auth): auth could not identify password for [caddy]
Feb 20 13:36:53 home-pi sudo[4096]: caddy : user NOT in sudoers ; PWD=/ ; USER=root ; COMMAND=/usr/bin/tee /usr/local/share/ca-certificates/Caddy_Local_Authority_-_2026_ECC_Root_181431030895689318105254784498745868923.crt
Feb 20 13:36:53 home-pi caddy[4082]: {"level":"error","ts":1771612613.8992815,"logger":"pki.ca.local","msg":"failed to install root certificate","error":"failed to execute sudo: exit status 1","certificate_file":"storage:pki/authorities/local/root.crt"}
Feb 20 13:36:53 home-pi caddy[4082]: {"level":"info","ts":1771612613.9001496,"msg":"autosaved config (load with --resume flag)","file":"/var/lib/caddy/.config/caddy/autosave.json"}
Feb 20 13:36:53 home-pi caddy[4082]: {"level":"info","ts":1771612613.9003837,"msg":"serving initial configuration"}
Feb 20 13:36:53 home-pi systemd[1]: Started caddy.service - Caddy.
3. Caddy version:
caddy version: 2.6.2
4. How I installed and ran Caddy:
I followed the instructions for stable release installation under the Debian, Ubuntu, Raspbian header in the Caddy docs: Install — Caddy Documentation
a. System environment:
Client: Debian, x86_64 CPU, no docker
Server: Raspberry Pi OS, aarch64 CPU
b. Command:
systemctl stop caddy
systemctl start caddy
systemctl restart caddy
d. My complete Caddy config:
# The Caddyfile is an easy way to configure your Caddy web server.
#Test website
10.0.0.2 {
#Caddy example lives here
root * /usr/share/caddy
file_server
#mTLS verify client
tls {
client_auth {
#Default is none
#there are other options here if you want it to be optional
#i.e. to bypass a signin page when using mTLS
mode require_and_verify
trust_pool file {
#Can be specified multiple times for multiple roots
pem_file /etc/caddy/root.pem
}
}
}
}
5. Links to relevant resources:
- I followed this guide exactly to setup Caddy with mTLS Securely Expose your Homelab Services with Mutual TLS :: apalrd's adventures
- Same tutorial but as a video https://www.youtube.com/watch?v=YhuWay9XJyw
Thanks!