"Not secure" after providing self-signed certificates and using reverse proxy

1. The problem I’m having:

We deploy our application with pm2 and use caddy as a reverse proxy. We recieved a self-signed certificate from the customer in a pfx format. I exported the client and private key with these commands:

openssl pkcs12 -in file.pfx -nocerts -out private.key -nodes
openssl pkcs12 -in file.pfx -clcerts -nokeys -out certificate.crt

(I later also exported the CA with openssl pkcs12 -in file.pfx -cacerts -nokeys -chain | openssl x509 -out cacerts.crt and added them with the ca_root module (tried ca module as well))

When running caddy the website returns with a “Not secure” warning everytime. I know that the pfx is legit, as we use it for a windows server, which is displaying the proper secure connection.

This setup has worked for other customers, the difference is that this customer user is usign a self-signed certificate rather than a paid-for one.

I have also tried the pfx caddy plugin which didn’t make a difference.

We are running caddy with docker compose. I tried running caddy bare metal but got the same issues. I also tried installing the ca certificate with sudo update-ca-certificates (I’m trying everything at this point).

2. Error messages and/or full log output:

{"level":"info","ts":1729596544.6084354,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
{"level":"info","ts":1729596544.608952,"msg":"adapted config to JSON","adapter":"caddyfile"}
{"level":"info","ts":1729596544.6093082,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//127.0.0.1:2019","//localhost:2019","//[::1]:2019"]}
{"level":"info","ts":1729596544.6094074,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc00044f580"}
{"level":"warn","ts":1729596544.6095397,"logger":"tls","msg":"stapling OCSP","error":"no OCSP stapling for [A1.domain.local A2.domain.local A3.domain.local]: no OCSP server specified in certificate"}
{"level":"info","ts":1729596544.6095731,"logger":"http.auto_https","msg":"skipping automatic certificate management because one or more matching certificates are already loaded","domain":"A1.domain.local","server_name":"srv0"}
{"level":"info","ts":1729596544.6095772,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
{"level":"info","ts":1729596544.6096992,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}
{"level":"info","ts":1729596544.6097817,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
{"level":"info","ts":1729596544.6097968,"logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}
{"level":"info","ts":1729596544.6098483,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}{"level":"info","ts":1729596544.6098523,"msg":"serving initial configuration"}
{"level":"info","ts":1729596544.6105423,"logger":"tls","msg":"cleaning storage unit","storage":"FileStorage:/data/caddy"}
{"level":"info","ts":1729596544.6106112,"logger":"tls","msg":"finished cleaning storage units"}

3. Caddy version:

caddy:2.8.4-alpine

4. How I installed and ran Caddy:

Inside docker compose

a. System environment:

Debian 12.7
Docker 27.3.1

b. Command:

docker compose up -d

c. Service/unit/compose file:

services:
  caddy:
    image: caddy:2.8.4-alpine
    restart: always
    cap_add:
      - NET_ADMIN
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    network_mode: host
    extra_hosts:
      - host.docker.internal:host-gateway
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
      - ./ssl/:/etc/ssl/certs/
      - ./private/:/etc/ssl/private/

volumes:
  caddy_data:
  caddy_config:

d. My complete Caddy config:

A1.domain.local {
	tls /etc/ssl/certs/client.crt /etc/ssl/private/private.key
	reverse_proxy localhost:3000
}

I also tried

A1.domain.local {
	tls /etc/ssl/certs/client.crt /etc/ssl/private/private.key {
		ca_root /etc/ssl/certs/ca.crt
	}
	reverse_proxy localhost:3000
}

5. Links to relevant resources:

This is the expected behavior for self-signed certificates in browsers. Self-signed certificates can be generated without any verification of domain ownership. Since the self-signed certificate is not signed by a public certificate authority, the browser will display the “not secure” warning.

https://www.entrust.com/resources/learn/what-is-a-self-signed-certificate

1 Like

You’d need to install the CA’s root cert to every client which connects. Modern browsers have their own trust stores now (separate from the system’s) so you also need to add it to those as necessary.

Why not use tls internal? Caddy can already automate this for you. It generates its own internal CA to issue certs, and you can use that root CA cert to install on your various clients.

2 Likes

@francislavoie @skip2networks
Thank you for your replies!

The company has their own CA in their internal network. The idea is to have our application in their environment, which can’t be reached from outside their network.

They provided certificates signed by their own CA. The CA is present on the windows machine trusted root certificate store, and in the browser. The other service we set up with windows, can be reached through their intranet and shows secure (it uses the exact same certificate that I’m trying to use, but I get “not secure” every time).

So to summarize, they have their own CA, which has issued us a pfx file. I extracted the certificate(s) + key, used the tls module in the Caddyfile, but we are still getting “Not secure”. Since it is self-signed, is it not possible to get the “Secures” connection in their intranet? Then why did the other service we set up (doesn’t use caddy) return “Secure” connection?

Thanks once again

If it’s signed by a CA, it’s not self-signed. Self-signed specifically means a certificate whose private key signed itself, i.e. no chain of trust.

What’s in your /etc/ssl/certs/client.crt exactly? It it only the leaf cert? If so, it needs to contain the full chain (root → intermediate → leaf) concatenated. If there’s an intermediate, it needs to be in there because your clients won’t know how to get the intermediate to complete the chain of trust (assuming the root is installed on the client).

1 Like

Oh, sorry for the misleading information!

I have tried having only the leaf cert in the client.crt, and parsing the root certificate with the ca_root module.

(im obfuscating the real domain with a1.domain.local)

a1.domain.local {                                                                                                                                                                                       
    tls /etc/ssl/certs/leaf.crt /etc/ssl/private/private.key {
        ca_root /etc/ssl/certs/ca.crt
    }                                                                                                                                 
    reverse_proxy localhost:3000
}

Currently, I’m using this, where i combined the leaf and root (and made sure they are in the right order.

a1.domain.local {                                                                                                                                                                                       
    tls /etc/ssl/certs/fullchain.crt /etc/ssl/private/private.key                                                                                                                                 
    reverse_proxy localhost:3000
}

There is no intermediate, so only leaf and root. So from what I can see, my setup should work, as caddy is not complaining about anything, but for some reason the webbrowser is still returning “Not secure”. I tried two browsers and made sure that the CA cert was present in both, and on the computer. I also confirmed that the domainname is present in the certificate.

Are you certain there’s no intermediate? It’s really unusual to have a root cert directly signing the leaf certs.

I’ve never used it but you might be able to use chrome://net-export in Chrome to get more detail about the reason for the error (possibly feed it into https://netlog-viewer.appspot.com/ to read it)

1 Like

Yes, I ran this command:

openssl pkcs12 -in file.pfx -nodes -chain -info

And got:

subject=CN = a1.domain.local
issuer=DC = local, DC = domain, CN = CompanyCAv1

subject=DC = local, DC = domain, CN = CompanyCAv1
issuer=DC = local, DC = domain, CN = CompanyCAv1

I tried feeding the exported log int netlog viewer.
The interesting part i found was:

"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"a1.domain.local"

I exported the CA from the pfx file again, made sure that I installed it on the debian machine and the windows machine I’m testing from (both are in their environment), and still got “Not secure”

Edit:

When I ran this:

openssl s_client -connect a1.domain.local:443 -servername a1.domain.local

It returned:
CN = a1.domain.local

Subject Alternative Name:
DNS:a2.domain.local
DNS:a3.domain.local

the domain names in the SAN are used for the windows systems (and are returning Secure). Could this be the issue? that a1.domain.locla is not in the SAN list?

Yes, the certificate needs SANs that match the TLS SNI the client requests. Caddy will not choose a certificate that does not have matching SANs. Using CN for TLS is deprecated since RFC 2818, published in May 2000 (CN is unsupported in Caddy).

1 Like

Thank you so much for all the help Francis! Have a nice day

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.