> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oximail.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# TLS & ACME

> Certificates for HTTPS, SMTP, and IMAP: built-in ACME (Let's Encrypt) in direct topology, the reverse-proxy alternative, hot-reload without restarts, and the DANE coupling on renewal.

One certificate serves every TLS surface of the instance: HTTPS/JMAP (443), submission STARTTLS (587), SMTPS (465), and IMAPS (993). Who obtains it depends on the deployment topology chosen at [first boot](../first-boot).

## Direct topology: built-in ACME

When OxiMail owns ports 80/443, it provisions and renews its own certificate from Let's Encrypt:

```toml theme={null}
[tls]
acme_enabled = true
acme_email = "admin@example.com"

[server]
http_bind = "0.0.0.0:80"    # serves the HTTP-01 challenge (and redirects to HTTPS)
```

The first certificate is obtained at first start (the wizard polls up to 90 seconds for it); renewals happen automatically before expiry. Port 80 must stay reachable from the internet for the HTTP-01 challenge. The wizard also publishes a **CAA record** restricting issuance to Let's Encrypt — a hardening default that matches this setup.

**Renewal never restarts anything.** The new certificate is swapped in atomically (`ArcSwap`, ADR-016): in-flight connections finish on the old one, new handshakes get the new one, on every listener at once — including SMTP and IMAP.

## Reverse-proxy topology

Behind Caddy or Nginx, the proxy owns 80/443 and its own certificates, and `acme_enabled = false` (the wizard writes this for you). Two things still matter:

* **SMTP (25/587/465) and IMAPS (993) are not proxied** — OxiMail terminates TLS on those itself. It needs a certificate for them: point `[tls]` at the proxy's certificate files or run a deploy hook that copies them, and remember OxiMail hot-reloads the files when they change.
* The proxy must forward **everything** except the ACME challenge path, with `X-Forwarded-For` from a `[server] trusted_proxies` address — see [first boot](../first-boot) for the generated snippet and the CORS rule.

## The DANE coupling

If you publish DANE TLSA records ([email authentication](./email-auth-security#dane-tlsa)), a certificate renewal **changes the pinned value** — stale TLSA records break inbound mail from every DANE-validating sender. This is why `dane_enabled = true` (the default) requires a configured `[dns]` provider: after each renewal, OxiMail republishes the TLSA records itself (Cloudflare API), or logs a loud reminder in `manual` mode. A server with DANE on and no DNS provider **refuses to start** rather than setting you up for that silent breakage.

## Post-quantum key exchange

Every TLS surface OxiMail terminates itself — 25, 465, 587, 993, and 443 when no frontend sits in front of it — offers the hybrid group **X25519MLKEM768** first, with classic X25519 as the fallback. Nothing is broken by this: a peer that does not know the hybrid group negotiates X25519 exactly as it did before. There is no setting to turn it on; it is the shipped posture.

The threat it addresses is "harvest now, decrypt later" — an adversary recording today's traffic to decrypt it when the machine to do so exists. Being late to this is not recoverable after the fact, which is why it is a default rather than an option. It is not theoretical for mail either: Gmail already offers the hybrid group on 25, 465, 587 and 993, so outbound mail to it negotiates post-quantum today.

**Verify rather than assume.** This exact property was silently false here for months: the feature was compiled in while the installed crypto provider had no implementation for it, so every session fell back to classic X25519 while the configuration read as if it did not. A fallback of this kind is invisible — no error, no log line, nothing failing. With OpenSSL 3.5 or later:

```bash theme={null}
openssl s_client -connect mail.example.com:993 -groups X25519MLKEM768 </dev/null 2>&1 | grep -i 'group'
```

Run it against each port you serve. A frontend in front of 443 answers for itself, not for OxiMail — check the mail ports separately.

**Outbound connections use the same stack.** The HTTP client behind webhooks, push and link unfurling runs on the same TLS implementation as the server, so those fetches gained the hybrid group too. It trusts the **operating system's** certificate store rather than a bundled root list, deliberately: an endpoint whose certificate chains to a private CA installed on the box keeps working.

## Inspecting and troubleshooting

```bash theme={null}
oximail cert ...                      # inspect the active certificate(s)
journalctl -u oximail | grep -i acme  # challenge and renewal history
```

Common failures:

* **Port 80 unreachable** — the HTTP-01 challenge cannot complete. Open it in the firewall/security group; it only ever serves challenges and redirects.
* **Rate-limited by Let's Encrypt** — repeated failed issuance attempts (usually a DNS mistake) can hit LE's per-domain limits; fix the cause before retrying in a loop.
* **DANE senders bouncing after a renewal** — the TLSA records were not updated. Check the `[dns]` provider configuration and the renewal logs.

Certificates and ACME account material live under `/etc/oximail/tls/`, which is part of the standard [backup surface](./operations#backups).
