Most people running HTTPS in 2026 think in 90-day chunks. Let's Encrypt issues the certificate, a renewal job runs on cron, and we have collectively forgotten what one-year and two-year certificates used to feel like. The renewal automation is one of the quiet success stories of the last decade.
But Let's Encrypt is not the only certificate authority you can use. If a domain sits behind Cloudflare's proxy - and a large share of the public web does - there is a different CA available, with very different rules. Cloudflare's Origin CA will issue a certificate valid for up to 5,475 days. That is roughly fifteen years.
Almost nobody uses it. This article is about what it is, when it is the right tool, what the catch is, and how to implement an auto-issue pipeline that picks between the Cloudflare Origin CA, DNS-01 Let's Encrypt, and HTTP-01 Let's Encrypt without ever asking the operator to think about it.
The two legs of HTTPS, very briefly
When a visitor loads https://example.com and that domain is proxied through Cloudflare - the orange-cloud icon in the dashboard - the TLS connection the browser makes is not with the origin server. It is with Cloudflare's edge. Cloudflare presents an edge certificate, issued automatically on Cloudflare's behalf by Let's Encrypt or Google Trust Services. The browser trusts that certificate because both CAs sit in the public root store.
Behind the edge, Cloudflare then opens a second TLS connection to the origin. That second leg is the one almost nobody thinks about. It uses whatever certificate is installed on the origin server. The browser never sees it; only Cloudflare's edge does.
This split is the entire reason Cloudflare Origin Certificates exist. The certificate on the second leg only has to be trusted by Cloudflare's edge - not by Chrome, Firefox, or Safari. Cloudflare can therefore issue it themselves from a private CA, with terms no public CA would touch.
The Cloudflare Origin CA API accepts the following requested validity values, in days:
days
The last value is the one that matters here. 5,475 days is roughly fifteen years. Cloudflare will sign that certificate and trust it at their edge until 2041.
When this is the right tool
The constraint to keep clearly in mind: an Origin Certificate is only valid on the edge-to-origin leg. It is not a substitute for a public certificate. If someone bypasses Cloudflare and connects directly to the origin's IP - either because DNS leaks the origin, or because they have it from history - the browser will see a certificate signed by an unknown root and refuse the connection. That is by design.
This is exactly the right tool when:
- The domain is always proxied through Cloudflare (orange cloud on for the apex and the wildcards you care about).
- You want to set up edge-to-origin TLS once and then not think about renewals for the operational lifetime of the server.
- You are happy enforcing Cloudflare's "Full (strict)" SSL mode, which makes Cloudflare validate the origin certificate on every connection. Origin certificates are trusted by Cloudflare's edge specifically for this mode.
It is the wrong tool when the domain is not proxied, when you sometimes serve traffic directly (bypassing Cloudflare), or when your threat model treats Cloudflare's edge as untrusted. For everything else, a 15-year origin cert eliminates one of the most common operational failure modes - "the certificate quietly expired" - for as long as the server stays in service.
The decision tree
A practical implementation cannot just default to Origin Certificates. Most domains the system handles will fall into one of three buckets, and the right certificate path differs for each. The pipeline that has to make that choice looks like this:
Three observations about this tree.
First, the leaf for proxied-CF-domain is the Origin Certificate. Nothing else needs to renew. The branch beneath it is the case where the domain sits in Cloudflare DNS but is not proxied (grey-cloud) - here a public certificate is still required because the browser is talking directly to the origin, and DNS-01 is preferred over HTTP-01 because it does not depend on the origin being publicly reachable on port 80 at challenge time.
Second, the "is the record proxied" check is per-domain runtime state, not a global setting. A user can flip the orange cloud at any time, which means the SSL pipeline should re-check on every renewal rather than memoising the decision.
Third, the standard HTTP-01 fallback on the right is there for the large category of domains that are not on Cloudflare at all - and also serves as the fallback when a Cloudflare zone is detected but the operator never connected an API credential. The point of the pipeline is that all three paths feel identical to the user: press one button, get HTTPS.
Implementing the Origin Certificate request
The Cloudflare Origin CA exposes a single endpoint: POST /certificates against api.cloudflare.com/client/v4. The interesting parts of the request are the hostname list, the request type, the validity, and a CSR you generate locally.
Here is the Go that does it, trimmed of error handling for readability:
// Generate an ECDSA P-256 key for the certificate. // Small, fast, modern. RSA is allowed by the API but you almost // never want it here. privKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) // Build a CSR with the apex and a wildcard in the SAN list. csrTemplate := x509.CertificateRequest{ Subject: pkix.Name{CommonName: hostnames[0]}, DNSNames: hostnames, SignatureAlgorithm: x509.ECDSAWithSHA256, } csrDER, _ := x509.CreateCertificateRequest( rand.Reader, &csrTemplate, privKey, ) csrPEM := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE REQUEST", Bytes: csrDER, }) // The Origin CA request body. body, _ := json.Marshal(originCertRequest{ Hostnames: hostnames, // []string{"example.com", "*.example.com"} RequestType: "origin-ecc", // or "origin-rsa" RequestedValidity: 5475, // ~15 years CSR: string(csrPEM), }) req, _ := http.NewRequest( "POST", "https://api.cloudflare.com/client/v4/certificates", bytes.NewReader(body), ) req.Header.Set("Content-Type", "application/json")
Authentication is where this endpoint historically tripped people up. The Origin CA endpoint accepts two distinct auth modes, depending on the credential type, and the headers differ:
// Modern: a scoped API Token with the Origin CA scope. // Looks like a 40-char base62/url-safe string. if looksLikeOriginAPIToken(apiKey) { req.Header.Set("Authorization", "Bearer "+apiKey) } else { // Legacy: User Service Key plus the account's email. // Older accounts still need both headers. req.Header.Set("X-Auth-User-Service-Key", apiKey) req.Header.Set("X-Auth-Email", email) req.Header.Set("X-Auth-Key", apiKey) }
A good implementation passes whichever the operator gave it. The heuristic check on length and character set is enough to pick the right auth mode without making the user declare which kind of credential they are using.
The response, on success, contains the signed certificate in PEM form. Concatenate it with your locally generated private key, install both on the origin, and configure nginx or Apache to present them. Cloudflare's edge will accept the certificate the moment "Full (strict)" SSL is enabled for the zone.
Why HTTP-01 fails behind a Cloudflare proxy
The reason DNS-01 is the fallback for the non-Origin-CA case - and not HTTP-01 - is a structural one worth being explicit about.
HTTP-01 works by Let's Encrypt's validator making an HTTP request to http://example.com/.well-known/acme-challenge/<token> and expecting the response to contain a specific value the client has placed on disk. When the domain is proxied through Cloudflare, that HTTP request hits Cloudflare's edge first. Several things can then go wrong:
- Cloudflare's "Always Use HTTPS" page rule rewrites the request to HTTPS, breaking the HTTP-01 spec which expects port 80.
- The challenge path may be cached, served stale, or rate-limited by the edge.
- A custom WAF rule or firewall rule may block the request entirely.
- Some edge configurations strip the response body or modify content-types in ways that fail the validator.
You can sometimes make HTTP-01 work by temporarily flipping the proxy off, issuing, and flipping it back on. That works, but it briefly exposes the origin IP, requires write access to the DNS record, and turns a one-step operation into three. It is what you fall back to when DNS-01 is not available.
DNS-01 sidesteps the proxy entirely. The challenge value is placed in a TXT record at _acme-challenge.example.com, the validator queries DNS rather than HTTP, and Cloudflare's proxy is irrelevant. The cost is that the issuance tool needs an API credential that can write DNS records on the zone - which, conveniently, is the same credential already configured for the Origin CA path. The two flows share authentication.
DNS-01 with lego, briefly
The implementation uses go-acme/lego's built-in Cloudflare DNS provider, configured with the same credential resolution as the Origin path:
cfg := cloudflare.NewDefaultConfig() if looksLikeAPIToken(apiKey) { cfg.AuthToken = apiKey } else { cfg.AuthEmail = email cfg.AuthKey = apiKey } provider, _ := cloudflare.NewDNSProviderConfig(cfg) client.Challenge.SetDNS01Provider(provider)
A Global API Key carries the auth via email and key headers; a scoped API Token carries it via a Bearer header. lego accepts both via the same config struct and prefers the Bearer when set.
One subtlety worth flagging: when the ACME client is set to DNS-01, the implicit HTTP-01 setup the library does by default has to be suppressed. Otherwise lego tries both, and you get confusing logs about HTTP-01 failures even when DNS-01 succeeds. Inside the pipeline this looks like a simple flag:
if s.challengeMode == "dns01" { // already configured above; do not also set HTTP-01 } else { client.Challenge.SetHTTP01Provider(httpProvider) }
The decision log
A pipeline that picks between three paths quietly is convenient when it works, and opaque when it does not. The remedy is to record a plain-text log of the choices made on every issue or renewal, and return that log alongside the result. When something goes wrong, the log explains why the pipeline chose the path it did. When something goes right, the log is a credible audit trail.
[SSL-AUTO] Starting auto-issue for example.com (prefer=auto) [SSL-AUTO] CF zone matched: example.com (credential=Main, account=ops@...) [SSL-AUTO] CF record present: example.com [A] proxied=true [SSL-AUTO] Auto-strategy: cf_origin (proxied -> 15-year cert, no renewal) [SSL-AUTO] ECDSA P-256 keypair generated [SSL-AUTO] CSR built: CN=example.com SAN=[example.com, *.example.com] [SSL-AUTO] POST /certificates -> 200 OK (id=...) [SSL-AUTO] Certificate installed: /etc/ssl/.../example.com/fullchain.pem [SSL-AUTO] CF Origin Certificate installed (5475 days, hostnames=2)
There is nothing sophisticated about this. It is just log.Printf at every meaningful branch. The discipline is making the log exhaustive enough that someone unfamiliar with the codebase can reconstruct the decision from the trace alone.
Trade-offs the documentation will not tell you
A 15-year certificate sounds unambiguously good until you think about key compromise. If the private key leaks on day one, you have a 5,475-day window during which an attacker who can intercept the edge-to-origin leg can impersonate the origin to Cloudflare's edge. That is not a hypothetical for everyone, but it is not zero either.
A few mitigations that are worth applying regardless of the validity period:
- Rotate keys annually, not on expiry. Validity is the date the cert stops working, not the date you should stop using it. Reissuing once a year resets the compromise window. The implementation pays nothing for this - the same code path that issued the original cert reissues a fresh one.
- Restrict by IP, not just by cert. Cloudflare publishes its edge IP ranges. Filtering inbound origin traffic to only those IPs makes a leaked origin certificate useless to an attacker who is not actually inside Cloudflare's edge.
- Pin the certificate fingerprint in monitoring. If the certificate the origin presents changes, you want to know within minutes, not at the next renewal.
- Mandate Full (strict). Cloudflare's "Full" mode accepts any certificate; only "Full (strict)" actually validates the chain. Origin certificates are designed for strict mode and should be paired with it.
The other class of trade-off is operational. A 15-year cert removes one task - renewal - and replaces it with a different task - making sure the original issuance succeeded, and the cert ended up where you think it did, and your monitoring still notices when something goes wrong. The mental load shifts more than it disappears. Operators who have never lived with anything other than 90-day renewals sometimes find that absence of a renewal cycle slightly disorienting, and create a yearly rotation anyway just for the cadence.
Renewals are a separate problem
For the Let's Encrypt branches of the tree, the renewal job is the standard one. A reconciler walks the installed certificates, anything within 30 days of expiry gets a fresh issuance through the same pipeline, and the new cert replaces the old in place. The renewal uses the same decision tree as the original - the domain's proxy state can have changed since the last issue.
For the Origin Certificate branch the renewal job is essentially a no-op. The cert is valid for fifteen years; there is no urgency. The only reason to touch it is the annual rotation discussed above, and that is a cron job at the operator's discretion rather than something the SSL pipeline forces.
What is worth keeping in the reconciler regardless of cert type is the health check: confirm that the cert on disk matches the cert the origin is presenting, that the chain still validates, and that the private key has not drifted from the public key in the cert. A surprisingly common failure mode is that a service restart picked up a stale file, and the system is happily running with a cert that no longer matches its key. That is independent of validity period.
What this looks like to the operator
With the pipeline above, the user-visible interaction collapses to a single button. The operator opens a domain, clicks "Issue SSL", and one of three things happens:
- The domain is proxied through Cloudflare and a credential exists. The pipeline issues an Origin Certificate covering apex and wildcard, installs it, and prints the decision log. Total time: a few seconds.
- The domain is on Cloudflare but the proxy is off. The pipeline performs a DNS-01 ACME order against Let's Encrypt using the Cloudflare DNS API, installs the resulting cert, and schedules renewal at 60 days.
- The domain is not on Cloudflare. The pipeline performs a standard HTTP-01 ACME order against Let's Encrypt, installs the cert, and schedules renewal at 60 days.
The interesting design property is that the three paths are not exposed as a choice. The operator does not pick - the pipeline does, deterministically, from observable state. The decision log is the receipt.
Why almost nobody does this
A reasonable question after reading this far: if a 15-year certificate is available, free, and well-suited to the very common case of a proxied domain, why is the default for almost every panel and reverse-proxy stack still "throw Let's Encrypt at every problem"?
A few honest reasons.
The Origin CA is private and surprising. It does not show up in the same documentation flows as Let's Encrypt; it is a feature of Cloudflare's dashboard, easy to miss. Many engineers have never heard of it.
It is not a drop-in replacement. Origin certificates are useful in the specific topology where a CDN terminates TLS at the edge. If your stack does not look like that, they are not relevant. Generalised guides understandably default to the topology that always works.
"Just use Let's Encrypt" is good advice 90% of the time, and engineers internalised it during the years when getting any free certificate at all was the win. Optimising further has rarely been the bottleneck.
The renewal-as-a-fact-of-life mental model is sticky. Some teams treat the 90-day renewal as a feature: it forces the automation to be exercised, and a broken renewal flow is found within three months rather than at year fourteen. That argument is defensible.
None of those are reasons to ignore the option. They are reasons the option has been quietly under-used, which is a different thing. For the right topology - and "the right topology" describes a meaningful share of the modern web - 15-year origin certificates remove a class of operational failure that the industry has implicitly accepted as a recurring tax.
Wrapping up
None of the techniques above are new. Cloudflare Origin CA has been generally available for years; DNS-01 has been part of ACME from the beginning; lego is a mature library; the decision-tree pattern is unremarkable engineering. What is new, or at least underused, is wiring all four together into a single auto-issue pipeline that picks the right path for the topology in front of it and leaves no decision for the operator to make.
The fifteen-year part is the headline because the number is jarring, but the deeper point is the absence of a renewal cycle for proxied domains. A renewal that does not exist cannot fail. For a class of failure modes that have plagued operators since HTTPS became universal, that is a meaningful reduction in the surface area where things can go wrong.
The auto-issue pipeline described in this article is implemented in Panelica, a hosting panel. The relevant files are origin_cert.go (Cloudflare Origin CA request), acme_dns_provider.go (lego Cloudflare DNS-01 wiring), and ssl_handler.go (the decision tree). Questions or corrections welcome.