Skip to content

Certificate Management

fuomag9 edited this page Sep 26, 2026 · 9 revisions

Certificate Management

Manage automatic ACME certificates and imported custom certificates in Caddy Proxy Manager.

Table of Contents

  1. How Certificates Work
  2. Certificates Page Overview
  3. ACME Certificates
  4. Import Custom Certificates
  5. CA Certificates
  6. Renewal and Monitoring
  7. Security Considerations
  8. Troubleshooting

How Certificates Work

Caddy Proxy Manager supports three certificate paths:

  1. Automatic ACME (recommended)
  • Caddy obtains and renews certificates automatically (Let's Encrypt / ZeroSSL)
  • Used when a proxy host is set to Managed by Caddy (Auto)
  1. Imported custom certificates
  • You provide certificate PEM + private key PEM
  • Use for internal CAs, pre-issued wildcard certs, or compliance-driven workflows
  1. Built-in CA certificates
  • Generate your own CA root and issue internal client certificates
  • Useful for mTLS or testing without a third-party CA

Certificates Page Overview

The Certificates page has four tabs:

  1. ACME
  • Proxy hosts currently using automatic certificate management
  • Shows domains and host enabled/disabled status
  • Server-side paginated
  1. Imported
  • Custom certificates stored in the application database
  • Shows issuer, expiry, covered domains, and which hosts use each cert
  1. CA / mTLS
  • Internal CA roots you have created
  • Issue and manage client certificates signed by each CA
  • Download CA cert, issue new client certs, view existing issued certs
  1. Roles
  • mTLS roles: named groups of issued client certificates
  • Used by path-based access rules on mTLS proxy hosts (see Feature Guide mTLS RBAC)

ACME Certificates

What is shown

For each auto-managed proxy host, the table displays:

  • Proxy host name
  • Domains
  • Host enabled/disabled status

Caddy manages ACME certificate issuance and renewal entirely on its own. The ACME tab lists which proxy hosts use automatic certificates so you can verify coverage.

Pagination

ACME table pagination is server-side.

  • Default page size: 25
  • URL param: ?page=N

Import Custom Certificates

Use custom import when ACME is not suitable.

Import flow

  1. Open Certificates.
  2. Scroll to Import Custom Certificate.
  3. Provide:
    • Certificate Name
    • Domains (one per line)
    • Certificate PEM (full chain recommended)
    • Private Key PEM
  4. Click Import Certificate.

Manage imported certs

For each imported certificate, you can:

  • Review issuer and expiry
  • See covered domains
  • See which proxy hosts currently reference it
  • Update fields
  • Delete the certificate

When editing, keep certificate/key pairs matched.


CA Certificates

The built-in CA lets you create internal certificate authorities and issue client certificates.

Create a CA

  1. Open Certificates → CA / mTLS.
  2. Click Add CA Certificate.
  3. On the Generate tab, provide a name, a Common Name (CN) and a validity period.
  4. Click Generate CA Certificate.

The Import PEM tab adds an existing CA certificate without a private key. CPM can then validate client certificates signed by that CA, but cannot issue new ones.

The CA root certificate can be downloaded and installed in browsers or systems that need to trust certificates issued by this CA.

Issue a Client Certificate

  1. Open the menu on a CA row and select Issue Client Cert, or expand the row and click Issue Cert.
  2. Provide a Common Name (CN), a Validity in days and an Export Password. Compatibility mode (on by default) uses 3DES for broader OS/browser import compatibility; turn it off for AES-256.
  3. Click Issue Certificate.
  4. Download the .p12 bundle (client certificate, private key and CA chain). The client private key is not stored server-side.

These options appear only for a CA with a stored private key. With Feature Guide Instance Sync, issue client certificates on the master (see CA private keys).

Manage Issued Certificates

Expand a CA row to see its active client certificates, and click Manage to list all certificates issued by that CA, including expiry status, and to revoke them.

CA private keys

(since v1.13.1)

CA private keys are encrypted at rest (AES-256-GCM, keyed from SESSION_SECRET), like imported certificate keys. Keys stored in plaintext by older releases are encrypted on the next start, which logs Encrypted N legacy CA private key(s).

With Instance Sync, CA private keys never leave the master: slaves receive the CA certificates without their private keys, and the first sync removes the copies that older versions stored on slaves. As a result:

  • Slaves keep validating client certificates, but cannot issue new ones.
  • A slave promoted to master cannot issue certificates from the existing CAs.
  • To keep the ability to issue, back up the master's database together with its SESSION_SECRET. Neither is usable without the other.

If SESSION_SECRET changes without the old value in SESSION_SECRET_PREVIOUS, issuing fails with a clear error (see Troubleshooting). Client certificates already issued keep working, because validating them only needs the CA certificate.

When to use the built-in CA

  • mTLS (mutual TLS) between services inside a trusted network
  • Internal testing without purchasing a certificate
  • Short-lived client credentials that need to be revoked quickly

Renewal and Monitoring

ACME certificates

  • Renewed automatically by Caddy
  • Renewal attempts start before expiration
  • No manual renew action is usually required

Imported certificates

  • Not auto-renewed
  • You must replace them before expiry

Operational checks

Caddy logs:

docker compose logs caddy | grep -i acme

Public endpoint check:

echo | openssl s_client -connect your-domain.com:443 2>/dev/null | openssl x509 -noout -issuer -dates

Security Considerations

Private key storage

Imported certificate private keys and CA private keys are stored in SQLite, encrypted at rest with AES-256-GCM, using a key derived from SESSION_SECRET (CA private keys since v1.13.1). Anyone who has both the database and SESSION_SECRET can decrypt them.

After rotating SESSION_SECRET, keep the old value in SESSION_SECRET_PREVIOUS at least until the next successful start, so the stored keys are re-encrypted with the new secret (see Security Configuration#secret-rotation).

Recommendations:

  • Restrict data volume/file permissions
  • Encrypt backups
  • Rotate imported certificates regularly
  • Limit host access to backup artifacts

Certificate source of truth

  • ACME cert material lives in Caddy's data volume
  • Imported cert material lives in the app database
  • CA private keys live in the app database (with Instance Sync, only on the master; slaves hold the CA certificates only)

Troubleshooting

Certificate issuance fails

Checks:

  1. Domain resolves to your public IP
  2. Ports 80/443 reachable (for HTTP-01/TLS-ALPN-01)
  3. DNS provider configured in Settings → DNS Providers for wildcard or internal-only domains (see DNS Provider Configuration)
  4. ACME email configured in Settings

Imported cert rejected or unusable

Verify key/cert match:

openssl x509 -noout -modulus -in cert.pem | openssl md5
openssl rsa  -noout -modulus -in key.pem  | openssl md5

Hashes must match.

Client certificate issuance fails

  • "The CA private key cannot be decrypted with the current SESSION_SECRET. Restore the previous secret via SESSION_SECRET_PREVIOUS or create a new CA." The CA key was encrypted under an earlier SESSION_SECRET. Set that value in SESSION_SECRET_PREVIOUS and recreate the web container (docker compose up -d), or create a new CA. Certificates already issued by the CA keep working. The web container logs Failed to decrypt the private key of CA certificate <id>; SESSION_SECRET may have changed.
  • No Issue Client Cert option: the CA has no stored private key. This is the case for CAs added through Import PEM, and for every CA on an Instance Sync slave. Issue client certificates on the master.

Related Documentation


Need help? Open an issue with logs and non-sensitive certificate metadata (never share private keys).

Clone this wiki locally