TLS certificates¶
Goal: nobody sees a certificate error, and nobody deploys a CA by GPO. Everything Tenantry installs can get a publicly trusted certificate, internal-only servers included.
You may not need this page
If Tenantry is behind Cloudflare Tunnel or behind your own reverse proxy, that layer serves the certificate users see. TLS options of install.sh (--tls) apply only to --expose local, where Tenantry serves HTTPS itself.
Which method?¶
| Your situation | Method | Installer option | Page |
|---|---|---|---|
| The server is reachable from the Internet on port 80 | HTTP-01 | --tls acme-http |
below |
| Not reachable (LAN, behind NAT, firewall) or you are not sure | DNS-01 through your DNS provider's API | --tls acme-dns --dns-provider dns_xxx |
one page per provider (below) |
| You already have a certificate (PEM chain and key) | Your own files | --tls files --tls-cert FILE --tls-key FILE |
Own certificates |
| You run your own ACME server (step-ca, internal PKI) | Your own ACME server | --acme-server https://.../directory |
Own certificates |
| Lab, nobody should trust it | Self-signed | --tls selfsigned |
Own certificates |
Not sure? Choose DNS-01. It works everywhere, needs no inbound port, and the name may resolve on your internal network only.
DNS-01 providers¶
DNS-01 proves that you own a name by writing a TXT record _acme-challenge.<name> in its public DNS zone through the API of the company that hosts the zone. The name must live under a public domain you own (tenantry.acme.ch, not tenantry.acme.local).
Pick the page of your DNS host. Each page lists the exact variables, the least-privilege token and the install.sh options.
| DNS host | --dns-provider |
Page |
|---|---|---|
| Infomaniak | dns_infomaniak |
Infomaniak (contains a worked example) |
| OVHcloud | dns_ovh |
OVHcloud |
| Cloudflare | dns_cf |
Cloudflare |
| Gandi LiveDNS | dns_gandi_livedns |
Gandi |
| Hetzner DNS | dns_hetzner |
Hetzner |
| AWS Route 53 | dns_aws |
Route 53 |
| Azure DNS | dns_azure |
Azure DNS |
| deSEC | dns_desec |
deSEC |
| DigitalOcean | dns_dgon |
DigitalOcean |
| Linode / Akamai | dns_linode_v4 |
Linode |
| PowerDNS (API) | dns_pdns |
PowerDNS |
| Anything else supported by acme.sh | dns_xxx + --dns-secret-var |
Other acme.sh provider |
scripts/install.sh --print-dns-providers prints the list with the secret variables and settings of each provider.
HTTP-01¶
The acme.sh container answers the challenge on port 80. Port 80 of the server must be reachable from the Internet and the name must resolve publicly to this server.
sudo scripts/install.sh --name acme --public-url https://tenantry.acme.ch \
--teleport-local auto --tls acme-http --acme-email ops@acme.ch
With acme-http, port 80 belongs to the acme container, so there is no HTTP to HTTPS redirect. If Teleport already uses port 443 on the same server, add --https-port 9443 and use https://tenantry.acme.ch:9443 as the public URL.
How it works¶
The installer adds two containers next to Tenantry (file <dir>/compose.tls.yml):
acme: the officialneilpang/acme.shimage, pinned by tag and digest, never upgrades itself,cap_drop: ALLplus the three capabilities it needs (CHOWN,FOWNER,DAC_OVERRIDE;NET_BIND_SERVICEfor HTTP-01 only). The Docker socket is never mounted. It issues an ECDSA P-256 certificate and renews it withacme.sh --cron(four times a day, renewal 30 days before expiry).caddy: a pinned Caddy that serveshttps://<name>:<https-port>with static certificate files (Caddy's own ACME is off). A watcher checks the certificate hash every 5 minutes and reloads Caddy after a renewal. Caddy reaches Tenantry on a dedicatededgeDocker network, at a fixed address that is the only one Tenantry trusts forX-Forwarded-For(TRUST_PROXY).
Tenantry itself stays on 127.0.0.1. Certificates are published atomically (copy, then rename) in <dir>/certs/.
The same acme.sh sidecar is used by the Steward bundles (certificate read by the console, hot reload) and by the Teleport master bundle (https_keypairs with https_keypairs_reload_interval: 1h, so no restart is needed).
Secrets: never in a bundle or on a command line¶
- The DNS API secrets are typed (hidden) at the prompt of
install.sh, or read from--dns-credentials-file FILE(oneVARIABLE=valueper line). They are stored only in<dir>/secrets/acme-dns.env(mode 0600). That file is mounted read-only into theacmecontainer (/run/secrets/acme-dns.env) and sourced by its process: the secrets are never in the container's configured environment, sodocker inspectdoes not show them. - The EAB HMAC key never appears on a command line (where any user of the host could read it with
ps): the issue script writes it, with the shell'sprintfbuiltin, into the CA'sca.confin theacme-statevolume, where acme.sh reads it for the account registration. - Non-secret settings (for example
OVH_END_POINT,CF_Zone_ID) go in<dir>/acme/provider.envand are passed with--dns-setting VAR=value. The installer refuses a secret variable passed as a setting. - acme.sh keeps a copy of the credentials in its
acme-statevolume for unattended renewals: treat host backups as sensitive. - To replace a token: delete
<dir>/secrets/acme-dns.envand runinstall.shagain. - Least privilege: a token limited to the zone, dedicated to one host, with an expiry.
Challenge alias¶
If your DNS host cannot restrict a token to one zone, delegate the challenge: create a CNAME _acme-challenge.<name> pointing to _acme-challenge.<alias zone>, and pass --challenge-alias <alias zone>. The token then only needs rights on the alias zone.
Certificate authorities¶
--acme-server accepts letsencrypt (default), letsencrypt_test (staging, untrusted: use it to test and avoid rate limits), zerossl (needs --eab-kid; the HMAC key is typed or comes from ACME_EAB_HMAC_KEY in the credentials file), or an ACME directory URL. --acme-email gives the CA a contact address for expiry notices.
Monitoring¶
Tenantry reads the certificate presented by each Steward console (during the status poll) and by the Teleport proxy (one TLS handshake per round). The alert certificate_expiring fires under 14 days (critical when expired or under 3 days). See Alerts.
Troubleshooting¶
- Logs:
docker compose -p tenantry-<name> logs acme. - Force a renewal:
docker compose -p tenantry-<name> exec acme acme.sh --renew -d <name> --ecc --force. - Authentication error at issuance: wrong or under-privileged token. Delete
<dir>/secrets/acme-dns.envand runinstall.shagain. --tls-domain ...: a public CA cannot issue for this name: the name ends with.local,.lan,.internaland so on. Use a name under your public domain, or your own ACME server.- Test with
--acme-server letsencrypt_testfirst to avoid rate limits.