ACME v2 300 new order rate limit per 3 hours



I’ve written a custom ACME client integration to request certificates for a large number of domains (8,000+). I have read and understand the limit of 300 new orders every 3 hours for an ACMEv2 client. However, I seem to be hitting this limit in situations where it is not expected.

For instance, I created exactly 100 new orders earlier today before reaching the limit and getting a 429 error. I had not made any orders within the 3 hours prior to that time.

My questions are:

  • Is the 3 hour limit rolling or based on UTC?
  • Once the limit is reached, is there a “Retry-After” of exactly 3 hours?
  • Is there an undocumented sub-limit such as 100 per hour and 300 per 3 hours, whichever is hit first?

The rate limit docs just don’t seem to match the behavior I’m seeing, and I would love to understand how it works and document it clearly for someone trying this in the future.



Hi @rokclimb15,

Always nice to hear about more client integrations :slight_smile:

Can you share the exact 429 response you received? What is your ACME account ID?

All of our rate limits are a rolling window.

We don’t send Retry-After headers presently.

There are no undocumented rate limits. That would definitely be a bug :slight_smile:

Is there any chance you’re hitting the more general “Overall Requests” rate limit mentioned on ?


Were those 100 names completely unrelated?
Or does this limit apply:
The main limit is Certificates per Registered Domain, (50 per week).

If they were “related”, maybe you should consider getting wildcard certs.


Hi @cpu,

Thanks for the quick response!

Here’s my error:

 This client reached the rate limit of the server: Error creating new order :: too many new orders recently: see (on request "POST")

I’m having some trouble locating the header on the POST response with my account id. Would a ID for one of my certs suffice?


The names are completely unrelated in terms of the domain.


Ok! That rules out the overall requests rate limit or the certificates per domain ratelimit @rg305 mentioned.

If you share a domain name you’ve been issuing for I can work backwards to the account ID. Or if you’re interacting with the API from a single IP address I can work with that too.


@cpu thanks for your help! is one of the domains I’ve issued a cert against under my account.


Happy to help!

Great, thank you. I was able to find your ACME account ID (it was 45707749 for your records :-)). Looking at the past 14 days I see that:

  • on 19/11/2018 you successfully created 301 new orders.
  • on 20/11/2018 you successfully created 499 new orders.

Looking at the 19th I see the first successful new-order response was at 19/11/2018 20:34:11. The first 429 was at 19/11/2018 20:48:31, exactly 300 orders later. That matches my expectation of how the rate limit is implemented since the requests were all within the 3 hour rolling window started at 20:34. I didn’t check the 20th but I bet it would match up too.

Overall you sent a lot more new-order requests, the difference would be the # of 429’s received.

  • on 19/11/2018 there were 557 new-order requests
  • on 20/11/2018 there were 646 new-order requests

When I quickly looked at the new order requests it seems like your system is submitting duplicate orders for every domain name. E.g. on 20/11/2018 18:02:36 your system POSTed new-order with one identifier Less than one second at 20/11/2018 18:02:37 your system POSTed new-order with the same identifier. That shouldn’t affect your new-order rate limit because our system is smart enough to reuse an already existing pending order for the duplicate order request. That said: it does indicate a potential bug on your side and it will affect your overall request rate limit negatively.

One other point of feedback: Your ACME integration is sending the user-agent “GuzzleHttp/6.2.1 curl/7.53.1 PHP/7.2.8” It would be nice if you could change that to something that also includes your organization as an identifier. It makes it a lot easier for us to separate your ACME client from other PHP clients if you can do that.

Hope that helps!


Thanks @cpu!

I’m not certain why it would POST new-order twice. I will look into that and change the user-agent.


One more thing! If you haven’t already fiddled with it you might find installing Pebble and configuring your ACME integration to use it as the ACME server might help you debug this. You won’t have to worry about rate limits that way and can interrogate both the client and server side.


Very helpful! Thanks!


The code indeed makes perfectly sense:

Could it perhaps be that all the checks the code does, before adding it to the NewOrder-database at the bottom of the function, are too slow? That, when two orders are requested fast enough, the first isn’t added to the database quickly enough, so both NewOrder()-functions are running simultaneously?


@cpu I studied the call stack carefully and discovered that the duplicate calls to new-order are somewhat intentional in the client lib I am using ( Since I am using http-01 authorization, I must explicitly request this authorization type prior to requesting a new order. But requesting authorization creates a new-order as well.

I am going to test a different method of requesting the authorization which will result in only one call to new-order. I mention it here in case anyone encounters this issue in the future.


Hi @rokclimb15,

I’m sorry, that doesn’t make much sense to me. The ACME protocol doesn’t have the client choose a challenge type by explicitly requesting it. You always just follow the same process:

  1. The client asks the server to create a new order for a set of identifiers
  2. The server responds with an order. The order has URLs for an authorization for each identifier. Each authorization has some challenges.
  3. The client picks one challenge for each identifier authorization and proceeds.

Let’s Encrypt will always return a HTTP-01 challenge as part of step 2 unless the identifier is for a wildcard name. You don’t need to create two orders to pre-select HTTP-01 or anything of the sort.

It still feels like a bug to me but perhaps I’m misunderstanding why ACMEPHP is doing this.


Hi @cpu,

I would classify it more as an unintended side-effect of using the API client a certain way. It is possible to memoize the resulting order and use a call to finalize it. In that case, I just have to fetch the authorizations manually and find the one I want (http-01). In looking at the methods of the client alone, it seemed like less work and it wasn’t initially apparent that it would result in duplicate calls to new-order.

I suppose it could be an item for documentation clarification since there isn’t a single way to consume the LetsEncrypt ACME API with this client. It allows for some greater flexibility, and the ability to shoot yourself in the foot, as I have done here.


That makes sense! Thanks for the explanation.


This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.