TLS: what you can tune
Pingclair’s TLS surface is deliberately small: a name gets a certificate automatically, and the settings that decide how that happens are the ones documented here. Anything else Caddy accepts is refused by name rather than ignored, so a configuration never quietly does less than it says. This page collects what actually works, measured on a real host, and what does not.
🧾 Before you start
Section titled “🧾 Before you start”- Pingclair installed and running (Install).
- For the certificate-authority parts, a name that resolves to the host, or the internal authority for a lab machine (HTTPS).
🌐 Which protocols are served
Section titled “🌐 Which protocols are served”The protocol set lives in the global servers block:
{ servers { protocols h1 h2 h3 }}Measured with sudo ss -lun | grep ':443 ':
| Configuration | UDP 443 listener |
|---|---|
protocols h1 h2 |
0 — no HTTP/3 |
protocols h1 h2 h3 |
1 — HTTP/3 enabled |
⚠️ The list decides HTTP/3, and only HTTP/3. Listing h1 alone does not take
HTTP/2 away: with protocols h1, a client that offered h2 in ALPN still
negotiated HTTP/2. The compiler maps this list onto the HTTP/3 switch
(config.global.http3 = protocols.contains(H3)), so there is no setting that
disables HTTP/2 for a name.
Per site, http3 off takes that name out of HTTP/3 without stopping the QUIC
listener:
https://internal.test { tls { internal http3 off } file_server /srv/site}🏛️ Certificate sources
Section titled “🏛️ Certificate sources”Three sources, all shown on the HTTPS page:
| Source | Configuration | What it is for |
|---|---|---|
| Let’s Encrypt | a bare public name | Public names, renewed in the background. |
| Internal authority | tls internal |
Lab names, private origins, tunnels. |
| Your own files | tls { cert … key … } |
Certificates issued elsewhere. |
Renewal runs on its own; renewal_window_ratio in the global options changes how
early it starts, as a fraction of each certificate’s lifetime.
🔐 Client certificates
Section titled “🔐 Client certificates”client_auth requires a certificate from the client. Generate a small authority
and a client certificate with openssl, then point the site at the authority’s
file:
https://internal.test { tls { internal client_auth { mode require_and_verify trusted_ca_cert_file /etc/pingclair/client-ca.crt } } file_server /srv/site}Measured: a request without a client certificate fails the handshake, and the
same request with --cert client.crt --key client.key answers 200.
The modes are request, require, verify_if_given, and require_and_verify,
and there is no fallback: a misspelled mode is refused with the whole list
(expected request, require, verify_if_given or require_and_verify).
⚠️ trusted_ca_cert takes the certificate inline, and trusted_ca_cert_file
takes a path. Using the first with a path compiles, and then fails at startup
with trusted_ca_cert is not a certificate: not valid base64: Invalid symbol 45
— the - of -----BEGIN. The file also has to be readable by the pingclair
user.
📦 Moving the certificate store
Section titled “📦 Moving the certificate store”The store holds the issued certificates, the ACME account, and the internal
authority, and it lives at /var/lib/pingclair/.local/share/pingclair — the data
directory of the service user’s home. PINGCLAIR_TLS_STORE names it when a
command runs as somebody else, which is why the examples below prefix it: root’s
own default would be /root/.local/share/pingclair. storage-export and
storage-import move it:
sudo PINGCLAIR_TLS_STORE=/var/lib/pingclair/.local/share/pingclair pingclair storage-export -o /tmp/store.tarsudo systemctl stop pingclairsudo rm -rf /var/lib/pingclair/.local/share/pingclairsudo mkdir -p /var/lib/pingclair/.local/share/pingclair && sudo chown pingclair:pingclair /var/lib/pingclair/.local/share/pingclairsudo PINGCLAIR_TLS_STORE=/var/lib/pingclair/.local/share/pingclair pingclair storage-import -i /tmp/store.tarsudo systemctl start pingclair✅ Store exported to /tmp/store.tar✅ Store imported into /var/lib/pingclair/.local/share/pingclairThree details from the run. The archive is a plain tar whatever it is named,
and it is written mode 600, so reading it back needs root. The import restores
the ownership recorded in the archive. And the store holds autosave.json, the
configuration the Admin API last applied, so an import restores that too.
If the service refuses to start afterwards with
Internal CA I/O error: Permission denied, the store’s files are not writable by
the service account; sudo chown -R pingclair:pingclair /var/lib/pingclair/.local/share/pingclair
fixes it, and the site answers again.
🚫 What cannot be tuned
Section titled “🚫 What cannot be tuned”These are Caddy settings that Pingclair recognizes and refuses, so the file never runs with the setting silently dropped:
Caddy-compatible directive 'tls ciphers' is not supported by Pingclair yet: Pingclair does not implement this TLS option yetCaddy-compatible directive 'tls curves' is not supported by Pingclair yet: Pingclair does not implement this TLS option yetCaddy-compatible directive 'tls alpn' is not supported by Pingclair yet: Pingclair does not implement this TLS option yetCaddy-compatible directive 'tls on_demand' is not supported by Pingclair yet: Pingclair does not implement this TLS option yetIn practice that means: cipher suites, curves, the ALPN list, and on-demand
issuance are the build’s choices, not the configuration’s; OCSP stapling and
preferred_chains are not implemented either. If one of them matters to you, it
is a feature request rather than a configuration mistake.
⚠️ When it does not work
Section titled “⚠️ When it does not work”client_authrefuses to start withnot valid base64. A path was given totrusted_ca_cert; the file spelling istrusted_ca_cert_file.- A client with a valid certificate is rejected. Check the CA that signed it
is the one in
trusted_ca_cert_file, and that the certificate has not expired. tls ciphers/tls curves/tls alpn/tls on_demandrefuse the file. They are not implemented; see the section above.- HTTP/3 still runs after
protocols h1 h2. It should not — that list is what controls it. If UDP 443 is still listening, the file that is running is not the file you edited (what a reload means). - The service will not start after moving a store. Ownership, as above.
