LetsEncryptTls class

A TlsProvider that provisions and renews certificates automatically via Let's Encrypt (ACME HTTP-01), backed by package:shelf_letsencrypt.

OmnyHub owns the server lifecycle, so this adapter does not bind its own listeners (unlike LetsEncrypt.startServer). Instead it exposes:

  • challengeMiddleware — the hub mounts this first on its plaintext (port
    1. transport so the ACME CA's HTTP-01 validation requests are answered;
  • provision — requests certificates for any seed domains that lack a valid one, then builds their SecurityContexts;
  • contextFor — the SNI resolver: returns the certificate for a requested host, and (in on-demand mode) kicks off provisioning for allowed hosts it does not yet have;
  • maybeRenew — renews certificates near expiry.

Dynamic / on-demand domains

Passing an allowDomain policy (plus an onDemandEmail) enables on-demand issuance: any host the policy allows gets a certificate provisioned the first time it is seen, without the developer listing it in code. Because certificates are served via SNI from a live cache, foo.example.com and bar.example.com "just work" as they start being used:

final tls = LetsEncryptTls(
  onDemandEmail: 'ops@example.com',
  allowDomain: (host) => host.endsWith('.example.com'),
  cacheDir: '/var/lib/omnyhub/certs',
  production: true,
);
final hub = OmnyHub(transports: [
  HttpTransport.http(port: 80),                 // ACME HTTP-01 challenges
  HttpTransport.https(port: 443, tls: tls),     // SNI, on-demand certs
]);

The first TLS handshake for an allowed, not-yet-provisioned host triggers background issuance (validated via the challenge on port 80); the certificate is served on the next connection. You can also pre-provision a host at any time with obtain (e.g. at tenant sign-up).

Ports: ACME HTTP-01 requires a reachable port 80; add an HttpTransport.http(port: 80). Renewal self-checks use securePort.

Staging vs production: production defaults to false (Let's Encrypt staging, whose certificates are browser-invalid but avoid the strict production rate limits). Set it to true only once issuance works.

Implemented types

Constructors

LetsEncryptTls({List<Domain> domains = const [], DomainPolicy? allowDomain, String? onDemandEmail, EmailResolver? onDemandEmailResolver, required String cacheDir, bool production = false, bool autoIssue = true, Duration renewBefore = const Duration(days: 5), int challengePort = 80, int securePort = 443})
Creates an ACME provider.
factory
LetsEncryptTls.forDomain(String domain, String email, {required String cacheDir, bool production = false, bool autoIssue = true, Duration renewBefore = const Duration(days: 5), int challengePort = 80, int securePort = 443})
Convenience constructor for a single fixed domain/email.
factory
LetsEncryptTls.onDemand({required DomainPolicy allowDomain, required String cacheDir, String? email, EmailResolver? emailResolver, List<Domain> domains = const [], bool production = false, bool autoIssue = true, Duration renewBefore = const Duration(days: 5), int challengePort = 80, int securePort = 443})
Convenience constructor for on-demand issuance of any host matching allowDomain.
factory

Properties

allowDomain → DomainPolicy?
Policy allowing on-demand certificates for hosts it returns true for. null disables on-demand issuance (only domains are served).
final
autoIssue bool
Whether a missing certificate may be requested from the CA.
final
cacheDir String
The directory where account keys and certificates are cached.
final
challengeMiddleware Middleware
A middleware that answers ACME HTTP-01 challenges, mounted first in the pipeline of the plaintext transport. null for providers that need no challenge handling (e.g. StaticTls).
no setteroverride
defaultContext SecurityContext?
The fallback context served when contextFor returns null (e.g. a client that sent no SNI). null means connections for unknown hosts are dropped until a certificate exists.
no setter
domains List<Domain>
Seed domains provisioned up front on provision.
final
effectiveRenewBefore Duration
The renewal threshold as the underlying ACME client sees it — renewBefore is only a safety margin if it actually reaches shelf_letsencrypt.
no setter
hashCode int
The hash code for this object.
no setterinherited
hotReloadable bool
Whether the securityContext may change at runtime (ACME renewal), in which case the hub rebinds the HTTPS transport after maybeRenew reports a refresh.
no setteroverride
isOnDemand bool
Whether on-demand issuance is enabled.
no setter
onDemandEmail String?
The fixed contact email used for on-demand certificates. null when an onDemandEmailResolver is supplied instead.
final
onDemandEmailResolver → EmailResolver?
Resolves the contact email per host for on-demand certificates, allowing a different email for each domain. Takes precedence over onDemandEmail.
final
production bool
Whether to use the production ACME endpoint (false = staging).
final
renewBefore Duration
How much validity a certificate must have left to be kept; below this it is renewed by maybeRenew. Defaults to 5 days.
final
runtimeType Type
A representation of the runtime type of the object.
no setterinherited
securePort int
The public HTTPS port, used by renewal self-checks.
final
supportsSni bool
Whether the transport should use the SNI binding path for this provider.
no setter

Methods

contextFor(String? host) SecurityContext?
The certificate context for host (the SNI hostname), or null if none is available yet. Called synchronously during the TLS handshake, so it must not block; dynamic providers may kick off background provisioning and return null (or a default) for now.
isAllowed(String host) Future<bool>
Whether host may be served (a seed domain or allowed by the policy).
maybeRenew() Future<bool>
Renews certificates if they are near expiry. Returns true if the securityContext changed and the listener should be rebound. A no-op (returning false) for static providers.
override
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
obtain(String host) Future<bool>
Ensures a certificate exists for host (provisioning it if allowed and missing) and caches its SecurityContext. Returns whether a certificate is available afterwards. Concurrent calls for the same host are de-duplicated.
provision() Future<void>
Ensures certificates exist, provisioning them if necessary. A no-op for static providers.
override
securityContext() SecurityContext
The current SecurityContext to bind the listener with.
override
toString() String
A string representation of this object.
inherited

Operators

operator ==(Object other) bool
The equality operator.
inherited