Skip to content

Rate Limiting Troubleshooting & Errors

TL;DR: Rate limit errors require waiting for token refill—exact set of identifiers limit (5 per 7 days) can be worked around by modifying the domain set, while authorization failures (5 per hour) reset automatically or after successful validation.

Overview

Rate limiting errors disrupt certificate automation and require systematic troubleshooting to identify root causes and implement recovery strategies. This comprehensive guide addresses common rate limiting failures, diagnostic procedures, and proven workarounds based on production deployment experience. Operations teams use these patterns to recover from rate limit scenarios and prevent future occurrences.

Production environments encounter rate limiting when certificate automation workflows exceed Let's Encrypt's capacity constraints. Common scenarios include troubleshooting loops that consume the exact set of identifiers limit, authorization failure cascades that trigger hourly limits, and bulk operations that exhaust new orders quota. Understanding error patterns helps teams diagnose issues efficiently and choose appropriate recovery strategies.

Enterprise troubleshooting requires analyzing rate limit error messages, calculating token refill timelines, and implementing workarounds that maintain service availability. Unlike many errors that have immediate fixes, rate limits require patience as capacity refills gradually. Teams must balance operational needs with rate constraints, sometimes using alternative validation methods or modifying certificate scopes.

Common Rate Limiting Errors

Exact Set of Identifiers Limit

Error Pattern:

Error creating new order :: too many certificates already issued for exact set of domains: example.com,www.example.com: see https://letsencrypt.org/docs/rate-limits/

Details:

  • Limit: 5 certificates per exact set of identifiers per 7 days
  • Refill Rate: 1 certificate per 34 hours
  • Override Available: No

Root Causes:

  1. Repeated certificate requests during troubleshooting
  2. Reinstalling ACME client or deleting configuration data
  3. Testing configuration changes with production certificates
  4. Automated systems requesting duplicate certificates

Recovery Strategies:

Option 1: Modify Identifier Set (Immediate)

# Original request that hit limit
# certbot certonly -d example.com -d www.example.com

# Workaround: Add/remove an identifier to create a different set
certbot certonly -d example.com -d www.example.com -d blog.example.com

# Or remove an identifier
certbot certonly -d example.com

# Note: New set still subject to other limits

Option 2: Wait for Refill

# Calculate when next certificate slot will be available
# Limit: 5 certificates per 7 days
# Refill: 1 per 34 hours

# Check recent certificates via Certificate Transparency
curl -s "https://crt.sh/?q=example.com&output=json" | \
    jq '[.[] | select(.not_before > (now - 604800 | todate))] | 
    {not_before: .not_before, domains: .name_value}' | less

Prevention:

#!/bin/bash
# Always test with staging before production
certbot certonly --staging --dry-run \
    --webroot -w /var/www/html \
    -d example.com -d www.example.com

# Only request production cert after staging success
if [ $? -eq 0 ]; then
    certbot certonly --staging \
        --webroot -w /var/www/html \
        -d example.com -d www.example.com

    # Final production request
    certbot certonly \
        --webroot -w /var/www/html \
        -d example.com -d www.example.com
fi

New Orders per Account Limit

Error Pattern:

Error creating new order :: too many new orders recently (300) for this account, retry after 2026-01-25 18:30:00 UTC

Details:

  • Limit: 300 orders per account per 3 hours
  • Refill Rate: 1 order per 36 seconds
  • Override Available: Yes (for large hosting providers)

Root Causes:

  1. Bulk certificate operations without rate limiting
  2. Automated renewal scripts running too frequently
  3. Multiple services sharing the same ACME account
  4. Testing or development using production accounts

Recovery Strategies:

Option 1: Wait for Token Refill

# Parse Retry-After from error message
# "retry after 2026-01-25 18:30:00 UTC"

# Calculate wait time
RETRY_AFTER="2026-01-25 18:30:00"
RETRY_EPOCH=$(date -d "$RETRY_AFTER" +%s)
NOW_EPOCH=$(date +%s)
WAIT_SECONDS=$(( RETRY_EPOCH - NOW_EPOCH ))

echo "Must wait $WAIT_SECONDS seconds ($((WAIT_SECONDS / 60)) minutes)"

# Automated wait and retry
sleep $WAIT_SECONDS
certbot certonly --webroot -w /var/www/html -d example.com

Option 2: Use Multiple ACME Accounts

# Create separate accounts for different services
certbot register --email [email protected] --account service1
certbot register --email [email protected] --account service2

# Use specific account for requests
certbot certonly --account service1 \
    --webroot -w /var/www/html -d app1.example.com

certbot certonly --account service2 \
    --webroot -w /var/www/html -d app2.example.com

Option 3: Request Override (Large Hosting Providers)

# Submit override request (processing takes weeks)
# https://isrg.formstack.com/forms/rate_limit_adjustment_request

# Provide:
# - Account ID
# - Expected certificate volume
# - Business justification
# - Infrastructure details

Prevention:

#!/bin/bash
# Enterprise-grade order rate limiting

ACCOUNT="production"
MAX_ORDERS_PER_HOUR=90  # Conservative limit (300/3h = 100/h)
ORDER_COUNT_FILE="/var/cache/certbot-orders.count"

# Track orders in current hour
CURRENT_HOUR=$(date +%Y%m%d%H)
if [ -f "$ORDER_COUNT_FILE" ]; then
    STORED_HOUR=$(head -n1 "$ORDER_COUNT_FILE")
    STORED_COUNT=$(tail -n1 "$ORDER_COUNT_FILE")

    if [ "$CURRENT_HOUR" = "$STORED_HOUR" ]; then
        if [ "$STORED_COUNT" -ge "$MAX_ORDERS_PER_HOUR" ]; then
            echo "Order limit reached for this hour, waiting..."
            sleep 3600
        fi
        ORDERS=$((STORED_COUNT + 1))
    else
        ORDERS=1
    fi
else
    ORDERS=1
fi

# Update counter
echo "$CURRENT_HOUR" > "$ORDER_COUNT_FILE"
echo "$ORDERS" >> "$ORDER_COUNT_FILE"

# Proceed with order
certbot certonly --account "$ACCOUNT" \
    --webroot -w /var/www/html -d example.com

New Certificates per Registered Domain Limit

Error Pattern:

Error creating new order :: too many certificates already issued for registered domain "example.com": 50, retry after 2026-01-26 12:00:00 UTC

Details:

  • Limit: 50 certificates per registered domain per 7 days
  • Refill Rate: 1 certificate per 202 minutes (approximately 3.4 hours)
  • Override Available: Yes (for large hosting providers)

Root Causes:

  1. Large-scale certificate deployment or migration
  2. Multiple subdomains requiring individual certificates
  3. Development/testing consuming production quota
  4. Automated systems creating redundant certificates

Recovery Strategies:

Option 1: Consolidate Identifiers

# Instead of separate certificates:
# certbot certonly -d app1.example.com
# certbot certonly -d app2.example.com
# certbot certonly -d app3.example.com

# Use single certificate with multiple identifiers (up to 100)
certbot certonly \
    -d app1.example.com \
    -d app2.example.com \
    -d app3.example.com \
    -d app4.example.com

# Benefits:
# - Consumes only 1 certificate from domain limit
# - Still subject to exact set limit (5 per 7 days)

Option 2: Use Subdomains Under Different Registered Domains

# If hitting limit on example.com, use subdomain under different registered domain
# Registered domains are determined by Public Suffix List

# Instead of: app.example.com (counts against example.com)
# Use: app.example.co.uk (counts against example.co.uk if separate registered domain)

# Check registered domain at: https://publicsuffix.org/list/

Option 3: Wait for Refill

#!/bin/bash
# Monitor domain certificate count

DOMAIN="example.com"
LIMIT=50

check_domain_certificates() {
    # Query Certificate Transparency logs
    COUNT=$(curl -s "https://crt.sh/?q=${DOMAIN}&output=json" | \
        jq '[.[] | select(.not_before > (now - 604800 | todate))] | length' 2>/dev/null)

    echo "Certificates for $DOMAIN in last 7 days: $COUNT/$LIMIT"

    if [ "$COUNT" -ge "$LIMIT" ]; then
        echo "Rate limit reached. Capacity refills at 1 per 202 minutes."
        echo "Next certificate available in approximately: $((202 - (COUNT - LIMIT) * 202)) minutes"
        return 1
    fi
    return 0
}

check_domain_certificates

Authorization Failures Limit

Error Pattern:

Error finalizing order :: too many failed authorizations recently: 5 for identifier "example.com"

Details:

  • Limit: 5 authorization failures per identifier per account per hour
  • Refill Rate: 1 failure per 12 minutes
  • Override Available: No

Root Causes:

  1. Misconfigured DNS records
  2. Firewall blocking validation requests
  3. Web server configuration issues
  4. Incorrect challenge type selection

Recovery Strategies:

Option 1: Fix Underlying Issue Then Wait

#!/bin/bash
# Troubleshoot and fix before retrying

DOMAIN="example.com"

# Test HTTP-01 challenge accessibility
echo "Testing HTTP-01 challenge endpoint..."
curl -I "http://${DOMAIN}/.well-known/acme-challenge/test-file"

# Test DNS resolution
echo "Testing DNS resolution..."
dig +short "$DOMAIN" A
dig +short "$DOMAIN" AAAA

# Test DNS-01 if using
echo "Testing DNS-01 TXT record..."
dig +short "_acme-challenge.${DOMAIN}" TXT

# Only retry after fixes and waiting 1 hour
echo "Authorization failures reset after 1 hour of no failures"
echo "Next attempt available at: $(date -d '+1 hour' '+%Y-%m-%d %H:%M:%S')"

Option 2: Use Different Challenge Type

# If HTTP-01 failing, try DNS-01
certbot certonly --dns-cloudflare \
    --dns-cloudflare-credentials ~/.secrets/cloudflare.ini \
    -d example.com

# If DNS-01 failing, try HTTP-01
certbot certonly --webroot -w /var/www/html \
    -d example.com

Prevention:

#!/bin/bash
# Pre-flight checks before certificate request

DOMAIN="example.com"
WEBROOT="/var/www/html"

echo "Running pre-flight checks for $DOMAIN..."

# Check DNS resolution
if ! dig +short "$DOMAIN" A | grep -q .; then
    echo "ERROR: DNS A record not found for $DOMAIN"
    exit 1
fi

# Check HTTP accessibility
if ! curl -f -s -I "http://${DOMAIN}" > /dev/null; then
    echo "ERROR: HTTP not accessible for $DOMAIN"
    exit 1
fi

# Check webroot accessibility
if [ ! -d "$WEBROOT/.well-known/acme-challenge" ]; then
    echo "Creating challenge directory..."
    mkdir -p "$WEBROOT/.well-known/acme-challenge"
fi

# Test challenge file serving
TEST_TOKEN="test-$(date +%s)"
echo "test" > "$WEBROOT/.well-known/acme-challenge/$TEST_TOKEN"
if ! curl -f -s "http://${DOMAIN}/.well-known/acme-challenge/$TEST_TOKEN" | grep -q "test"; then
    echo "ERROR: Cannot serve challenge files for $DOMAIN"
    rm "$WEBROOT/.well-known/acme-challenge/$TEST_TOKEN"
    exit 1
fi
rm "$WEBROOT/.well-known/acme-challenge/$TEST_TOKEN"

echo "Pre-flight checks passed. Proceeding with certificate request..."
certbot certonly --webroot -w "$WEBROOT" -d "$DOMAIN"

Consecutive Authorization Failures

Error Pattern:

Your account has an issue. Please contact support.

Details:

  • Limit: 1,152 consecutive authorization failures
  • Recovery: Resets after 1 successful authorization
  • Override Available: No

Root Causes:

  1. Persistent configuration problems causing repeated failures
  2. Automated renewal systems with systemic issues
  3. Long-running validation problems across many domains

Recovery Strategy:

#!/bin/bash
# Recover from consecutive failure limit

echo "Account paused due to consecutive authorization failures"
echo "Must complete 1 successful authorization to reset counter"

# Strategy: Find ONE domain that can validate successfully
# Use most reliable configuration

RELIABLE_DOMAIN="test.example.com"
WEBROOT="/var/www/html"

# Ensure perfect configuration
mkdir -p "$WEBROOT/.well-known/acme-challenge"
chmod 755 "$WEBROOT/.well-known/acme-challenge"

# Test manually first
TEST_TOKEN="recovery-test-$(date +%s)"
echo "test-content" > "$WEBROOT/.well-known/acme-challenge/$TEST_TOKEN"

echo "Verify this URL returns test-content:"
echo "http://${RELIABLE_DOMAIN}/.well-known/acme-challenge/$TEST_TOKEN"
read -p "Press enter when verified..."

# Request certificate for reliable domain
certbot certonly --webroot -w "$WEBROOT" -d "$RELIABLE_DOMAIN"

if [ $? -eq 0 ]; then
    echo "SUCCESS! Consecutive failure counter has been reset."
    echo "You can now proceed with other certificate requests."
else
    echo "FAILED! Fix validation issues before retrying."
fi

rm "$WEBROOT/.well-known/acme-challenge/$TEST_TOKEN"

Overall Request Limits (Per-Endpoint)

Error Pattern:

503 Service Unavailable
Retry-After: 60

Details:

  • Load balancer enforces per-IP per-second limits by endpoint
  • Example: /acme/new-order limited to 300 req/sec with burst of 200
  • Response includes Retry-After header

Recovery Strategy:

#!/usr/bin/env python3
# Handle 503 rate limiting with exponential backoff

import requests
import time
from datetime import datetime

class ACMEClientWithBackoff:
    def __init__(self, base_url):
        self.base_url = base_url
        self.max_retries = 5

    def make_request(self, endpoint, method='GET', **kwargs):
        retry_count = 0
        backoff = 1

        while retry_count < self.max_retries:
            response = requests.request(method, f"{self.base_url}/{endpoint}", **kwargs)

            if response.status_code == 503:
                # Parse Retry-After header
                retry_after = int(response.headers.get('Retry-After', backoff))
                print(f"503 Service Unavailable. Waiting {retry_after} seconds...")
                time.sleep(retry_after)

                retry_count += 1
                backoff = min(backoff * 2, 300)  # Max 5 minutes
                continue

            return response

        raise Exception(f"Max retries ({self.max_retries}) exceeded")

# Usage
client = ACMEClientWithBackoff("https://acme-v02.api.letsencrypt.org")
response = client.make_request("directory")

Enterprise Troubleshooting Patterns

Rate Limit Detection and Alerting

#!/bin/bash
# Automated rate limit detection

LOGFILE="/var/log/letsencrypt/letsencrypt.log"
ALERT_EMAIL="[email protected]"

# Check for rate limit errors
RATE_LIMIT_ERRORS=$(grep -c "too many" "$LOGFILE" 2>/dev/null)

if [ "$RATE_LIMIT_ERRORS" -gt 0 ]; then
    # Parse error details
    LATEST_ERROR=$(grep "too many" "$LOGFILE" | tail -1)

    # Extract retry time
    RETRY_TIME=$(echo "$LATEST_ERROR" | grep -oP 'retry after \K[^,]*')

    # Send alert
    mail -s "ALERT: Let's Encrypt Rate Limit Hit" "$ALERT_EMAIL" <<EOF
Rate limit error detected at $(date)

Latest error:
$LATEST_ERROR

Retry after: $RETRY_TIME

Action Required:
1. Review recent certificate requests
2. Check for automation loops
3. Wait for rate limit reset
4. Consider using staging environment for testing

Logs: $LOGFILE
EOF
fi

Multi-Account Load Distribution

#!/bin/bash
# Distribute certificate requests across multiple accounts

ACCOUNTS=("account1" "account2" "account3")
DOMAINS=("app1.example.com" "app2.example.com" "app3.example.com" "app4.example.com")

# Round-robin distribution
for i in "${!DOMAINS[@]}"; do
    DOMAIN="${DOMAINS[$i]}"
    ACCOUNT="${ACCOUNTS[$((i % ${#ACCOUNTS[@]}))]}"

    echo "Requesting certificate for $DOMAIN using $ACCOUNT"
    certbot certonly --account "$ACCOUNT" \
        --webroot -w /var/www/html \
        -d "$DOMAIN"

    # Rate limit between requests
    sleep 2
done