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:
- Repeated certificate requests during troubleshooting
- Reinstalling ACME client or deleting configuration data
- Testing configuration changes with production certificates
- 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:
- Bulk certificate operations without rate limiting
- Automated renewal scripts running too frequently
- Multiple services sharing the same ACME account
- 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:
- Large-scale certificate deployment or migration
- Multiple subdomains requiring individual certificates
- Development/testing consuming production quota
- 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:
Details:
- Limit: 5 authorization failures per identifier per account per hour
- Refill Rate: 1 failure per 12 minutes
- Override Available: No
Root Causes:
- Misconfigured DNS records
- Firewall blocking validation requests
- Web server configuration issues
- 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:
Details:
- Limit: 1,152 consecutive authorization failures
- Recovery: Resets after 1 successful authorization
- Override Available: No
Root Causes:
- Persistent configuration problems causing repeated failures
- Automated renewal systems with systemic issues
- 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:
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
Related Documentation
- Rate Limiting Overview - Core concepts and quick reference
- Rate Limiting API Reference - Endpoint limits and token bucket details
- Rate Limiting Commands - Command-line usage and monitoring
- Certificate Lifecycle Management - Automated renewal strategies
- HTTP-01 Challenge Troubleshooting - Authorization failure diagnosis
- DNS-01 Challenge Validation - Alternative validation method