CertAuto
Automatic HTTPS/TLS certificate management for Rust, powered by the ACME protocol.
CertAuto brings production-grade automatic certificate management to Rust programs: obtain, renew, and serve TLS certificates from any ACME-compatible Certificate Authority, with just a few lines of code.
use Config;
async
Table of Contents
- Features
- Requirements
- Installation
- Quick Start
- Usage Examples
- Architecture Overview
- The ACME Challenges
- Storage
- Certificate Maintenance
- On-Demand TLS (Detailed)
- API Reference
- License
Features
- Fully automatic certificate management -- obtain, renew, and cache TLS certificates without manual intervention
- All three ACME challenge types -- HTTP-01, TLS-ALPN-01, and DNS-01
- Multiple CA support -- Let's Encrypt (production and staging), ZeroSSL, Google Trust Services, or any ACME-compliant CA
- OCSP stapling -- automatic OCSP response fetching and stapling for improved privacy and performance; staples are persisted to storage across restarts
- Wildcard certificates -- via the DNS-01 challenge with a pluggable
DnsProvidertrait - On-demand TLS -- obtain certificates at handshake time for previously unknown domains, with configurable allowlists, decision functions, and rate limiting
- Certificate caching -- in-memory
CertCachewith domain name indexing and wildcard matching for fast TLS handshake lookups - Configurable key types -- ECDSA P-256 (default), ECDSA P-384, RSA 2048, RSA 4096, and Ed25519
- Background maintenance -- automatic renewal checks (every 10 minutes) and OCSP staple refresh (every hour)
- Built-in rate limiting -- prevents overwhelming CAs with too many requests
- Retry with exponential backoff -- failed certificate operations are retried with increasing delays (up to 30 days)
- Distributed challenge solving --
DistributedSolvercoordinates challenges across multiple instances via sharedStorage, enabling clustered deployments behind load balancers - File system storage with atomic writes -- default
FileStorageuses write-to-temp-then-rename for crash safety; distributed lock files with background keepalive for cluster coordination - Custom storage backends -- implement the
Storagetrait to use databases, KV stores, or any other persistence layer - Event callbacks -- observe certificate lifecycle events (
cert_obtaining,cert_obtained,cert_renewed,cert_failed,cert_revoked, etc.) - Builder pattern -- ergonomic
Config::builder(),AcmeIssuer::builder(), andZeroSslIssuer::builder()for easy configuration - External Account Binding (EAB) -- first-class support for CAs that require EAB (e.g., ZeroSSL)
- Certificate chain preference -- select preferred chains by root/issuer Common Name or chain size
- Certificate revocation -- revoke compromised certificates via the ACME protocol
- Native rustls integration --
CertResolverimplementsrustls::server::ResolvesServerCertand plugs directly into any rustls-based server
Requirements
- Rust 2021 edition with a Tokio async runtime
- Public DNS name(s) you control, pointed (A/AAAA records) at your server
- Port 80 accessible from the public internet (for HTTP-01 challenge), and/or port 443 (for TLS-ALPN-01 challenge)
- These can be forwarded to other ports you control
- Or use the DNS-01 challenge to waive both requirements entirely
- This is a requirement of the ACME protocol, not a library limitation
- Persistent storage for certificates, keys, and metadata
- Default: local file system (
~/.local/share/certautoon Linux,~/Library/Application Support/certautoon macOS,%APPDATA%/certautoon Windows) - Custom backends available via the
Storagetrait
- Default: local file system (
Before using this library, your domain names MUST be pointed (A/AAAA records) at your server (unless you use the DNS-01 challenge).
Installation
Add certauto to your Cargo.toml:
[]
= "0.1"
= { = "1", = ["full"] }
Quick Start
The simplest way to get started -- one function call manages everything:
use Config;
async
This will:
- Create a
FileStoragein the default OS-specific directory. - Obtain certificates from Let's Encrypt (production) for the given domains.
- Return a
rustls::ServerConfigwired up with aCertResolverthat serves the managed certificates.
Usage Examples
Basic -- Manage Certificates with Defaults
use Arc;
use ;
async
Custom CA and Email
use Arc;
use ;
let storage: = new;
let issuer = builder
.ca // Use staging while developing!
.email
.agreed
.storage
.build;
let config = builder
.storage
.issuers
.build;
DNS-01 Challenge (Wildcard Certificates)
The DNS-01 challenge is required for wildcard certificates and works even when your server is not publicly accessible.
use Arc;
use ;
// Implement DnsProvider for your DNS service (Cloudflare, Route53, etc.)
let dns_solver = new;
let issuer = builder
.dns01_solver
.email
.agreed
.storage
.build;
// Now you can obtain wildcard certificates:
let domains = vec!;
To implement a DNS provider, implement the DnsProvider trait:
use async_trait;
use ;
ZeroSSL
ZeroSSL provides free certificates via ACME with External Account Binding. CertAuto handles EAB provisioning automatically using your ZeroSSL API key.
use Arc;
use ;
let storage: = new;
let issuer = builder
.api_key
.email
.storage
.build
.await?;
let config = builder
.storage
.issuers
.build;
Custom Storage Backend
Implement the Storage trait to use databases, Redis, S3, or any other persistence layer. All instances sharing the same storage are considered part of the same cluster.
use async_trait;
use ;
use Result;
On-Demand TLS
On-demand TLS obtains certificates at TLS handshake time for domains that have not been pre-configured. Always gate this with an allowlist or decision function to prevent abuse.
use HashSet;
use Arc;
use OnDemandConfig;
let on_demand = new;
let config = builder
.storage
.on_demand
.build;
Event Callbacks
Subscribe to certificate lifecycle events for logging, monitoring, or alerting:
use Arc;
let config = builder
.storage
.on_event
.build;
Events emitted include:
cert_obtaining-- a certificate obtain operation is startingcert_obtained-- a certificate was successfully obtainedcert_renewed-- a certificate was successfully renewedcert_failed-- a certificate obtain or renewal operation failedcert_revoked-- a certificate was revokedcached_managed_cert-- a managed certificate was loaded from storage into cache
Architecture Overview
+-----------+
| Config | Central coordinator
+-----+-----+
|
+---------------+---------------+
| | |
+-----v-----+ +----v----+ +------v------+
| Issuer | | Cache | | Storage |
+-----------+ +---------+ +-------------+
| | |
+-----v-----+ +----v--------+ +---v-----------+
| AcmeIssuer| | CertResolver| | FileStorage |
| ZeroSSL | | (rustls) | | (or custom) |
+-----------+ +-------------+ +---------------+
|
+-----v-------+
| AcmeClient |----> ACME CA (Let's Encrypt, ZeroSSL, etc.)
+--------------+
+------------------+
| start_maintenance| ---> Renewal loop (every 10 min)
| | ---> OCSP refresh loop (every 1 hr)
+------------------+
Key components:
| Component | Role |
|---|---|
Config |
Central entry point; coordinates obtain, renew, revoke, and cache operations |
AcmeIssuer / ZeroSslIssuer |
Implement the Issuer trait; drive the ACME protocol flow |
AcmeClient |
Low-level ACME HTTP client (directory, nonce, JWS signing, order management) |
CertCache |
In-memory certificate store indexed by domain name (with wildcard matching) |
CertResolver |
Implements rustls::server::ResolvesServerCert; resolves certificates during TLS handshakes |
Storage / FileStorage |
Persistent key-value storage with distributed locking |
start_maintenance |
Background tokio task for automatic renewal and OCSP refresh |
The ACME Challenges
The ACME protocol verifies domain ownership through challenges. CertAuto supports all three standard challenge types.
HTTP-01 Challenge
The HTTP-01 challenge proves control of a domain by serving a specific token at http://<domain>/.well-known/acme-challenge/<token> on port 80.
CertAuto's Http01Solver starts a lightweight HTTP server that automatically serves the challenge response. The server is started when a challenge is presented and stopped when the challenge completes.
use Http01Solver;
let solver = new; // or Http01Solver::default()
Requirements: Port 80 must be accessible from the public internet (directly or via port forwarding).
TLS-ALPN-01 Challenge
The TLS-ALPN-01 challenge proves control of a domain by presenting a self-signed certificate with a special acmeIdentifier extension during a TLS handshake on port 443, negotiated via the acme-tls/1 ALPN protocol.
CertAuto's TlsAlpn01Solver handles this by generating an ephemeral challenge certificate and serving it on a temporary TLS listener.
use TlsAlpn01Solver;
let solver = new; // or TlsAlpn01Solver::default()
Requirements: Port 443 must be accessible from the public internet. This is often the most convenient challenge type because it uses the same port as your production TLS server.
DNS-01 Challenge
The DNS-01 challenge proves control of a domain by creating a specific TXT record at _acme-challenge.<domain>. This is the only challenge type that supports wildcard certificates and does not require your server to be publicly accessible.
CertAuto's Dns01Solver accepts a DnsProvider implementation that creates and deletes TXT records via your DNS provider's API. It automatically waits for DNS propagation before notifying the CA.
use Dns01Solver;
let solver = new;
// With custom propagation settings:
let solver = with_timeouts;
Requirements: A DNS provider with an API, and an implementation of the DnsProvider trait.
Storage
CertAuto requires persistent storage for certificates, private keys, metadata, OCSP staples, and lock files. Storage is abstracted behind the Storage trait, making it easy to swap backends.
Default: FileStorage
The built-in FileStorage stores everything on the local file system with these properties:
- Atomic writes -- data is written to a temporary file, then atomically renamed into place, preventing partial reads
- Distributed locking -- lock files contain a JSON timestamp refreshed by a background keepalive task every 5 seconds; stale locks (older than 10 seconds) are automatically broken
- Platform-aware paths -- defaults to
~/.local/share/certauto(Linux),~/Library/Application Support/certauto(macOS), or%APPDATA%/certauto(Windows)
Clustering: Any instances sharing the same storage backend are considered part of the same cluster. For FileStorage, mounting a shared network folder is sufficient. For custom backends, ensure that all instances point to the same database/service.
Storage layout:
<root>/
certificates/<issuer>/<domain>/
<domain>.crt -- PEM certificate chain
<domain>.key -- PEM private key
<domain>.json -- metadata (SANs, issuer info)
ocsp/
<domain>-<hash> -- cached OCSP responses
acme/<issuer>/
users/<email>/ -- ACME account data
locks/
<name>.lock -- distributed lock files
Certificate Maintenance
CertAuto runs background maintenance via certauto::start_maintenance(), which spawns a tokio task performing two periodic loops:
-
Renewal loop (every 10 minutes by default) -- iterates all managed certificates in the cache and renews any that have entered the renewal window (by default, when less than 1/3 of the certificate lifetime remains)
-
OCSP refresh loop (every 1 hour by default) -- fetches fresh OCSP responses for all cached certificates and persists them to storage
Both loops respect the CertCache::stop() signal for graceful shutdown.
let config = builder.storage.build;
// Start background maintenance.
let handle = start_maintenance;
// ... later, to stop gracefully:
// config.cache.stop();
// handle.await;
On-Demand TLS (Detailed)
On-demand TLS obtains certificates during TLS handshakes for domains that were not pre-configured. When a ClientHello arrives with an unknown SNI value, the CertResolver can trigger background certificate acquisition so that subsequent handshakes for the same domain succeed.
This is powerful but must be gated carefully to prevent abuse:
| Gate | Description |
|---|---|
host_allowlist |
A HashSet<String> of permitted hostnames (case-insensitive) |
decision_func |
A closure Fn(&str) -> bool for dynamic allow/deny logic |
rate_limit |
An optional RateLimiter to throttle issuance |
If neither decision_func nor host_allowlist is configured, on-demand issuance is denied (fail-closed) to prevent unbounded certificate requests.
Because rustls::server::ResolvesServerCert::resolve is synchronous, on-demand acquisition is spawned in the background. The current handshake receives the default certificate (or None); the next handshake for the same domain will find the certificate in cache.
API Reference
Full API documentation is available on docs.rs.
Key entry points:
certauto::manage()-- highest-level function, returns a ready-to-userustls::ServerConfigConfig::builder()-- configure and build aConfigAcmeIssuer::builder()-- configure an ACME issuerStoragetrait -- implement custom storage backendsSolvertrait -- implement custom challenge solversDnsProvidertrait -- implement DNS providers for DNS-01 challenges
Development and Testing
Let's Encrypt imposes strict rate limits on its production endpoint. During development, always use the staging endpoint:
use LETS_ENCRYPT_STAGING;
let issuer = builder
.ca
.email
.agreed
.storage
.build;
Staging certificates are not publicly trusted, but the rate limits are much more generous.
License
CertAuto is dual-licensed under the MIT License and the Apache License 2.0. You may choose either license at your option.