# ๐Ÿ” HTTPS A site block whose address is a public name gets HTTPS without a `tls` directive: Pingclair asks Let's Encrypt for a certificate over ACME, answers the HTTP-01 challenge on port 80, stores the result, and renews it in the background. The other three ways to get a certificate โ€” DNS-01, a local authority, and files you supply โ€” are covered below with what each one requires and what it actually does in v0.2.0-rc.3. ## ๐Ÿงพ Before you start - A name that resolves to this host. Check it before blaming the server: `dig +short A example.com`. - Ports 80 and 443 reachable from the internet. The HTTP-01 challenge is served on port 80, and the certificate is used on 443. - An email address for the ACME account. It must be a real mailbox: Let's Encrypt refuses the reserved example domains, and the issuance fails with `contact email has forbidden domain "example.com"`. The configuration below replaces `/etc/Pingclair/Pingclairfile`, which the service runs. Validate before reloading; [Quickstart](/start/quickstart/) shows that loop and [Run it as a service](/start/service/) explains the reload. ## ๐ŸŒ Certificates from Let's Encrypt ```caddyfile { email pingclair@pingclair.com } example.com { file_server /var/lib/pingclair/html } ``` There is nothing else to configure. At startup the server authorises the hostname, starts the ACME flow, and serves the challenge: ```text ๐ŸŒ Automatic public certificates authorised for 1 hostname(s) ๐Ÿš€ Eager issuance for 1 hostname(s) ๐Ÿ” Starting ACME flow for domains: ["example.com"] ๐Ÿ” Serving ACME challenge for token: Ix9X74-tENLdJY0F6f7kUe3TXkoXOxOyTb8iHcnv9Z4 โœ… Certificate stored successfully: example.com ๐ŸŽ‰ Certificate issuance complete for example.com ``` The challenge request in the access log comes from the certificate authority, not from a browser: ```text ๐Ÿ“ Access ... path="/.well-known/acme-challenge/Ix9X74-..." status=200 user_agent="Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)" ``` Verify what is actually served, from another machine: ```bash curl -I https://example.com/ ``` ```text HTTP/2 200 content-type: text/html; charset=utf-8 etag: "493b-6ab1f452" server: Pingclair ``` ```bash echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \ | openssl x509 -noout -subject -issuer -dates ``` ```text subject=CN=example.com issuer=C=US, O=Let's Encrypt, CN=YE2 notBefore=Sep 22 02:35:03 2026 GMT notAfter=Dec 21 02:35:02 2026 GMT ``` The certificate material is kept in the service user's data directory, `/var/lib/pingclair/.local/share/pingclair` โ€” the path the binary resolves from that account's home, which is also what `PINGCLAIR_TLS_STORE` names when a command runs as somebody else. ## ๐Ÿ“ก DNS-01 and wildcards DNS-01 proves control of a name by publishing a TXT record instead of answering on port 80, which is what a wildcard certificate requires. The configuration needs the provider block: ```caddyfile { email pingclair@pingclair.com } *.example.com { tls { auto dns cloudflare resolvers 1.1.1.1 propagation_delay 10s } file_server /var/lib/pingclair/html } ``` Two details are easy to miss. The `auto` line inside the block is what puts the name on the issuance list; without it the server logs `authorised for 0 hostname(s)` and never asks for a certificate, leaving every handshake to fail with `NO_CERTIFICATE_SET`. And the token is a Cloudflare API token with `Zone:DNS:Edit` for the zone that holds the name. โš ๏ธ **DNS-01 issuance does not complete in v0.2.0-rc.3.** The challenge itself runs: the record is published, propagation is confirmed against the resolver the configuration names, and the authority is asked to validate. The order then comes back invalid within a second, on every name tried, and the certificate is never stored: ```text ๐Ÿ“ก Published the DNS-01 record for _acme-challenge.example.com via cloudflare ๐Ÿ‘ DNS-01 record for _acme-challenge.example.com is visible ๐Ÿš€ Verification triggered for example.com โณ Polling order status... โš ๏ธ Eager issuance failed for example.com: Order ended in state: Invalid ``` Until that is fixed, use HTTP-01 for public names. A wildcard name therefore cannot be served with a certificate yet; the alternative is one name per certificate, or a certificate you issue elsewhere and supply as files. ## ๐Ÿ›๏ธ Certificates from the internal authority For private origins โ€” a tunnel, an internal hostname, a lab machine โ€” Pingclair can be its own authority: ```caddyfile https://internal.test { tls internal file_server /var/lib/pingclair/html } ``` The site answers with a certificate issued by `CN=Pingclair Local Authority` for ten years, and the root is published in the store: ```bash sudo ls -l /var/lib/pingclair/.local/share/pingclair/internal/ ``` ```text -rw------- 1 pingclair pingclair 652 Sep 22 03:40 root.crt ``` Clients do not trust it yet, so a request without `-k` fails. Install the root into the system trust store: ```bash sudo PINGCLAIR_TLS_STORE=/var/lib/pingclair/.local/share/pingclair pingclair trust ``` ```text โœ… Internal CA root installed into the system trust store ``` The `PINGCLAIR_TLS_STORE` prefix matters: `pingclair trust` looks in the store of the user who runs it, which for root is `/root/.local/share/pingclair`, while the service uses `/var/lib/pingclair/.local/share/pingclair`. Without the prefix it answers `No internal CA root at /root/.local/share/pingclair/internal/root.crt`. After trusting the root, the same request succeeds without `-k`: ```bash curl -s -o /dev/null -w '%{http_code}\n' https://internal.test/ ``` ```text 200 ``` `pingclair untrust` removes it again, with the same store prefix. ## ๐Ÿ“œ Certificates you supply When another system issues your certificates, point `tls` at the files: ```caddyfile https://byo.test { tls { cert /etc/pingclair/certs/byo.crt key /etc/pingclair/certs/byo.key } file_server /var/lib/pingclair/html } ``` The files must be readable by the `pingclair` user, because the service runs as that user. `validate` refuses a path that does not exist rather than failing at the first handshake: ```text โŒ TLS certificate file does not exist: /etc/pingclair/certs/missing.crt ``` ## โš ๏ธ When HTTPS does not come up - **`contact email has forbidden domain "example.com"`.** Let's Encrypt rejects the reserved example domains as account contacts. Put a real mailbox in the `email` option. - **`NO_CERTIFICATE_SET` in the log.** The handshake presented a name the server has no certificate for. Read the log above it: a `tls` block without `auto` never starts issuance, and DNS-01 does not complete in this release. - **The challenge is never served.** Port 80 is blocked by a firewall, or something else holds the port. The authority has to reach `http://your-name/.well-known/acme-challenge/` from the internet. - **The name does not resolve to this host.** `dig +short A your-name` shows what the authority will connect to, which is not always what you expect after a recent change. - **Repeated failures.** Let's Encrypt rate-limits failed validations per hostname. Fix the cause before retrying, or the retries themselves become the error. ## ๐Ÿงญ Next steps - [Run it as a service](/start/service/): the unit, its reload semantics, and its logs. - [`tls`](/reference/directives/#tls): every mode and option of the directive. - [Pingclairfile](/reference/pingclairfile/): addresses, matchers, and what the compiler accepts.