# 🛡️ TLS: what you can tune

📌 TLS examples below use `./certs/` in the working directory. Supply your own certificate, matching private key, or client CA file there; validation reads these files too.

Pingclair obtains certificates for site names automatically. This page covers
certificate sources, HTTP/3, and client certificate authentication. Unsupported
TLS options are rejected when the configuration loads.

📌 This page describes **v0.2.2**.

## 🧾 Before you start

- Pingclair installed and running ([Install](/start/install/)).
- For the certificate-authority parts, a name that resolves to the host, or the
  internal authority for a lab machine ([HTTPS](/start/https/)).

## 🌐 Which protocols are served

Configure protocols in the global `servers` block:

```caddyfile
{
    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 controls **HTTP/3** only. Listing `h1` alone does not
turn HTTP/2 off: with `protocols h1`, a client that offered `h2` still
negotiated HTTP/2. The only thing the server reads from the list is whether `h3`
is in it, so no setting disables HTTP/2. Without a `protocols` line, HTTP/3 is
on.

The per-site `http3 off` option disables HTTP/3 for that site while the QUIC
listener continues serving other sites:

```caddyfile
https://internal.test {
    tls {
        internal
        http3 off
    }
    file_server /srv/site
}
```

`http3 off` refuses this site's QUIC handshake and removes its HTTP/3 advertisement from `Alt-Svc`. Other sites on the port may continue to use QUIC.

## 🏛️ Certificate sources

Three sources, all shown on the [HTTPS page](/start/https/):

| 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 in the background. The global `renewal_window_ratio` option sets
how early it starts, as a fraction of each certificate's lifetime.

## 🔐 Client certificates

`client_auth` configures the server to request a client certificate. Create a small
authority and a client certificate with `openssl`, then point the site at the
authority's certificate **file**:

```caddyfile
https://internal.test {
    tls {
        internal
        client_auth {
            mode require_and_verify
            trusted_ca_cert_file ./certs/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`. 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 itself, base64-encoded on one line,
and `trusted_ca_cert_file` takes a path. Giving a path to the first compiles,
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.

An exact site's `client_auth` policy takes precedence over a wildcard on the same port. An exact site with no block requires no client certificate; add its own block if it must require one. Certificates whose usage extensions exclude client authentication are refused.

## 📦 Moving the certificate store

The store contains the issued certificates, the ACME account, and the internal
authority. For a package install it is `/var/lib/pingclair/.local/share/pingclair`,
the data directory under the service account's home. A command run as another
user looks in that user's own data directory, so the examples set
`PINGCLAIR_TLS_STORE`; without it, root would use `/root/.local/share/pingclair`.
`storage-export` and `storage-import` move the store:

```bash
sudo PINGCLAIR_TLS_STORE=/var/lib/pingclair/.local/share/pingclair pingclair storage-export -o /tmp/store.tar
sudo systemctl stop pingclair
sudo rm -rf /var/lib/pingclair/.local/share/pingclair
sudo mkdir -p /var/lib/pingclair/.local/share/pingclair && sudo chown pingclair:pingclair /var/lib/pingclair/.local/share/pingclair
sudo PINGCLAIR_TLS_STORE=/var/lib/pingclair/.local/share/pingclair pingclair storage-import -i /tmp/store.tar
sudo systemctl start pingclair
```

```text
✅ Store exported to /tmp/store.tar
✅ Store imported into /var/lib/pingclair/.local/share/pingclair
```

The archive is an **uncompressed tar**, regardless of its filename,
written with mode `600`, so reading it back needs root. The import restores the
ownership recorded in the archive. The store also contains `autosave.json`, the
configuration the Admin API last applied, so an import restores that too.

📌 **0.2.0:** the internal authority uses Caddy's directory layout, under
`pki/authorities/local/`. The old `internal/` directory is not migrated: the
server creates a new authority, and every client must trust the new root
again (`pingclair trust`). A global `storage file_system <path>` option can also
name the store in the configuration.

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`
restores the required ownership.

<span id="-what-cannot-be-tuned"></span>

## 🚫 Unsupported TLS options

The following Caddy TLS options are recognized but rejected during
configuration loading:

```text
Caddy-compatible directive 'tls ciphers' is not supported by Pingclair yet: Pingclair does not implement this TLS option yet
Caddy-compatible directive 'tls curves' is not supported by Pingclair yet: Pingclair does not implement this TLS option yet
Caddy-compatible directive 'tls alpn' is not supported by Pingclair yet: Pingclair does not implement this TLS option yet
Caddy-compatible directive 'tls on_demand' is not supported by Pingclair yet: Pingclair does not implement this TLS option yet
```

Cipher suites, curves, the ALPN list, and on-demand issuance are therefore
fixed by the build, not by the configuration. OCSP stapling is not performed
either. These settings cannot be enabled through configuration.

<span id="️-when-it-does-not-work"></span>

## ⚠️ Troubleshooting

- **`client_auth` refuses to start with `not valid base64`.** A path was given to
  `trusted_ca_cert`; the file spelling is `trusted_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_demand` refuse the
  file.** They are not implemented; see the section above.
- **HTTP/3 still runs after `protocols h1 h2`.** It should not, because that list
  controls it. If UDP 443 is still listening, the file that is running is
  not the file you edited ([what a reload means](/start/service/#-what-a-reload-means)).
- **The service will not start after moving a store.** Ownership, as above.

## 🧭 Next steps

- [HTTPS](/start/https/): the four certificate sources, with their exact
  log lines.
- [HTTP/3](/guides/http3/): configuration and client-side protocol verification.
- [`tls`](/reference/directives/#tls): the directive reference.
