Issuance errors counting against limit of certificates

While doing my bi-monthly certificate renewal process, I found that one of two renewals failed:

An unexpected error occurred:
requests.exceptions.ReadTimeout: HTTPSConnectionPool(host='acme-v02.api.letsencrypt.org', port=443): Read timed out. (read timeout=45)

Which, fine, things can happen during busy hours and the likes. But I tried again, and it failed again. Fine, I will it sit for a few hours before retrying. 3 hours later, failed. Odd. Sit idle a few more hours, came back and now I cannot even request a new certificate:

An unexpected error occurred:
too many certificates (5) already issued for this exact set of identifiers in the last 168h0m0s, retry after 2025-07-04 20:14:43 UTC: see Rate Limits - Let's Encrypt

I understand limits have to be put in place, but not only I disagree with these limits being enforced, I disagree with how many times I actually ran certbot to reach such limits.

It is not that I had 5 certificates issued for the exact set of identifiers and decided to request new ones: they failed to be issued, so I have zero certificates issued, yet the counter hit 5 somehow. That is a bug, either at the certbot or at the LE rate limiting process.

Then, the second bit is with certbot: Unless it is, behind the scenes, requesting a certificate twice automatically, I ran it a total of four times for this certificate, with the last being the time I got the limit reached message. Or the rate limit does not consider an RSA and EC certificates as "exact", which even so increases the request count only by one, and still short of 5.

Below is the command line I use on my dirty shell script to request manual issued certificates:

DNS_HOOK=" --dns-rfc2136
--dns-rfc2136-credentials ${WORKDIR}/dns-creds.ini
--dns-rfc2136-propagation-seconds 180"

${CERTBOT_DIR}/bin/certbot certonly
--verbose
--agree-tos
-m ${EMAIL}
--preferred-chain "ISRG Root X1"
--force-renewal
--preferred-challenges dns
${DNS_HOOK}
--csr ${WORKDIR}/${DOMAIN}.ec.csr

Finally, as per Rate Limits page,

When testing or troubleshooting your applications, we recommend configuring your client to use our staging environment,

But guess what, everything works against the staging environment. I run both requests first against staging to ensure certbot updates were successful and no Internet errors exists before placing production requests:

${CERTBOT_DIR}/bin/certbot certonly
--dry-run
--test-cert
--register
--agree-tos
-m ${EMAIL}
--preferred-chain "ISRG Root X1"
--force-renewal
--preferred-challenges dns
${DNS_HOOK}
--csr ${WORKDIR}/${DOMAIN}.ec.csr
if [ $? -ne 0 ]; then
if [ x"${FRESH}" != "x" ]; then
rm -f ${WORKDIR}/${DOMAIN}.ec.key
fi
echo "Error during dry run, please verify and correct before continuing"
exit 1
fi

All that said, and while I have plenty time to resolve this, I feel it is worth for LE to review how errors are treated: not every error is at the end user' side.

The fact that you're doing some manual process every couple months means you're already a bit off the beaten path.

That is odd, and really the core of the problem. It sounds like you can't download the certificate once it's issued, and the steps you're taking are trying to issue a new certificate rather than download the one that was already issued.

No, they got issued. Your system just couldn't download them. But the work that Let's Encrypt needed to do, in order to sign the certificate, and promise to keep revocation status up-to-date for its lifetime, and so forth, already happened. Which is really the point of the limits, is for that.

Sure, but errors just in the download of certificate process is really unusual. And I'm not sure that the CA would have the ability to distinguish between a client that issued a certificate and couldn't download it, from one that could.

4 Likes

This almost always leads to duplicate certificate rate limitation.

2 Likes
  • Why are you registering an account every time?
  • Providing an email no longer does anything.
2 Likes

Specifying a preferred chain is rarely needed.

Utilizing a custom/recycled CSR is suboptimal.

2 Likes

In short, I think your process needs some updating and streamlining.

2 Likes

There's no need to use both of these parameters together. The dry run already uses the staging environment.

2 Likes

Multiple hints provided, let me go thru them.

The manual process ensures I can trigger these during maintenance windows.
I use these certificates in multiple systems that are not "certbot-friendly".
The next stages of my script are to call different APIs to transform, transfer, import and/or configure these certificates into a dozen or so other solutions not supported by any ACME style automation.
I do not know there is/was an option to download an already issued certificate within certbot.
Same as above for the usage of force-renewal, registration and email at every request.
A hand-picked chain just facilitates the process with importing certificates to other tools and systems.
The recycling of a CSR is dynamically generated and actually used as a way to quickly update the DSNs, which may or may not change in between the renewal window.

Given that I won't be able to request a new certificate until at least tomorrow, I have enough time to my next maintenance window to review certbot's documentation and update things that have been written 6+ years ago to bring them to 2025 standards. If, again, I can download an issued certificate, I sure would rather do that than hammer LE servers or get locked out again. Aside of the provided hints, if anything comes to mind, I would be more than happy to hear them.

1 Like

I highly recommend streamlining out the unnecessary/conflicting parameters. Forced renewal is a recipe for disaster. Just because you're using manual acquisition doesn't mean you aren't saving "certificate lineages" in certbot's data directories. I highly recommend reading up on that topic in the certbot doc. In essence, a "certificate" to certbot isn't the thing downloaded from the CA. It's a managed "entity" that you update and renew in certbot. You probably should just be renewing using --cert-name to allow certbot to utilize the data it already has. Note that a "cert name" is a name for convenience and need not directly reflect any domain names on the cert.

In particular, this section might prove most helpful (link below is to section of doc linked directly above):

2 Likes

Looking to the changelog of my script, --force-renewal was added because I could not request an RSA certificate right after an EC certificate was requested (or vice versa). Unless cerbot now issues both RSA and EC certificates and doesn't complain an "exact" certificate was just requested and bails out of the second request, I might need to rethink how to work around these restrictions - laziest but perhaps easier alternative is shifting them by a few weeks apart.

Thanks for the links, definitely going thru them next.

2 Likes

Ah, yeah. There's that. :slightly_smiling_face:

I believe that the "duplicate certificate" rate limit only takes into account the SANs on a cert and not the key type. This would quickly lead to rate limiting.

You might want to use different cert names for certs with the same SANs, but different key types. If you're "pinning" keys (not generating new keys with each renewal), special care will need to be taken there too. Pinning keys is not ideal, but is a strategy used in some systems to keep things more "static".

4 Likes

Yes, I do name files different, prefixed by the key type - such as domain.com.ec.pem or domain.com.rsa.pem. And again, pinning is part of the many, many supporting scripts I run after certbot delivered the certificates.

2 Likes

If you're in dire need of a working production cert, you can add/remove a domain name from your requested cert so the SANs aren't identical. You should get your house in order in the staging environment first (--test-cert and --break-my-certs). Since you're using certonly, I'm not too worried about your saving staging certs unless you start using deployment hooks and/or scripts to start deploying these "fake" certs. The important part is to get your "certificate lineages" in certbot's data in order then start leveraging them with --cert-name.

Note: I'm just now realizing how limited this all may be with --csr. :face_with_diagonal_mouth:

Update:

:grinning_face:

Here's your winner to avoid using --csr:

--reuse-key

It will let you pin your keys without needing to use a static csr.

3 Likes

Another thing that may be useful in testing, or as a workaround if you continue to have connectivity issues with reading data from Let's Encrypt's servers, would be to try some other CAs.

3 Likes

Note that most parameters specified in acquiring a certificate are saved in certbot's data with the lineage. Once those parameters (e.g. --reuse-key) are saved in the lineage, it will make scripting your acquisitions/renewals much easier and more robust.

Ideally:

certbot renew --cert-name x

with --dry-run used to test the config for x

2 Likes

@griffin apologies, I mis-interpreted the CSR and the OpenSSL config file. Updates to the DSN are done at the Openssl config file, then reflected on the CSR. Earlier on the process, I check if both key and CSR exist, and if either doesn't, they get generated fresh. The CSR gets deleted by the end of the process, so a fresh CSR is always created via my manual hook to "openssl req". I will look how to also approach the --reuse-key option.

Why use openssl to generate csr? Just let certbot do it for you (once key is pinned)?

2 Likes

... again, a 6+ year old script that has not evolved with certbot. It was working until it isn't :melting_face:

Now, how would certbot fetch a previously issued cert? That would resolve the problem at hand, while giving me 59 days and change to review the overall renewal process. Just taking the --force-renewal option would do the trick? Or a complete change to let the "renew" option to take over?

1 Like

I hear that. I'm a client developer myself (CertSage), so I've experienced these things from both sides of the fence. :sweat_smile:

I'll give you some time to digest.

3 Likes

3 options to run this anytime:

  • Redirect all traffic from /.well-known/acme-challenge/* onto another domain/server that runs your ACME client.
  • Proxy all traffic on /.well-known/acme-challenge/* to a higher port, and run certbot on standalone mode configured to that port - or proxy that traffic to a port/system/etc that is running our ACME client
  • Use the DNS-01 challenge. You can also CNAME the _acme-challenge record onto another domain
2 Likes