# Configuration: security and TLS

> Source: https://ferron.sh/docs/configuration/security/tls

This page documents the TLS crypto directives available inside a `tls { ... }` block. These settings are optional. Ferron uses safe defaults when you omit a setting. The TLS crypto configuration applies to all TLS providers: `manual`, `acme`, and `local`.

## Directives

### Certificate and private key

- `cert <path: string>`
  - This directive specifies the path to the server certificate file. The file must be PEM format and include the leaf certificate and any intermediate certificates your CA requires.
- `key <path: string>`
  - This directive specifies the path to the server private key file. The file must be PEM format.

These directives are required for the `manual` provider. Make sure Ferron can read both files. The certificate and private key must match.
If they do not match, TLS handshakes fail. You can use environment variable interpolation for the paths.

**Configuration example:**

```ferron
example.com {
    tls {
        provider manual
        cert /path/cert.pem
        key /path/key.pem
    }
}
```

### Cipher suites

- `cipher_suite <suite: string>`
  - This directive specifies a cipher suite to add to the allowed list. Repeatable. Each occurrence adds one suite. When you omit it, rustls defaults apply. Default: rustls defaults.

**Configuration example:**

```ferron
example.com {
    tls {
        provider manual
        cert /path/cert.pem
        key /path/key.pem
        cipher_suite TLS_AES_128_GCM_SHA256
        cipher_suite TLS_AES_256_GCM_SHA384
    }
}
```

#### Supported cipher suites

| Suite                                           | Protocol | Key exchange  | Notes                               |
| ----------------------------------------------- | -------- | ------------- | ----------------------------------- |
| `TLS_AES_128_GCM_SHA256`                        | TLS 1.3  | Any           | Default in most deployments         |
| `TLS_AES_256_GCM_SHA384`                        | TLS 1.3  | Any           | Stronger encryption                 |
| `TLS_CHACHA20_POLY1305_SHA256`                  | TLS 1.3  | Any           | Software-optimized, good for mobile |
| `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`       | TLS 1.2  | ECDHE + ECDSA | ECDSA certificate required          |
| `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`       | TLS 1.2  | ECDHE + ECDSA | ECDSA certificate required          |
| `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256` | TLS 1.2  | ECDHE + ECDSA | ECDSA certificate required          |
| `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`         | TLS 1.2  | ECDHE + RSA   | RSA certificate                     |
| `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`         | TLS 1.2  | ECDHE + RSA   | RSA certificate                     |
| `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256`   | TLS 1.2  | ECDHE + RSA   | RSA certificate                     |

> [!note]
> TLS 1.2 cipher suites are only effective when `min_version` allows TLS 1.2.

### ECDH curves

- `ecdh_curve <curve: string>`
  - This directive specifies an ECDH key exchange group to add to the allowed list, in priority order. Repeatable. Each occurrence adds one curve. When you omit it, rustls defaults apply. Default: rustls defaults.

#### Supported curves

| Curve            | Type                   | Notes                                              |
| ---------------- | ---------------------- | -------------------------------------------------- |
| `x25519`         | ECDH                   | Fast, widely supported, recommended default        |
| `secp256r1`      | ECDH                   | NIST P-256, required for some compliance standards |
| `secp384r1`      | ECDH                   | NIST P-384, higher security level                  |
| `x25519mlkem768` | Hybrid (ECDH + ML-KEM) | Post-quantum hybrid with `x25519`                  |
| `mlkem768`       | ML-KEM                 | Pure post-quantum KEM                              |

### TLS protocol version

- `min_version <version: string>`
  - This directive specifies the minimum allowed TLS version. Supported values: `TLSv1.2`, `TLSv1.3`. Default: `min_version TLSv1.2`
- `max_version <version: string>`
  - This directive specifies the maximum allowed TLS version. Supported values: `TLSv1.2`, `TLSv1.3`. Default: `max_version TLSv1.3`

**Configuration example: TLS 1.3 only**

```ferron
example.com {
    tls {
        provider manual
        cert /path/cert.pem
        key /path/key.pem
        min_version TLSv1.3
        max_version TLSv1.3
    }
}
```

If you omit both, Ferron uses the safe default range (TLS 1.2 to 1.3). Setting only `min_version` restricts the lower bound. Setting only `max_version` restricts the upper bound. Ferron returns an error if `max_version` is older than `min_version`.

### Client certificate authentication (mTLS)

- `client_auth [bool: boolean]`
  - This directive turns client certificate authentication on or off. When `true`, clients must present a valid certificate. Default: `client_auth false`
- `client_auth_ca <source: string>`
  - This directive specifies the source of trusted CA certificates for verifying client certificates. Supported values: a file path (`"/path/ca-cert.pem"`), `system` (OS native root store, requires `native-certs` feature), `webpki` (Mozilla root bundle). Default: `client_auth_ca webpki`

**Configuration example: full mTLS**

```ferron
api.example.com {
    tls {
        provider manual
        cert /etc/ssl/api.example.com/cert.pem
        key /etc/ssl/api.example.com/key.pem

        min_version TLSv1.3
        max_version TLSv1.3

        cipher_suite TLS_AES_256_GCM_SHA384
        cipher_suite TLS_CHACHA20_POLY1305_SHA256

        ecdh_curve x25519
        ecdh_curve secp256r1

        client_auth
        client_auth_ca /etc/ssl/internal-ca/ca-bundle.pem
    }
}
```

> [!note]
>
> - Client certificate verification uses the same trust model as server-side TLS. The client cert chain must validate against the configured CA roots.
> - When `client_auth_ca` points to a file with multiple CA certificates (a bundle), Ferron loads all of them into the trust store.
> - The `system` trust store includes all OS-trusted root CAs. Use it only when you want to accept client certificates from any publicly trusted CA. This is rarely the right choice for mTLS.
> - For internal mTLS deployments, use a private CA and set `client_auth_ca` to the CA bundle file path.

> [!tip]
>
> If you see client certificate handshake failures:
>
> - Verify the client certificate chain validates against the CA specified in `client_auth_ca`.
> - Check that the CA certificate file is a valid PEM and has not expired.
> - If you use `client_auth_ca system`, make sure the OS trusts the issuing CA.

## Observability

### Metrics

| Metric                             | Type  | Attributes                                                                        | Description                                  |
| ---------------------------------- | ----- | --------------------------------------------------------------------------------- | -------------------------------------------- |
| `ferron.tls.certificate_not_after` | Gauge | `ferron.host`, `ferron.tls.provider` (`http`), `crypto.certificate.serial_number` | Certificate `notAfter` as Unix epoch seconds |

## See also

- [ACME automatic TLS](https://ferron.sh/docs/configuration/security/acme.md)
- [TLS session ticket keys](https://ferron.sh/docs/configuration/security/session-tickets.md)
- [OCSP stapling](https://ferron.sh/docs/configuration/security/ocsp.md)

## Best practices

`ferron doctor` reports the following best-practice checks for directives on this page.

- **`max_version TLSv1.2`**: Disabling TLS 1.3 reduces security and performance. Allow TLS 1.3 unless legacy clients require TLS 1.2 only.
- **`client_auth` with public trust store**: Using `system` or `webpki` roots for mTLS client authentication trusts any certificate from the public PKI. Use a private CA bundle file for mTLS instead.
- **`ocsp` disabled**: OCSP stapling improves TLS privacy, performance, and revocation behavior. Keep it enabled.
- **`ticket_keys` without `auto_rotate`**: Session ticket keys should rotate automatically in production to limit the impact of key compromise.
- **`ticket_keys.max_keys` outside 2–5**: The optimal range keeps enough old keys for rotation without interruption and without excessive retention.
- **`ticket_keys.rotation_interval` > 24h**: Rotate session ticket keys every 12–24 hours in production.