From 082b8cf660cd7b28afc12e0beb2877fecb5f0900 Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Tue, 5 May 2026 04:02:25 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20Phase=204=20follow-on=20batch=203=20?= =?UTF-8?q?=E2=80=94=205=20file-based=20target=20per-pages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracts the file-based deploy target connectors: - haproxy.md (107 lines) — combined-PEM (cert+chain+key) deploy with haproxy -c validate; multi-frontend + crt-list directory guidance - traefik.md (105 lines) — file-provider zero-reload deploy; file watcher latency notes; mixing with built-in ACME guidance - caddy.md (100 lines) — admin API mode (recommended) vs file mode; admin-API exposure threat model - envoy.md (112 lines) — file SDS mode (recommended) vs static bootstrap; service-mesh interactions - postfix.md (175 lines) — dual-mode (Postfix MTA / Dovecot IMAPS) connector with daemon-specific quirks (STARTTLS chain expectations, no shared session cache); Bundle 11 test pins Index forward-list expanded to enumerate all 10 target connectors (5 from Phase 4 structural + 5 from this batch) in alphabetical order. This is part 3 of 4 for the Phase 4 follow-on (per-connector page extraction) tracked in cowork/docs-overhaul-phase-2-restructure-2026-05-04/log.md. Net add: 5 files, 599 lines. No content removed from index.md. --- docs/reference/connectors/caddy.md | 100 +++++++++++++++ docs/reference/connectors/envoy.md | 112 +++++++++++++++++ docs/reference/connectors/haproxy.md | 107 ++++++++++++++++ docs/reference/connectors/index.md | 15 ++- docs/reference/connectors/postfix.md | 175 +++++++++++++++++++++++++++ docs/reference/connectors/traefik.md | 105 ++++++++++++++++ 6 files changed, 609 insertions(+), 5 deletions(-) create mode 100644 docs/reference/connectors/caddy.md create mode 100644 docs/reference/connectors/envoy.md create mode 100644 docs/reference/connectors/haproxy.md create mode 100644 docs/reference/connectors/postfix.md create mode 100644 docs/reference/connectors/traefik.md diff --git a/docs/reference/connectors/caddy.md b/docs/reference/connectors/caddy.md new file mode 100644 index 0000000..f3a49df --- /dev/null +++ b/docs/reference/connectors/caddy.md @@ -0,0 +1,100 @@ +# Caddy Connector — Operator Deep-Dive + +> Last reviewed: 2026-05-05 +> +> Operator-grade documentation for the Caddy target connector. For +> the connector-development context (interface contract, registry, +> atomic deploy primitive shared across all targets), see the +> [connector index](index.md). + +## Overview + +The Caddy connector supports two deployment modes: + +- **API mode (recommended).** Posts the certificate directly to + Caddy's admin API for zero-downtime hot reload. +- **File mode (fallback).** Writes cert and key files to disk, + relying on Caddy's built-in file watcher or a manual reload. + +Implementation lives at `internal/connector/target/caddy/`. + +## When to use this connector + +Use the Caddy connector when: + +- Caddy fronts your services and you want certctl-managed certs + rather than letting Caddy run its own ACME client. +- You want zero-downtime hot reload via Caddy's admin API. + +Look elsewhere when: + +- You'd rather Caddy keep running its own ACME client — point it + at certctl's ACME server (see + [migration/acme-from-caddy.md](../../migration/acme-from-caddy.md)) + for the cleanest pattern. + +## Configuration + +```json +{ + "mode": "api", + "admin_api": "http://localhost:2019", + "cert_dir": "/etc/caddy/certs", + "cert_file": "site.crt", + "key_file": "site.key" +} +``` + +When `mode` is `"api"`, the connector posts the certificate to +the admin API endpoint. When `mode` is `"file"`, it writes files +to `cert_dir` (same pattern as Traefik). The `admin_api` field is +ignored in file mode. + +## Mode trade-offs + +### API mode + +- Zero-downtime hot reload via `POST /load` or + certificate-specific endpoints. +- Requires Caddy's admin API to be enabled and reachable from the + deployment agent. +- Best fit for production deployments where Caddy is configured + with an admin endpoint. + +### File mode + +- Writes cert and key files to `cert_dir`; Caddy picks them up + via its file watcher or on next config reload. +- Use when the admin API isn't available or when Caddy is + configured to read certificates from disk. +- Behaviorally equivalent to the [Traefik](traefik.md) connector. + +## Deploy contract + +API mode bypasses the Bundle I file-write deploy primitive and +talks directly to the Caddy admin API. File mode follows the +standard atomic-write + verify path (idempotency check → backup +→ atomic write → optional reload → post-deploy TLS verify). + +## Operator playbook + +### Admin API exposure + +Caddy's admin API is an unauthenticated control surface by +default. In API mode, ensure the admin API is bound to a +loopback or trusted network — exposing it to the public would +let anyone reload Caddy's config. Run the agent on the same host +as Caddy and use `http://localhost:2019` for the safest posture. + +### Falling back to file mode + +If the admin API is intermittently unreachable, switch the +target's `mode` to `file` via `PUT /api/v1/targets/{id}`. The +deploy still lands; reload behaviour is whatever the operator's +Caddy config does with file changes. + +## Related docs + +- [Connector index](index.md) — interface contract, registry, deploy primitive +- [Traefik](traefik.md) — comparable file-provider target +- [Migration: point Caddy at certctl's ACME](../../migration/acme-from-caddy.md) — alternative pattern when Caddy should keep its ACME client diff --git a/docs/reference/connectors/envoy.md b/docs/reference/connectors/envoy.md new file mode 100644 index 0000000..dadd06d --- /dev/null +++ b/docs/reference/connectors/envoy.md @@ -0,0 +1,112 @@ +# Envoy Connector — Operator Deep-Dive + +> Last reviewed: 2026-05-05 +> +> Operator-grade documentation for the Envoy target connector. For +> the connector-development context (interface contract, registry, +> atomic deploy primitive shared across all targets), see the +> [connector index](index.md). + +## Overview + +The Envoy connector uses **file-based certificate delivery** — it +writes certificate and key files to a directory that Envoy watches +via its SDS (Secret Discovery Service) file-based configuration or +static `filename` references in the bootstrap config. When files +change, Envoy automatically picks up the new certificates without +requiring a reload command. + +Implementation lives at `internal/connector/target/envoy/`. + +## When to use this connector + +Use the Envoy connector when: + +- Envoy fronts your services (standalone, as part of a service + mesh, or as an API gateway like Emissary or Gloo). +- You want certctl to drive cert rotation and let Envoy's file + SDS handle the rolling reload across worker threads. + +Look elsewhere when: + +- You're running an Envoy-based service mesh (Istio, Consul + Connect) — those meshes have their own cert distribution + pipelines, and integrating certctl at the mesh layer is a + different design than this connector covers. +- You're using Envoy's xDS/gRPC SDS path (not file-based SDS) — + the gRPC SDS-server connector is on the V3-Pro roadmap. + +## Configuration + +```json +{ + "cert_dir": "/etc/envoy/certs", + "cert_filename": "cert.pem", + "key_filename": "key.pem", + "chain_filename": "chain.pem", + "sds_config": true +} +``` + +| Field | Type | Default | Description | +|---|---|---|---| +| `cert_dir` | string | (required) | Directory where Envoy watches for certificate files | +| `cert_filename` | string | `cert.pem` | Filename for the certificate (leaf + chain unless `chain_filename` is set) | +| `key_filename` | string | `key.pem` | Filename for the private key | +| `chain_filename` | string | (empty) | If set, chain is written to a separate file instead of appended to the cert | +| `sds_config` | bool | `false` | If true, writes an `sds.json` file for Envoy's file-based SDS provider | + +## SDS mode (recommended for production) + +When `sds_config` is `true`, the connector writes an SDS JSON +file (`{cert_dir}/sds.json`) containing a `tls_certificate` +resource that points to the cert and key file paths. Envoy's +file-based SDS (`path_config_source`) watches this file for +changes, providing automatic hot-reload of certificates without +restarting worker threads. + +This is the recommended approach for production Envoy deployments +using dynamic TLS configuration. + +## Static-bootstrap mode + +When `sds_config` is `false` (the default), the connector simply +writes cert and key files. Use this mode when Envoy's bootstrap +config references the cert / key files directly via static +`filename` fields in the TLS context. + +In this mode Envoy still picks up file changes via its filesystem +watcher, but the operator should verify the bootstrap config sets +`watched_directory` (or equivalent) on each `tls_certificate` +entry — without it, the cert is loaded once at startup and +subsequent file changes are ignored. + +## Deploy contract + +Standard atomic-write + post-deploy verify (file-based deploy +primitive shared across all file-deploy connectors). When SDS +mode is on, the SDS JSON file is updated last so Envoy sees the +cert / key on disk before the SDS resource pointer changes. + +## Operator playbook + +### Hot-reload across worker threads + +Envoy's file SDS path triggers a per-worker-thread reload as each +worker re-reads the SDS file. In-flight TLS connections on each +worker continue with the OLD cert until they close; new +connections after the reload pick up the NEW cert. + +### Service mesh interactions + +If you're running Istio or Consul Connect, the mesh's own cert +distribution pipeline (citadel / SDS server) is the system of +record for sidecar certs. Don't point this connector at sidecar +cert paths — point it at standalone Envoy gateways or API edges +that aren't sidecar-managed. + +## Related docs + +- [Connector index](index.md) — interface contract, registry, deploy primitive +- [NGINX](nginx.md) — explicit-reload-command counterpart +- [Traefik](traefik.md) — file-watcher counterpart with simpler semantics diff --git a/docs/reference/connectors/haproxy.md b/docs/reference/connectors/haproxy.md new file mode 100644 index 0000000..9ec6731 --- /dev/null +++ b/docs/reference/connectors/haproxy.md @@ -0,0 +1,107 @@ +# HAProxy Connector — Operator Deep-Dive + +> Last reviewed: 2026-05-05 +> +> Operator-grade documentation for the HAProxy target connector. +> For the connector-development context (interface contract, +> registry, atomic deploy primitive shared across all targets), see +> the [connector index](index.md). + +## Overview + +HAProxy differs from NGINX and Apache in one important way: it +expects all TLS material in a **single combined PEM file** — +certificate, intermediate chain, and private key concatenated. The +connector builds this combined file, writes it with 0600 +permissions (since it contains the private key), optionally +validates the HAProxy configuration, and reloads. + +Implementation lives at `internal/connector/target/haproxy/`. + +## When to use this connector + +Use the HAProxy connector when: + +- HAProxy fronts your applications and you want certctl to + rotate the cert + chain + key in place atomically without + hand-rolling the combined-PEM build. +- You want validate-before-reload behaviour to keep a bad config + from taking down the load balancer mid-rotation. + +Look elsewhere when: + +- You're running HAProxy Enterprise's hot-cert-update API path — + the connector currently uses the file-write-and-reload model; + the API path is on the V3-Pro roadmap. +- You're not running HAProxy directly but a managed load balancer + (AWS ALB, Azure Application Gateway). Use the cloud-native + target connector for that platform instead. + +## Configuration + +```json +{ + "pem_path": "/etc/haproxy/certs/site.pem", + "reload_command": "systemctl reload haproxy", + "validate_command": "haproxy -c -f /etc/haproxy/haproxy.cfg" +} +``` + +The combined PEM is built in this order: server certificate, +intermediate / chain certificates, private key. + +The `validate_command` is optional — if omitted, the connector +skips config validation and goes straight to reload. Keeping it +on is the production-recommended posture. + +## Deploy contract + +Every cert deploy follows the Bundle I `deploy.Apply(ctx, plan)` +flow: + +1. **Idempotency check** — SHA-256 over the combined PEM bytes; + skip if the destination already matches. +2. **Pre-deploy backup** — copy existing PEM to + `.certctl-bak.`. +3. **Atomic write** — temp-file + chown + atomic rename. +4. **PreCommit (validate)** — runs `haproxy -c -f + /etc/haproxy/haproxy.cfg`. Failure aborts; no live cert + touched. +5. **Atomic rename** — temp → final. +6. **PostCommit (reload)** — runs `systemctl reload haproxy` (or + the operator's override). +7. **Post-deploy TLS verify** — dials the configured endpoint + when configured; pulls leaf cert SHA-256; compares against + deployed bytes. Mismatch triggers automatic rollback. + +## Operator playbook + +### Old cert served via session resumption + +HAProxy keeps TLS sessions alive for the configured +`tune.ssl.lifetime` (default 1h). Resumed clients see the OLD +cert until their session expires. Post-deploy verify in certctl +returns the NEW cert from a fresh handshake; warm clients see the +OLD cert until session expiration. + +### Multi-frontend deployments + +When HAProxy serves multiple frontends with different certs, +configure **one target per frontend's cert** in the certctl +control plane. Each gets its own `pem_path`. The reload command +is shared (HAProxy reloads all frontends together), so the +deploys can land in any order; the final reload picks them all up. + +### `crt-list` directories + +If your HAProxy config uses a `crt-list` directory rather than a +single PEM, set `pem_path` to a file inside the directory and let +HAProxy enumerate it on reload. The connector treats `pem_path` +as a single file regardless of HAProxy's directory semantics. + +## Related docs + +- [Connector index](index.md) — interface contract, registry, deploy primitive +- [NGINX](nginx.md) — separate-file deploy contract counterpart +- [Apache](apache.md) — separate-file deploy contract with `apachectl configtest` +- [Migration: ACME from HAProxy](../../migration/acme-from-caddy.md) — pattern for pointing edge proxies at certctl's ACME server (Caddy walkthrough; HAProxy ACME plumbing is similar) diff --git a/docs/reference/connectors/index.md b/docs/reference/connectors/index.md index 1d705f6..5fa0a8f 100644 --- a/docs/reference/connectors/index.md +++ b/docs/reference/connectors/index.md @@ -32,11 +32,16 @@ Issuer connectors: Target connectors: -- [Apache](apache.md) — Apache httpd connector deep dive -- [F5 BIG-IP](f5.md) — F5 connector deep dive (proxy agent + iControl REST) -- [IIS](iis.md) — Microsoft IIS connector deep dive (local PowerShell + WinRM modes) -- [Kubernetes Secrets](k8s.md) — k8s.io/tls Secrets connector deep dive -- [NGINX](nginx.md) — NGINX connector deep dive (deploy contract + quirks) +- [Apache](apache.md) — Apache httpd, separate-file deploy + `apachectl configtest` +- [Caddy](caddy.md) — admin-API hot reload or file-watcher fallback +- [Envoy](envoy.md) — file SDS hot reload, optional `sds.json` +- [F5 BIG-IP](f5.md) — proxy-agent pattern + transactional iControl REST +- [HAProxy](haproxy.md) — combined-PEM deploy + `haproxy -c` validate +- [IIS](iis.md) — Microsoft IIS, local PowerShell + WinRM modes +- [Kubernetes Secrets](k8s.md) — k8s.io/tls Secrets atomic update +- [NGINX](nginx.md) — separate-file deploy + `nginx -t` validate +- [Postfix / Dovecot](postfix.md) — dual-mode mail-server TLS connector +- [Traefik](traefik.md) — file-provider zero-reload deploy ## Contents diff --git a/docs/reference/connectors/postfix.md b/docs/reference/connectors/postfix.md new file mode 100644 index 0000000..7c5fa2d --- /dev/null +++ b/docs/reference/connectors/postfix.md @@ -0,0 +1,175 @@ +# Postfix / Dovecot Connector — Operator Deep-Dive + +> Last reviewed: 2026-05-05 +> +> Operator-grade documentation for the Postfix / Dovecot mail server +> TLS connector. For the connector-development context (interface +> contract, registry, atomic deploy primitive shared across all +> targets), see the [connector index](index.md). + +## Overview + +A dual-mode mail-server TLS connector. Writes certificate, key, and +chain files to configured paths and reloads the mail service. The +`mode` field selects between Postfix MTA and Dovecot IMAP/POP3, +which determines default file paths and reload commands. + +This connector pairs with certctl's S/MIME certificate support +(email protection EKU, email SAN routing) for a complete email +infrastructure story — TLS for transport encryption, S/MIME for +end-to-end message signing and encryption. + +Implementation lives at `internal/connector/target/postfix/`. + +## When to use this connector + +Use the Postfix / Dovecot connector when: + +- You operate a self-hosted mail server (Postfix as MTA, Dovecot + as IMAPS/POP3S) and want certctl to rotate the TLS material in + place. +- You want validate-before-reload behaviour to keep a bad cert + config from taking down mail. + +Look elsewhere when: + +- You're running a mail provider (Google Workspace, Microsoft 365) + — the provider rotates certs internally. +- Your MTA is something else (Exim, Sendmail) — these don't have + built-in connectors yet; use a [generic file-based + target](index.md#target-connector) by hand or commission a + custom adapter. + +## Configuration + +### Postfix mode + +```json +{ + "mode": "postfix", + "cert_path": "/etc/postfix/certs/cert.pem", + "key_path": "/etc/postfix/certs/key.pem", + "chain_path": "/etc/postfix/certs/chain.pem", + "reload_command": "postfix reload", + "validate_command": "postfix check" +} +``` + +### Dovecot mode + +```json +{ + "mode": "dovecot", + "cert_path": "/etc/dovecot/certs/cert.pem", + "key_path": "/etc/dovecot/certs/key.pem", + "chain_path": "/etc/dovecot/certs/chain.pem", + "reload_command": "doveadm reload", + "validate_command": "doveconf -n" +} +``` + +### Field reference + +| Field | Default (Postfix) | Default (Dovecot) | Description | +|---|---|---|---| +| `mode` | `postfix` | `dovecot` | Service mode — determines defaults | +| `cert_path` | `/etc/postfix/certs/cert.pem` | `/etc/dovecot/certs/cert.pem` | Path for certificate file | +| `key_path` | `/etc/postfix/certs/key.pem` | `/etc/dovecot/certs/key.pem` | Path for private key (0600 permissions) | +| `chain_path` | (empty) | (empty) | If set, chain written separately; otherwise appended to cert | +| `reload_command` | `postfix reload` | `doveadm reload` | Command to reload the mail service | +| `validate_command` | `postfix check` | `doveconf -n` | Optional config validation before reload | + +All commands are validated against shell injection via +`validation.ValidateShellCommand()`. File permissions: cert / +chain 0644, key 0600. + +## Choosing Mode=postfix vs Mode=dovecot + +Both modes share the same Go connector code (atomic-write, +PreCommit/PostCommit hooks, post-deploy verify, rollback), so the +rollback contract is identical across modes. The mode flag just +swaps the daemon-specific defaults. + +`mode: postfix` is also the **default when `mode` is unset**. + +### Hosts running BOTH Postfix and Dovecot + +The common mail-server pattern. Configure **two separate targets** +in the certctl control plane, one per daemon. Each gets its own +cert path, its own validate / reload command, and its own +optional verify endpoint. The cert + key bytes can be identical +across the two targets if your mail server uses the same TLS +material for both daemons (which many do); certctl does not +deduplicate the deploys, but the byte-equal cert hits the +SHA-256 idempotency short-circuit on subsequent renewals when +the target paths haven't changed. + +### Sharing a single cert file across daemons via symlink + +Works fine with the connector — the atomic-write path's +`os.Rename` follows symlinks. Configure both targets to point at +the same canonical path, or have one target's `cert_path` +symlink into the other's. Operators who want byte-deduplication +should rely on this approach rather than asking certctl to +coordinate it. + +## Daemon-specific quirks + +### Postfix STARTTLS (port 25) + +Typically requires the cert to chain to a public root for +receiving mail from arbitrary external MTAs that validate +SMTP-side server certs. If you're deploying a self-signed cert +from `iss-local`, configure the receiving Postfix accordingly +(e.g. `smtpd_use_tls=yes` + `smtpd_tls_security_level=may` for +opportunistic TLS so external senders that don't validate +continue to deliver). + +### Dovecot IMAPS (port 993) + +Typically client-facing — the chain you ship matters more here +because IMAPS clients (Thunderbird, Outlook) actively validate. +Set `chain_path` if your certificate chain is supplied +separately; when `chain_path` is unset, the connector appends the +chain bytes to `cert_path`. + +### No shared TLS session cache + +Postfix and Dovecot do not share a TLS session cache by default. +Both reload independently, so a cert renewal that updates both +targets via certctl requires both reloads to succeed before +clients re-handshake. The two targets are fully independent in +the certctl scheduler — one reload failing rolls back that +target only. + +## Post-deploy verify + +Operator-supplied via `post_deploy_verify` (`enabled` + +`endpoint` + `timeout`) — the connector does NOT bake in a +per-mode default port. Operators that opt in should set +`endpoint` to their daemon's listener (e.g. `mail.example.com:25` +for Postfix STARTTLS, `mail.example.com:993` for Dovecot IMAPS). + +## Test pins + +Bundle 11 (commit `88e8881`) added end-to-end tests for +`Mode=dovecot`: + +- `TestPostfix_Atomic_DovecotMode_HappyPath` — confirms + `applyDefaults` populates the dovecot validate + reload + commands AND the deploy threads them through to `runValidate` + + `runReload`. +- `TestPostfix_Atomic_DovecotMode_VerifyFails_Rollback` — + confirms the rollback path under `Mode=dovecot` restores + pre-deploy cert + key bytes byte-exact. + +The `Mode=postfix` branch has equivalent test coverage in the +same file (see `TestPostfix_HappyPath`, +`TestPostfix_VerifyMismatch_Rollback`, +`TestPostfix_ReloadFails_Rollback`). + +## Related docs + +- [Connector index](index.md) — interface contract, registry, deploy primitive +- [NGINX](nginx.md) — comparable file-based deploy with explicit reload +- [Apache](apache.md) — comparable file-based deploy with `apachectl configtest` diff --git a/docs/reference/connectors/traefik.md b/docs/reference/connectors/traefik.md new file mode 100644 index 0000000..83c1909 --- /dev/null +++ b/docs/reference/connectors/traefik.md @@ -0,0 +1,105 @@ +# Traefik Connector — Operator Deep-Dive + +> Last reviewed: 2026-05-05 +> +> Operator-grade documentation for the Traefik target connector. +> For the connector-development context (interface contract, +> registry, atomic deploy primitive shared across all targets), see +> the [connector index](index.md). + +## Overview + +The Traefik connector uses Traefik's **file provider** — it writes +certificate and key files to a watched directory, and Traefik +automatically picks up the changes without any explicit reload +command. This is the simplest deployment model in the catalog: +write the files, Traefik does the rest. + +Implementation lives at `internal/connector/target/traefik/`. + +## When to use this connector + +Use the Traefik connector when: + +- Traefik fronts your services with the file provider configured + (`providers.file.directory` in Traefik's static config). +- You want a no-reload deployment path — Traefik picks up file + changes automatically. + +Look elsewhere when: + +- You're running Traefik with its built-in ACME client. Either + point Traefik at certctl's ACME server (see + [migration/acme-from-traefik.md](../../migration/acme-from-traefik.md)) + or let certctl-issued certs flow through this file-provider + connector — but don't run both. +- Traefik is not exposed (e.g. behind another reverse proxy that + terminates TLS); the front-most TLS terminator is what wants + the cert. + +## Configuration + +```json +{ + "cert_dir": "/etc/traefik/certs", + "cert_file": "site.crt", + "key_file": "site.key" +} +``` + +The `cert_dir` is the directory Traefik is configured to watch +via its file provider. The connector writes `cert_file` and +`key_file` into this directory with appropriate permissions +(0644 for the cert, 0600 for the key). Traefik's file watcher +detects the change and reloads the TLS configuration +automatically. + +## Deploy contract + +Every cert deploy follows the Bundle I `deploy.Apply(ctx, plan)` +flow: + +1. Idempotency check on cert + key bytes. +2. Pre-deploy backup of existing files. +3. Atomic write of cert + key to temp paths. +4. Atomic rename of temp paths to final cert / key paths. +5. **No reload command** — Traefik's file watcher handles it. +6. Post-deploy TLS verify when configured (dials the endpoint; + pulls leaf cert SHA-256; compares). + +The validate / reload / rollback semantics that NGINX and HAProxy +depend on don't apply here — Traefik's file watcher is the +"reload"; if Traefik fails to load the new file, that's a Traefik +problem visible in Traefik's logs, and the previous cert remains +served until Traefik retries. + +## Operator playbook + +### File watcher latency + +Traefik's file watcher polls the directory; the cert may take a +few seconds to be picked up after the atomic rename. Post-deploy +verify with `PostDeployVerifyAttempts: 5` and a small backoff +covers this comfortably. + +### Multi-router deployments + +Traefik routes traffic by hostname, and the file provider can +expose multiple certs in the same directory. Configure one +certctl target per cert (one `cert_file` + `key_file` pair per +hostname); they all land in the same watched directory and +Traefik picks them up. + +### Mixing file provider with ACME + +If Traefik is also running its own ACME client, both can write to +the same `certificatesResolvers` config but with different +storage backends. Best practice: don't mix. Pick one source of +truth — either Traefik's ACME or certctl-supplied files — and +delete the other config block from `traefik.yml`. + +## Related docs + +- [Connector index](index.md) — interface contract, registry, deploy primitive +- [NGINX](nginx.md) — explicit-reload deploy contract counterpart +- [Migration: point Traefik at certctl's ACME](../../migration/acme-from-traefik.md) — alternative pattern when Traefik should pull rather than have certctl push