Skip to content

Releases: astralmaster/AutoHttps

AutoHttps 1.8.0

Choose a tag to compare

@astralmaster astralmaster released this 24 Oct 12:36

A release of fixes, from a review of 1.7.0 against the RFCs, the certificate authorities' own source and the cloud APIs. Every package changed. Read Upgrading before deploying: two of the changes need something from you.

Renewal

  • A certificate issued to a previous ACME account is renewed again. The account is keyed by the email address and the certificate by its domains, so changing EmailAddress, or losing the account key, left a certificate that a new account had to renew. Let's Encrypt and Pebble refuse a renewal hint (replaces) that names another account's certificate with 403 unauthorized, and only alreadyReplaced was handled, so renewal failed on every attempt, past expiry, until a restart. The order is now placed again without the hint, and event 148 says so.
  • Changing DomainNames keeps the names that stayed online. A changed list found nothing stored and served the self-signed fallback for every name until a certificate for the new set was issued, which never happened while a new name could not validate. The previous certificate is now served for the names it covers while the new set is ordered, the health check reports Degraded meanwhile, and event 149 says what happened. AutoHttps keeps a copy of the latest certificate from each authority for this: the latest- files in the storage directory.
  • Changing KeyAlgorithm replaces the certificate straight away instead of at its next renewal (event 151).
  • The stored ACME account is looked up instead of registered again on every start, so the external account binding is not sent again. Google Trust Services binding keys are single use and expire after seven days, so a restart no longer depends on the authority ignoring a spent key.
  • A finalize whose response was lost is no longer sent a second time. That used to report a certificate being issued as a failure and order a duplicate.
  • A lock that cannot be taken counts as a failed attempt. A storage directory that cannot be written now reaches the failure count, the overdue escalation (event 147), the listeners and the diagnostics.
  • Picking up a certificate another replica published clears this replica's failure count and backoff.
  • An issued certificate is saved even when shutdown starts during the write.
  • The stored certificate is loaded before Kestrel accepts connections, so the first handshakes after a restart no longer get the fallback.

Platforms and storage

  • macOS loads certificates. The library used EphemeralKeySet everywhere except Windows, the macOS loader refuses it, and so nothing loaded there while each retry ordered and lost a certificate. CI now includes macOS.
  • On Linux and macOS the storage directory is created owner-only whichever component creates it first, and key files are created at 0600 rather than changed to it after the write.
  • On Windows, a store write blocked by another open handle is retried, and if the certificate still cannot be written the previous key is put back, so the stored pair always matches.

Cloud packages

  • Route 53 and Azure DNS add their value to the TXT record set and later remove only that value, instead of replacing the set and deleting all of it. Another client ordering for the same name no longer loses its record. Azure writes are conditional on the record set's etag.
  • Route 53 waits for each change to reach INSYNC, for up to two minutes, before validation is requested.
  • Cloudflare and Route 53 choose the most specific zone, so a delegated subzone is used even after its parent was cached.
  • UseRedis also registers the new RedisDistributedLock, so replicas sharing Redis no longer all order at the same moment, and the application starts while Redis is down instead of failing as the host starts.
  • Key Vault recovers a soft-deleted secret before writing it.
  • Route 53 and Azure refuse partial credentials at startup instead of falling back to the default credential chain. On Azure, ClientId on its own now selects that user-assigned managed identity.

Diagnostics and messages

  • A DNS-over-HTTPS answer that is not JSON counts as no answer, instead of failing the order or the diagnostics report.
  • The DNS check no longer demands an address for a name validated by dns-01, and passes a name that has only an AAAA record.
  • The CAA check reads the RFC 8657 validationmethods and accounturi parameters, which Let's Encrypt enforces and every public authority must from 2027-03-15, reports a CAA lookup that fails, and warns about an unknown property marked critical.
  • The authority's explanationURL for a renewal window is logged (event 155), and error messages keep a problem's subproblems and instance, so a failure across several names names all of them.
  • P-384 signing requests are signed with SHA-384, which AWS Certificate Manager requires.

Upgrading

  • Route 53 now needs the route53:GetChange permission, because the provider waits for each change to apply. Without it every dns-01 order fails, so add it to the IAM policy before upgrading.
  • Partly configured cloud credentials stop the application at startup, with an error naming the options: only one of AccessKeyId and SecretAccessKey for Route 53, or an incomplete Azure service principal. Before, the application signed in through the default chain as whatever identity that found.
  • UseRedis registers a lock. If you supplied your own with UseDistributedLock<T>(), register it after UseRedis, or the Redis lock replaces it.
  • The interval, delay and timeout options are limited to 30 days, and a longer value fails options validation at startup. Before, a wait of more than about 49.7 days made the timer throw later, inside the renewal loop.
  • When RequireCertificateOnStartup stops the application, the process now exits with code 1, so restart policies such as systemd's Restart=on-failure restart it.
  • Removing a domain keeps the current certificate until its ordinary renewal, since it still covers the remaining names. Adding one orders straight away, as before.
  • A deactivated ACME account is reported rather than replaced. Every attempt fails with the authority's reason (event 110). Remove the stored account key to register a new account.

Packages

  • AutoHttps 1.8.0
  • AutoHttps.Redis 1.2.0
  • AutoHttps.Route53 1.1.0
  • AutoHttps.Azure 1.1.0
  • AutoHttps.Cloudflare 1.0.1

The cloud packages are tested against fakes of Route 53, Azure DNS, Key Vault and Cloudflare that follow their documented behaviour, not against the live services.

AutoHttps 1.7.0

Choose a tag to compare

@astralmaster astralmaster released this 03 Oct 15:23

Two additions: renewal that is failing now escalates on its own, and HTTP/3 is verified rather than assumed. Only the core AutoHttps package changed; the Cloudflare, Route 53, Redis and Azure packages are unchanged.

Renewal that is not going to finish in time

A failed order is retried and usually succeeds, so event 110, which fires on every attempt, is the wrong thing to page on. The case that needed a signal of its own is renewal failing for long enough that the certificate will expire. Let's Encrypt stopped sending expiry notifications on 2025-06-04, so nothing outside the process notices that on your behalf.

Near expiry is now a share of the certificate's lifetime rather than a fixed span, which is what lets it be on by default. NearExpiryWarningFraction defaults to half of RenewalThreshold, so a certificate renewing on schedule never reaches the window, and reaching it means renewal has been failing for as long as the threshold left to spare. One span cannot serve both lifetimes the library has to handle: a sixth of a 90 day certificate is 15 days, and a sixth of a 160 hour one is 27 hours. Set it to 0 to turn the warning and the health degradation off.

A run of failures that reaches the window logs event 147 at Error, once per run rather than once per attempt, and the health check turns Degraded and says which of the two situations it is, carrying the failure count and the last reason in its data. Anything that puts a certificate back in hand clears all of it, including a certificate another replica published.

MaxRetryDelay is also held to a tenth of the certificate's lifetime while one is in hand, so a ceiling chosen for a 90 day certificate does not leave a six day one with two attempts between the renewal point and expiry. At the defaults nothing changes.

The new gauge autohttps.certificate.renewal_failures reports how many attempts have failed in a row and returns to zero as soon as one succeeds, which the autohttps.certificate.renewals counter cannot do. A threshold on it means renewal is failing now rather than that it failed at some point.

IAutoHttpsCertificateInspector also reports ConsecutiveFailures, LastFailureAt and LastFailureReason, so a status page can show a failing renewal before the certificate gets close to expiring.

HTTP/3

HTTP/3 works with both wiring styles and needs nothing configured in AutoHttps. QUIC terminates TLS in the transport rather than in the HTTPS middleware, so Kestrel builds a second set of TLS options for it and refuses some HTTPS settings there, OnAuthenticate among them. AutoHttps uses a certificate selector and a handshake callback, neither of which Kestrel refuses on that path.

This release makes that a tested claim rather than an assumption: a real HTTP/3 request against a real certificate authority, with the chain built to that authority's own issuing root. Two requirements belong to the host rather than to this library, and both fail quietly enough to be worth stating:

  • On Linux, install libmsquic. It does not ship with .NET and is not in the distribution's own archive, only in Microsoft's. Without it an endpoint that asks for HTTP/3 binds TCP only and logs nothing about it.
  • With WebApplication.CreateSlimBuilder, call UseQuic() and UseKestrelHttpsConfiguration(). The slim builder leaves out both. Without the first the endpoint silently binds TCP only; without the second the bind fails outright. CreateBuilder includes both.

The chain caveat carries over from TCP unchanged: UseAutoHttps(listenOptions, ...) sends the issued intermediates over HTTP/3, and the automatic HTTPS-defaults wiring sends the leaf alone, because Kestrel drops a configured chain whenever a certificate selector is in use.

Upgrading

The health check now reports Degraded once the certificate is near expiry. That report existed before but was off unless you passed nearExpiryWarning. It is on now because the threshold is proportional, and a certificate that renews on time never reaches it.

If a readiness probe treats Degraded as unhealthy, and a certificate really is close to expiry because renewal is failing, that instance will now be taken out of rotation. That is the intent, and it is the situation the release is about. To keep the previous behaviour, set NearExpiryWarningFraction = 0.

Everything else is additive.

Packages

  • AutoHttps 1.7.0

AutoHttps 1.6.0

Choose a tag to compare

@astralmaster astralmaster released this 29 Sep 16:50

Two additions, both about deployments that were hard to get right rather than about the ACME protocol. The AutoHttps and AutoHttps.Redis packages changed; the Cloudflare, Route 53 and Azure packages are unchanged.

Answering http-01 from any replica

The instance that wins the certificate lock is not necessarily the one the authority's validation request reaches. Until now the answer to a challenge lived only in the process that ordered, so a request landing on any other replica got a 404 and that authorization failed. Measured with three replicas and the per-process store: eight failed validations to issue one certificate, repeated at every renewal, against a Let's Encrypt limit of five failed validations per account, per hostname, per hour.

The answer now lives behind IHttp01ChallengeStore. The default is still per-process, so a single instance behaves exactly as before. UseRedis registers a Redis-backed store alongside the certificate and account key stores, and UseHttp01ChallengeStore<T>() registers your own if you want to share only the challenges and keep certificates where they already are.

builder.Services
    .AddAutoHttps(options => { /* ... */ })
    .UseRedis("localhost:6379");

A pending answer is written to autohttps:challenge:<token> and deleted when the order finishes. RedisStoreOptions.ChallengeTtl, 15 minutes by default, bounds how long one survives if an order is interrupted before it can clean up.

The failure message moved with it. A 404 on the challenge path no longer blames something in front of the application when the store is shared, because with a shared store that diagnosis is wrong.

Diagnosing a failure

An authority reports what it saw from outside, which is rarely enough to act on. After the first failed order AutoHttps now checks its own configuration and surroundings once and writes the result to the log as event 144, or 145 when it finds nothing wrong. Nothing runs while orders succeed, and DiagnoseOrderFailures = false turns it off.

The same report is available on demand, which is the output to paste into an issue:

var diagnostics = app.Services.GetRequiredService<IAutoHttpsDiagnostics>();
AutoHttpsDiagnosticsReport report = await diagnostics.RunAsync();

Console.WriteLine(report);

Ten checks, every one read only: name count and IP address rules against the requested profile, whether the certificate can actually be written, whether anything is bound to port 80, whether the directory is reachable and offers renewal information, this host's clock against the authority's, external account binding required versus configured, the requested profile against the advertised ones, A and AAAA resolution including SERVFAIL as a broken DNSSEC signal, CAA on the closest ancestor that has a record set, and a token of its own published and fetched back over the public name.

The DNS and CAA checks need DnsPropagationResolver and are skipped without it, because AutoHttps makes no DNS queries unless you configure a resolver.

One thing no client can check for you: since 2025 an authority validates from several network perspectives at least 500km apart and does not publish where from, so a passing challenge-path check proves the path works from this host, not that the authority can reach it.

Upgrading

UseRedis now replaces the http-01 challenge store as well as the certificate and account key stores. That is what a multi-instance deployment wants, and it is why the package exists, but it is a behaviour change: if you had a reason to keep challenge answers per process, register your own store after UseRedis. Everything else is additive.

Packages

  • AutoHttps 1.6.0
  • AutoHttps.Redis 1.1.0, which requires AutoHttps 1.6.0

AutoHttps 1.5.0

Choose a tag to compare

@astralmaster astralmaster released this 21 Sep 18:10

Follow-up fixes to the 1.4.0 audit round, plus trim and native-AOT support for the core package. Only the core AutoHttps package changed; the Cloudflare, Route 53, Redis and Azure packages are unchanged.

Fixes

  • Report the wiring mistake where an HTTPS endpoint declared before AddAutoHttps keeps the HTTPS defaults from before AutoHttps was added and goes on serving its own certificate for a managed name. AutoHttps now logs a warning once the host has started, naming the cause and the remedies, instead of leaving the wrong certificate served without a word (in Development, the ASP.NET Core developer certificate).
  • Honour the Retry-After the authority returns with renewal information (RFC 9773). The renewal loop re-checks when the authority asks it to, held to a floor so a small or stale value cannot turn it into a tight poll, and never past a renewal that is already due.
  • Reject a certificate profile the directory does not advertise before the order is sent, with a message naming the profiles that exist, rather than travelling to the authority only to be refused. A profile still reaches an authority that advertises none, where an unknown one is reported rather than dropped.
  • Stop marking issued private keys exportable on Windows. The key container still lives only as long as the certificate object and is removed on dispose, so nothing accumulates, and Schannel, which rejects only ephemeral keys for server authentication, still accepts it.
  • Correct the Buypass deprecation dates. Buypass stopped issuing certificates in October 2025 and terminated its ACME service in April 2026.
  • Run the Pebble suite on a nightly schedule and on demand, and note in the README which suites run per push and which run nightly.

Trimming and native AOT

The core AutoHttps package is now trim and native-AOT compatible, and its build runs the trimming and AOT analyzers so it stays that way. Register it with AddAutoHttps(options => ...). The AddAutoHttps(IConfiguration) overload binds options by reflection, which a trimmed or ahead-of-time build cannot preserve, so it carries RequiresUnreferencedCode and RequiresDynamicCode.

Upgrading

1.4.0 added a guard that fails startup when a later ConfigureHttpsDefaults call replaces the certificate selector AutoHttps installs. 1.5.0 adds the companion case: when an HTTPS endpoint is declared before AddAutoHttps, AutoHttps warns once the host has started rather than serve the wrong certificate silently. If you configure HTTPS endpoints yourself, call AddAutoHttps before declaring them, or bind them with UseUrls or Kestrel:Endpoints, which work in either order.

v1.4.0

Choose a tag to compare

@astralmaster astralmaster released this 13 Sep 07:31

Fixes from a full audit of the certificate and ACME paths. Every change ships with unit, integration and Pebble (Docker) tests.

Core (AutoHttps 1.4.0)

  • Kestrel wiring: AutoHttps now preserves your own HTTPS defaults, the TLS options and any certificate selector, instead of dropping them, and fails at startup with a clear message when a later ConfigureHttpsDefaults call replaces its selector, rather than failing opaquely or serving the development certificate for a managed domain.
  • The self-signed fallback served before the first certificate arrives regenerates when it expires, instead of serving an expired certificate through a long outage.
  • A rate-limit Retry-After is capped at half the certificate's remaining validity, so a far-future value from a proxy or gateway cannot suspend renewal until the certificate expires.
  • The ACME directory is refetched when an endpoint it named answers 404 or stays unreachable, so an authority endpoint migration recovers without a restart.
  • The RFC 9773 replaces hint is sent only to authorities that advertise renewalInfo.
  • Renewal information is no longer queried for an already-expired certificate, and a finalize that reaches the authority before the order is ready is retried instead of abandoning the order.
  • Buypass is marked [Obsolete]: it ended its ACME service in 2025 and both directory URLs now return 404. Use Let's Encrypt, ZeroSSL or Google Trust Services.
  • Changing EmailAddress after the first run registers a new ACME account; this is now documented.

AutoHttps.Azure 1.0.1

  • Key Vault options are validated at startup: VaultUri is required, and SecretPrefix must be letters, digits and hyphens and at most 30 characters, so a bad prefix fails with a message that names it instead of a raw SDK error the first time a certificate is written.

The other companion packages (Cloudflare, Route 53, Redis) are unchanged and stay at 1.0.0.

AutoHttps 1.3.0

Choose a tag to compare

@astralmaster astralmaster released this 08 Sep 12:45

Five additions to the core library, all backward compatible, plus four new companion packages.

Core (AutoHttps 1.3.0)

  • Preferred certificate chain. PreferredChain names the root the served chain should lead up to when an authority offers more than one, the way certbot's preferred-chain flag does. This keeps clients working when a certificate authority retires a root.
  • DNS-01 propagation check. Point DnsPropagationResolver at a DNS-over-HTTPS resolver and AutoHttps polls it until the TXT record is visible before asking the authority to validate, instead of waiting a fixed time. Off by default, so nothing changes unless you enable it.
  • Fail-fast startup. RequireCertificateOnStartup stops the application if no certificate is obtained within StartupCertificateTimeout, so an orchestrator restarts the instance rather than leaving it up serving the self-signed fallback.
  • Jittered retry backoff. Instances that failed at the same moment no longer retry in lockstep, which is what the Let's Encrypt integration guide asks for.
  • Certificate revocation. Inject IAutoHttpsCertificateManager to revoke a certificate at the authority (RFC 8555 revokeCert) on a suspected key compromise or when decommissioning one.

The core package still has no NuGet dependencies.

New companion packages

Opt-in packages that keep their dependencies out of the core. Install one only if you need it:

  • AutoHttps.Cloudflare 1.0.0: dns-01 challenges, including wildcards, through the Cloudflare API.
  • AutoHttps.Route53 1.0.0: dns-01 challenges through AWS Route 53.
  • AutoHttps.Azure 1.0.0: dns-01 through Azure DNS, and certificate and account key storage in Key Vault.
  • AutoHttps.Redis 1.0.0: certificate and account key storage in Redis, for instances that do not share a filesystem.

Each targets net8.0 and net10.0 and depends on AutoHttps 1.3.0.

AutoHttps 1.2.0

Choose a tag to compare

@astralmaster astralmaster released this 02 Sep 11:25

Changes since 1.1.0. Four ways to see what AutoHttps is doing and react to it, none of them adding a dependency.

  • Certificate listeners. Implement IAutoHttpsCertificateListener and register it with AddCertificateListener to be told when the served certificate changes (issued, renewed, or picked up from another instance) and when an order fails. Use it to reload a proxy, copy the certificate elsewhere, warm a cache, or notify an operator. A change is reported only when the served certificate actually changes, so a reload runs when there is something new; a listener that throws is logged and cannot affect the certificate.
  • A health check. AddHealthChecks().AddAutoHttps() reports healthy while a certificate from the authority is in use, unhealthy once it has expired, and a configurable status before the first one is obtained. It is tagged ready, so a readiness probe picks it up while a liveness probe leaves it alone.
  • Metrics. A meter named AutoHttps publishes seconds until the certificate expires and a count of completed orders tagged outcome=success or outcome=failure. Subscribe with OpenTelemetry or a MeterListener.
  • An inspector. Inject IAutoHttpsCertificateInspector to read the current certificate and the next renewal time from your own code, for a status page or a diagnostic endpoint.

The ACME client and the ordering path are unchanged. Checked against the unit and integration suites and against Pebble, on net8.0 and net10.0. No breaking changes; upgrading is a package bump.

AutoHttps 1.1.0

Choose a tag to compare

@astralmaster astralmaster released this 31 Aug 10:26

Changes since 1.0.1.

  • Renewal polling now honours the authority's Retry-After. While an order or authorization is still pending, an authority may return a Retry-After header saying how long to wait before the next poll (RFC 8555 section 7.5.1). The client waits for it, never faster than PollInterval and never longer than 30 seconds in a single wait. With no header, behaviour is unchanged.
  • Recovers from the ACME alreadyReplaced error on renewal. When the authority reports that the certificate a renewal names has already been replaced (RFC 9773, HTTP 409), the client orders again without the replaces hint rather than failing every attempt.
  • Added a LettuceEncrypt migration guide.

Checked against the unit and integration suites on net8.0 and net10.0 and against Pebble. No breaking changes; upgrading is a package bump.

AutoHttps 1.0.1

Choose a tag to compare

@astralmaster astralmaster released this 21 Aug 19:24

Automatic HTTPS for ASP.NET Core. Add the package and the application obtains and renews its own TLS certificate from Let's Encrypt or any ACME certificate authority, with no external dependencies.

In this release:

  • http-01 and dns-01 challenges, with wildcard certificates through dns-01.
  • Renewal driven by ACME Renewal Information (RFC 9773), with a lifetime-proportional fallback, so the new six-day certificates renew without extra configuration.
  • Certificate profiles, including the shorter lifetimes Let's Encrypt now issues.
  • Let's Encrypt, ZeroSSL, Google Trust Services, Buypass, or any ACME directory, with external account binding where the authority requires it.
  • Targets net8.0 and net10.0 with zero NuGet dependencies.

Install:

dotnet add package AutoHttps

See the README for setup and configuration.