npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

Iโ€™ve always been into building performant and accessible sites, but lately Iโ€™ve been taking it extremely seriously. So much so that Iโ€™ve been building a tool to help me optimize and monitor the sites that I build to make sure that Iโ€™m making an attempt to offer the best experience to those who visit them. If youโ€™re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, ๐Ÿ‘‹, Iโ€™m Ryan Hefnerย  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If youโ€™re interested in other things Iโ€™m working on, follow me on Twitter or check out the open source projects Iโ€™ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soonโ€“ish.

Open Software & Tools

This site wouldnโ€™t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you ๐Ÿ™

ยฉ 2025 โ€“ย Pkg Stats / Ryan Hefner

acme-love

v2.1.0

Published

๐Ÿ” Modern TypeScript ACME client & CLI for Let's Encrypt certificates. Supports DNS-01/HTTP-01 validation, wildcard domains, EAB (ZeroSSL/Google), nonce pooling. RFC 8555 compliant. Node.js 20+

Readme

๐Ÿ” ACME Love

Modern, stronglyโ€‘typed ACME (RFC 8555) toolkit for Node.js 20+

Powerful CLI tool + TypeScript library for Let's Encrypt and other ACME Certificate Authorities

NPM Version NPM License Tests Coverage

๐Ÿ“‹ Table of Contents

Main

โœจ Key Features

| Feature | Description | | ------------------------------- | --------------------------------------------------------------------- | | ๐Ÿ–ฅ๏ธ Powerful CLI | Interactive & non-interactive modes with polished prompts | | ๐ŸŒ Multi-Environment | Staging, production & custom directory endpoints | | ๐Ÿ”’ Challenge Support | DNS-01 (wildcard-friendly) & HTTP-01 with built-in validation helpers | | ๐Ÿ” Crypto Algorithms | ECDSA P-256 / P-384 / P-521 and RSA 2048 / 3072 / 4096 | | ๐Ÿ”‘ External Account Binding | EAB support (ZeroSSL, Google Trust Services, etc.) | | ๐Ÿ› ๏ธ Resilient Error Handling | RFC 8555 problem+json mapping, retry & maintenance detection | | โšก Optimized Core | Nonce pooling, FS read minimization, undici HTTP client | | ๐Ÿข Multiple CAs | Presets: Let's Encrypt, Buypass, Google, ZeroSSL + custom URLs | | ๐Ÿ“ฆ Typed API | Strong TypeScript types for orders, accounts, directory metadata | | ๐Ÿ” Diagnostics | Namespaced debug logging; stress & performance reports | | ๐Ÿงช Tested | Unit + stress tests; concurrency & rate limit scenarios | | ๐Ÿ”ง Developer Friendly | CSR helpers, validation utilities, composable modules |

๐Ÿš€ Quick Start

CLI Installation & Usage

๐Ÿ“– Full CLI Documentation: See CLI.md for complete usage guide, examples, and advanced features.

# Global installation (recommended)
npm install -g acme-love
acme-love --help

# Or use without installation
npx acme-love interactive --staging

๐ŸŽฎ Interactive Mode (Easiest Way)

# Start interactive mode with environment selection
acme-love interactive

# Or with pre-selected environment
acme-love interactive --staging    # For testing
acme-love interactive --production # For real certificates

๐Ÿ“‹ Command Line Mode

# Get a staging certificate (recommended first)
acme-love cert \
  --domain test.acme-love.com \
  --email [email protected] \
  --staging \
  --challenge dns-01

# Get a production certificate with custom algorithms
acme-love cert \
  --domain acme-love.com \
  --email [email protected] \
  --production \
  --challenge http-01 \
  --account-algo ec-p256 \
  --cert-algo rsa-4096

# Create an account key with specific algorithm
acme-love create-account-key \
  --algo ec-p384 \
  --output ./my-account-key.json

# Use External Account Binding for commercial CAs
acme-love cert \
  --domain acme-love.com \
  --email [email protected] \
  --directory https://acme.zerossl.com/v2/DV90 \
  --eab-kid "your-key-identifier" \
  --eab-hmac-key "your-base64url-hmac-key"

๐ŸŽฏ Challenge Types

DNS-01 Challenge (Recommended)

acme-love cert --challenge dns-01 --domain acme-love.com --email [email protected] --staging
  • โœ… Works with wildcard certificates (*.acme-love.com)
  • โœ… No need for public web server
  • ๐Ÿ”ง Requires DNS provider access

HTTP-01 Challenge

acme-love cert --challenge http-01 --domain acme-love.com --email [email protected] --staging
  • โœ… Simple validation via HTTP file
  • โœ… Automatic validation with built-in checker
  • ๐Ÿ”ง Requires domain to point to your web server

๐Ÿ” Cryptographic Algorithms

The CLI uses P-256 ECDSA by default for both account and certificate keys, providing an excellent balance of security and performance. This algorithm is:

  • โœ… Fast: Quicker than RSA for signing operations
  • โœ… Secure: 256-bit elliptic curve equivalent to 3072-bit RSA
  • โœ… Compatible: Widely supported by browsers and servers
  • โœ… Compact: Smaller key sizes and certificate files

For programmatic usage via the library, you can choose from multiple algorithms including different ECDSA curves (P-256, P-384, P-521) and RSA key sizes (2048, 3072, 4096 bits). See the Supported Cryptographic Algorithms section for details.

๐Ÿ› ๏ธ Development & Local Usage

If you're developing or testing locally, you have multiple convenient options:

# Development wrapper (auto-builds when needed)
./acme-love --help

# NPM scripts
npm run cli:help
npm run cli:staging
npm run cli:production

# Make commands
make help
make interactive
make staging

See CLI.md for complete CLI documentation and usage examples.

๐Ÿ“– CLI Commands Reference

| Command | Purpose | Algorithm Options | EAB Options | | -------------------- | ------------------------------ | ------------------------------------ | ------------------------------------------ | | cert | Obtain SSL certificate | --account-algo, --cert-algo | --eab-kid, --eab-hmac-key | | create-account-key | Generate ACME account key | --algo | - | | interactive | Interactive certificate wizard | Full interactive algorithm selection | Prompts for EAB when custom directory used |

Algorithm Values: ec-p256 (default), ec-p384, ec-p521, rsa-2048, rsa-3072, rsa-4096

EAB Options: --eab-kid <identifier>, --eab-hmac-key <base64url-key> for commercial CAs

Examples:

# Generate P-384 ECDSA account key
acme-love create-account-key --algo ec-p384 --output ./my-account.json

# Mixed algorithms: P-256 account, RSA-4096 certificate
acme-love cert --account-algo ec-p256 --cert-algo rsa-4096 --domain acme-love.com

# Interactive mode with full algorithm selection
acme-love interactive --staging

# Commercial CA with External Account Binding
acme-love cert \
  --domain acme-love.com \
  --directory https://acme.zerossl.com/v2/DV90 \
  --eab-kid "your-eab-key-id" \
  --eab-hmac-key "your-eab-hmac-key"

# Note: For known CAs, you can also use predefined providers in the library:
# AcmeClient(provider.zerossl.production) or AcmeClient(provider.google.production)

๐Ÿ“š Library Usage

๐Ÿ” Back to Top

Installation

npm install acme-love

Modern ACME Client

import { AcmeClient, AcmeAccount, provider, generateKeyPair, createAcmeCsr } from 'acme-love';

// 1. Create ACME client - using provider preset (recommended)
const client = new AcmeClient(provider.letsencrypt.staging, {
  nonce: { maxPool: 64 },
});

// Alternative: Create client with string URL
// const client = new AcmeClient(provider.letsencrypt.staging.directoryUrl, {
//   nonce: { maxPool: 64 },
// });

// 2. Generate account keys (P-256 ECDSA recommended)
const algo = { kind: 'ec', namedCurve: 'P-256', hash: 'SHA-256' };
const keyPair = await generateKeyPair(algo);
const accountKeys = {
  privateKey: keyPair.privateKey,
  publicKey: keyPair.publicKey,
};

// 3. Create account
const account = new AcmeAccount(client, accountKeys);

// 4. Register account
const registration = await account.register({
  contact: '[email protected]', // string or array
  termsOfServiceAgreed: true,
});

// 5. Create order and solve challenges
const order = await account.createOrder(['acme-love.com']);

// DNS-01 challenge
const ready = await account.solveDns01(order, {
  setDns: async (preparation) => {
    console.log(`Create TXT record: ${preparation.target} = ${preparation.value}`);
    // Set DNS record via your DNS provider API
    await waitForUserConfirmation();
  },
  waitFor: async (preparation) => {
    // Validate DNS propagation
    console.log('Validating DNS...');
  },
});

// 6. Generate CSR and finalize
const { derBase64Url, keys: csrKeys } = await createAcmeCsr(['acme-love.com'], algo);
const finalized = await account.finalize(ready, derBase64Url);
const valid = await account.waitOrder(finalized, ['valid']);
const certificate = await account.downloadCertificate(valid);

console.log('Certificate obtained!', certificate);

External Account Binding (EAB) Support

For Certificate Authorities that require External Account Binding (like ZeroSSL, Google Trust Services), provide EAB credentials during account registration:

import { AcmeClient, AcmeAccount, generateKeyPair, provider } from 'acme-love';

// Create client for CA that requires EAB - using provider preset (recommended)
const client = new AcmeClient(provider.zerossl.production);

// Alternative: Create client with string URL
// const client = new AcmeClient('https://acme.zerossl.com/v2/DV90');

// Generate account keys
const keyPair = await generateKeyPair({ kind: 'ec', namedCurve: 'P-256', hash: 'SHA-256' });
const accountKeys = {
  privateKey: keyPair.privateKey!,
  publicKey: keyPair.publicKey!,
};

// Create account with EAB
const account = new AcmeAccount(client, accountKeys, {
  externalAccountBinding: {
    kid: 'your-key-identifier-from-ca', // Provided by your CA
    hmacKey: 'your-base64url-hmac-key-from-ca', // Provided by your CA
  },
});

// Register account (EAB is handled automatically)
const registration = await account.register({
  contact: '[email protected]',
  termsOfServiceAgreed: true,
});

// Continue with normal certificate issuance...
const order = await account.createOrder(['acme-love.com']);
// ... rest of the flow

EAB-enabled Certificate Authorities:

  • ZeroSSL: Requires EAB for new account registration
  • Google Trust Services: Requires EAB for all accounts
  • Buypass: Optional EAB for enhanced validation
  • Custom/Enterprise CAs: Many require EAB for access control

Getting EAB Credentials:

  1. Register with your chosen CA's website
  2. Navigate to ACME/API settings in your account dashboard
  3. Generate or retrieve your EAB Key ID and HMAC Key
  4. Use these credentials in your ACME client configuration

CLI Usage with EAB:

# ZeroSSL example
acme-love cert \
  --domain acme-love.com \
  --eab-hmac-key "your-zerossl-hmac-key"

# Google Trust Services example
acme-love cert \
  --domain acme-love.com \
  --eab-hmac-key "your-google-hmac-key"

๐Ÿ“– Detailed EAB documentation: docs/EAB.md

Supported Cryptographic Algorithms

ACME Love supports multiple cryptographic algorithms for both account keys and certificate keys:

ECDSA (Elliptic Curve Digital Signature Algorithm)

  • P-256 (recommended) - Fast, secure, widely supported
  • P-384 - Higher security, larger keys
  • P-521 - Maximum security, largest keys
// P-256 (recommended for most cases)
const p256Algo = { kind: 'ec', namedCurve: 'P-256', hash: 'SHA-256' };

// P-384 for enhanced security
const p384Algo = { kind: 'ec', namedCurve: 'P-384', hash: 'SHA-384' };

// P-521 for maximum security
const p521Algo = { kind: 'ec', namedCurve: 'P-521', hash: 'SHA-512' };

RSA (Rivest-Shamir-Adleman)

  • 2048-bit - Minimum supported, fast
  • 3072-bit - Enhanced security
  • 4096-bit - Maximum security, slower
// RSA 2048-bit (minimum)
const rsa2048Algo = { kind: 'rsa', modulusLength: 2048, hash: 'SHA-256' };

// RSA 3072-bit (enhanced security)
const rsa3072Algo = { kind: 'rsa', modulusLength: 3072, hash: 'SHA-256' };

// RSA 4096-bit (maximum security)
const rsa4096Algo = { kind: 'rsa', modulusLength: 4096, hash: 'SHA-384' };

Algorithm Selection Guidelines

  • P-256 ECDSA: Default choice, excellent performance/security balance
  • P-384/P-521 ECDSA: For compliance requirements or enhanced security
  • RSA 2048: Legacy compatibility, larger certificate size
  • RSA 3072/4096: High-security environments, significantly slower

Important: You can use different algorithms for account keys and certificate keys independently. Account keys are used for ACME protocol authentication, while certificate keys are embedded in the final TLS certificate.

TypeScript Types: The library exports AcmeEcAlgorithm, AcmeRsaAlgorithm, and their union AcmeCertificateAlgorithm for type-safe algorithm specification:

import type { AcmeEcAlgorithm, AcmeRsaAlgorithm, AcmeCertificateAlgorithm } from 'acme-love';

// Type-safe algorithm definitions
const ecAlgo: AcmeEcAlgorithm = { kind: 'ec', namedCurve: 'P-256', hash: 'SHA-256' };
const rsaAlgo: AcmeRsaAlgorithm = { kind: 'rsa', modulusLength: 2048, hash: 'SHA-256' };
const algo: AcmeCertificateAlgorithm = ecAlgo; // Union type

Example: Different Algorithms for Account and Certificate

import { generateKeyPair, createAcmeCsr } from 'acme-love';

// Use P-256 for account keys (fast ACME protocol operations)
const accountAlgo = { kind: 'ec', namedCurve: 'P-256', hash: 'SHA-256' };
const accountKeys = await generateKeyPair(accountAlgo);

// Use P-384 for certificate keys (enhanced security in final certificate)
const certAlgo = { kind: 'ec', namedCurve: 'P-384', hash: 'SHA-384' };
const { derBase64Url, keys: certKeys } = await createAcmeCsr(['acme-love.com'], certAlgo);

// Or mix ECDSA and RSA
const accountAlgo = { kind: 'ec', namedCurve: 'P-256', hash: 'SHA-256' }; // Fast ECDSA for account
const certAlgo = { kind: 'rsa', modulusLength: 4096, hash: 'SHA-256' }; // RSA for certificate compatibility

Working with Existing Accounts

When you already have a registered ACME account, you can reuse it by providing the kid (Key ID) to avoid creating duplicate registrations:

// First time: Register new account and save the registration details
const account = new AcmeAccount(client, accountKeys);
const registration = await account.register({
  contact: '[email protected]',
  termsOfServiceAgreed: true,
});

// Save the account URL (kid) for future use
const accountUrl = registration.accountUrl;

// Save account info for future use
// Save account information for reuse
const accountInfo = {
  accountUrl, // This is the kid
  privateKey: await crypto.subtle.exportKey('jwk', accountKeys.privateKey),
  publicKey: await crypto.subtle.exportKey('jwk', accountKeys.publicKey),
};
await fs.writeFile('account.json', JSON.stringify(accountInfo, null, 2));
// Later: Load existing account with kid
const savedAccount = JSON.parse(await fs.readFile('account.json', 'utf8'));
const accountKeys = {
  privateKey: await crypto.subtle.importKey(
    'jwk',
    savedAccount.privateKey,
    { name: 'ECDSA', namedCurve: 'P-256' },
    false,
    ['sign'],
  ),
  publicKey: await crypto.subtle.importKey(
    'jwk',
    savedAccount.publicKey,
    { name: 'ECDSA', namedCurve: 'P-256' },
    false,
    ['verify'],
  ),
};

// Create account with existing account URL (kid)
const account = new AcmeAccount(client, accountKeys, {
  kid: savedAccount.accountUrl, // Use existing account URL
});

// No need to call register() - account already exists
const order = await account.createOrder(['acme-love.com']);

Advanced Features

Comprehensive Error Handling

ACME Love provides detailed error types for precise error handling in your applications. All ACME-specific errors extend the base AcmeError class and follow RFC 8555 error specifications.

ACME Protocol Errors

import {
  AcmeError,
  ServerMaintenanceError,
  RateLimitedError,
  BadNonceError,
  AccountDoesNotExistError,
  OrderNotReadyError,
  ExternalAccountRequiredError,
  RateLimitError, // From rate limiter
} from 'acme-love';

try {
  await account.createOrder(['acme-love.com']);
} catch (error) {
  // Server maintenance detection
  if (error instanceof ServerMaintenanceError) {
    console.log('๐Ÿ”ง Service is under maintenance');
    console.log('๐Ÿ“Š Check https://letsencrypt.status.io/');
    console.log('โณ Please try again later when the service is restored.');
    return;
  }

  // Rate limiting with automatic retry information
  if (error instanceof RateLimitedError) {
    const retrySeconds = error.getRetryAfterSeconds();
    console.log(`โฑ๏ธ Rate limited. Retry in ${retrySeconds} seconds`);
    console.log(`๐Ÿ“ Details: ${error.detail}`);

    // Wait and retry automatically
    if (retrySeconds && retrySeconds < 300) {
      // Max 5 minutes
      await new Promise((resolve) => setTimeout(resolve, retrySeconds * 1000));
      // Retry the operation...
    }
    return;
  }

  // Nonce issues (automatically handled by nonce manager)
  if (error instanceof BadNonceError) {
    console.log('๐Ÿ”„ Invalid nonce - this should be handled automatically');
    // The NonceManager typically retries these automatically
  }

  // Account-related errors
  if (error instanceof AccountDoesNotExistError) {
    console.log('๐Ÿ‘ค Account does not exist - need to register first');
  }

  // Order state errors
  if (error instanceof OrderNotReadyError) {
    console.log('๐Ÿ“‹ Order not ready for finalization - complete challenges first');
  }

  // EAB requirement
  if (error instanceof ExternalAccountRequiredError) {
    console.log('๐Ÿ”‘ This CA requires External Account Binding (EAB)');
    console.log('๐Ÿ’ก Use --eab-hmac-key option or provide EAB credentials');
  }

  // Rate limiter errors (from internal rate limiting system)
  if (error instanceof RateLimitError) {
    console.log(`๐Ÿšฆ Internal rate limit: ${error.message}`);
    console.log(`๐Ÿ“Š Attempts: ${error.rateLimitInfo.attempts}`);
    console.log(`โณ Retry in ${error.rateLimitInfo.retryDelaySeconds}s`);
  }

  // Generic ACME error with details
  if (error instanceof AcmeError) {
    console.log(`โŒ ACME Error: ${error.detail}`);
    console.log(`๐Ÿ” Type: ${error.type}`);
    console.log(`๐Ÿ“Š Status: ${error.status}`);

    // Handle subproblems for compound errors
    if (error.subproblems?.length) {
      console.log('๐Ÿ“‹ Subproblems:');
      error.subproblems.forEach((sub, i) => {
        console.log(`  ${i + 1}. ${sub.detail} (${sub.type})`);
      });
    }
  }
}

Complete Error Types Reference

Account & Registration Errors:

  • AccountDoesNotExistError - Account doesn't exist, need to register
  • ExternalAccountRequiredError - CA requires EAB for registration
  • InvalidContactError - Invalid contact information (email format, etc.)
  • UnsupportedContactError - Unsupported contact protocol scheme

Authentication & Authorization Errors:

  • BadNonceError - Invalid anti-replay nonce (auto-retried by NonceManager)
  • BadPublicKeyError - Unsupported public key algorithm
  • BadSignatureAlgorithmError - Unsupported signature algorithm
  • UnauthorizedError - Insufficient authorization for requested action
  • UserActionRequiredError - Manual action required (visit instance URL)

Certificate & CSR Errors:

  • BadCSRError - Invalid Certificate Signing Request
  • BadRevocationReasonError - Invalid revocation reason provided
  • AlreadyRevokedError - Certificate already revoked
  • OrderNotReadyError - Order not ready for finalization
  • RejectedIdentifierError - Server won't issue for this identifier

Validation Errors:

  • CAAError - CAA DNS records forbid certificate issuance
  • ConnectionError - Can't connect to validation target
  • DNSError - DNS resolution problems during validation
  • IncorrectResponseError - Challenge response doesn't match requirements
  • TLSError - TLS issues during validation
  • UnsupportedIdentifierError - Unsupported identifier type

Server & Network Errors:

  • ServerInternalError - Internal server error (500)
  • ServerMaintenanceError - Service under maintenance (503)
  • RateLimitedError - ACME rate limit exceeded with retry info
  • MalformedError - Malformed request message
  • CompoundError - Multiple errors (check subproblems array)

Rate Limiting Errors:

  • RateLimitError - Internal rate limiter exceeded retry attempts

Error Handling Patterns

Pattern 1: Graceful Degradation

async function createCertificateWithFallback(domain: string) {
  try {
    return await createCertificate(domain);
  } catch (error) {
    if (error instanceof RateLimitedError) {
      // Wait and retry once
      const retrySeconds = error.getRetryAfterSeconds();
      if (retrySeconds && retrySeconds < 3600) {
        // Max 1 hour
        await new Promise((resolve) => setTimeout(resolve, retrySeconds * 1000));
        return await createCertificate(domain);
      }
    }

    if (error instanceof ServerMaintenanceError) {
      // Try staging environment as fallback
      return await createCertificateStaging(domain);
    }

    throw error; // Re-throw unhandled errors
  }
}

Pattern 2: Retry with Exponential Backoff

async function robustCertificateCreation(domain: string, maxRetries = 3) {
  let attempt = 0;

  while (attempt < maxRetries) {
    try {
      return await createCertificate(domain);
    } catch (error) {
      attempt++;

      if (error instanceof BadNonceError && attempt < maxRetries) {
        // Exponential backoff for nonce errors
        const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
        await new Promise((resolve) => setTimeout(resolve, delay));
        continue;
      }

      if (error instanceof RateLimitedError) {
        const retrySeconds = error.getRetryAfterSeconds();
        if (retrySeconds && retrySeconds < 1800) {
          // Max 30 minutes
          await new Promise((resolve) => setTimeout(resolve, retrySeconds * 1000));
          continue;
        }
      }

      // Don't retry these errors
      if (
        error instanceof ExternalAccountRequiredError ||
        error instanceof BadCSRError ||
        error instanceof RejectedIdentifierError
      ) {
        throw error;
      }

      if (attempt >= maxRetries) throw error;
    }
  }
}

Pattern 3: Error Categorization

function categorizeError(error: unknown): 'retry' | 'reconfigure' | 'fatal' {
  if (
    error instanceof RateLimitedError ||
    error instanceof BadNonceError ||
    error instanceof ServerMaintenanceError ||
    error instanceof ConnectionError
  ) {
    return 'retry';
  }

  if (
    error instanceof ExternalAccountRequiredError ||
    error instanceof BadCSRError ||
    error instanceof InvalidContactError ||
    error instanceof UnsupportedContactError
  ) {
    return 'reconfigure';
  }

  return 'fatal';
}

async function handleCertificateError(error: unknown) {
  const category = categorizeError(error);

  switch (category) {
    case 'retry':
      console.log('โณ Temporary issue - will retry automatically');
      break;
    case 'reconfigure':
      console.log('โš™๏ธ Configuration issue - please check your settings');
      break;
    case 'fatal':
      console.log('โŒ Fatal error - manual intervention required');
      break;
  }
}

JSON Serialization

All ACME errors support JSON serialization for logging and debugging:

try {
  await acct.createOrder(['acme-love.com']);
} catch (error) {
  if (error instanceof AcmeError) {
    // Structured error logging
    const errorData = error.toJSON();
    console.log('ACME Error Details:', JSON.stringify(errorData, null, 2));

    // Send to monitoring system
    await sendToMonitoring({
      event: 'acme_error',
      error: errorData,
      timestamp: new Date().toISOString(),
    });
  }
}

HTTP-01 Challenge with Validation

const ready = await acct.solveHttp01(order, {
  setHttp: async (preparation) => {
    // Serve challenge at: preparation.target
    // Content: preparation.value
    console.log(`Serve ${preparation.value} at ${preparation.target}`);
  },
  waitFor: async (preparation) => {
    // Built-in HTTP validator
    const { validateHttp01ChallengeByUrl } = await import('acme-love/validator');
    const result = await validateHttp01ChallengeByUrl(preparation.target, preparation.value);
    if (!result.ok) throw new Error('HTTP validation failed');
  },
});

Custom Cryptographic Algorithms

import { generateKeyPair, createAcmeCsr } from 'acme-love';

// High-security setup: P-521 for account, RSA-4096 for certificate
const accountAlgo = { kind: 'ec', namedCurve: 'P-521', hash: 'SHA-512' };
const certAlgo = { kind: 'rsa', modulusLength: 4096, hash: 'SHA-384' };

const accountKeys = await generateKeyPair(accountAlgo);
const { derBase64Url, keys: certKeys } = await createAcmeCsr(['acme-love.com'], certAlgo);

// Performance-optimized setup: P-256 for both
const fastAlgo = { kind: 'ec', namedCurve: 'P-256', hash: 'SHA-256' };
const accountKeys = await generateKeyPair(fastAlgo);
const { derBase64Url } = await createAcmeCsr(['acme-love.com'], fastAlgo);

Debug Logging for Library Usage

import { enableDebug, debugNonce, debugHttp } from 'acme-love';

// Enable debug for development
enableDebug();

// Use specific debug loggers in your code
debugNonce('Custom nonce debug message');
debugHttp('HTTP operation debug info');

โšก Nonce Management

๐Ÿ” Back to Top

ACME Love includes a sophisticated NonceManager that optimizes nonce handling for high-performance certificate operations. Nonces are automatically pooled, prefetched, and recycled to minimize network round-trips.

Global Configuration

Set default nonce behavior for all accounts:

const client = new AcmeClient(provider.letsencrypt.production, {
  nonce: {
    maxPool: 64, // Cache up to 64 nonces
    prefetchLowWater: 12, // Start prefetch when < 12 remain
    prefetchHighWater: 40, // Fill up to 40 nonces
    maxAgeMs: 5 * 60_000, // Expire after 5 minutes
    // Note: Logging is handled by unified debug system (DEBUG=acme-love:nonce)
  },
});

Per-Account Overrides

Fine-tune nonce behavior for specific accounts:

const account = new AcmeAccount(client, accountKeys, {
  nonceOverrides: {
    maxPool: 128, // Higher throughput for this account
    prefetchLowWater: 20,
    prefetchHighWater: 80,
    // Note: Logging is handled by unified debug system (DEBUG=acme-love:nonce)
  },
});

Configuration Options

| Option | Default | Description | | ------------------- | ------- | -------------------------------------------------- | | maxPool | 32 | Maximum cached nonces per namespace | | prefetchLowWater | 0 | Start prefetch when pool below this (0 = disabled) | | prefetchHighWater | 0 | Target fill level for prefetch | | maxAgeMs | 300000 | Discard nonces older than 5 minutes |

Note: Logging is now handled by the unified debug system. Use DEBUG=acme-love:nonce to enable nonce manager debug output.

Performance Scenarios

// Low traffic / sequential operations
{ prefetchLowWater: 0 }  // Disable prefetch

// Moderate parallelism (5-10 concurrent operations)
{ prefetchLowWater: 4, prefetchHighWater: 12, maxPool: 32 }

// High burst / parallel workloads
{ prefetchLowWater: 16, prefetchHighWater: 48, maxPool: 128 }

The NonceManager automatically handles badNonce retries, harvests nonces from response headers, and isolates nonce pools per CA/account combination.

Debug Logging

ACME Love uses the debug module for structured logging. Debug output is automatically disabled in production unless explicitly enabled:

# Enable all ACME Love debug output
DEBUG=acme-love:* node your-app.js

# Enable only nonce manager debug
DEBUG=acme-love:nonce node your-app.js

# Enable multiple specific components
DEBUG=acme-love:nonce,acme-love:http node your-app.js

# Available debug namespaces:
# acme-love:nonce    - Nonce management operations
# acme-love:http     - HTTP requests and responses
# acme-love:challenge - Challenge solving process
# acme-love:client   - Core client operations
# acme-love:validator - Validation functions
// Programmatic debug control
import { enableDebug, disableDebug, isDebugEnabled } from 'acme-love';

// Enable debug programmatically (useful for development)
enableDebug();

// Disable debug programmatically (useful for production)
disableDebug();

// Check if debug is enabled
if (isDebugEnabled()) {
  console.log('Debug logging is active');
}

Custom Nonce Manager Logging

In previous versions, NonceManager accepted a custom log function for debugging. This has been replaced with the unified debug system. If you need custom logging behavior for nonce operations, you can intercept the debug output:

// New unified approach:
import debug from 'debug';

// Override the nonce debug function for custom formatting
const originalNonceDebug = debug('acme-love:nonce');
debug.enabled = () => true; // Force enable for custom handler

// Custom nonce logging with your preferred logger
const customNonceLogger = (...args: any[]) => {
  logger.info('[nonce]', ...args); // Your custom logger
  originalNonceDebug(...args); // Still call original if needed
};

// Replace the debug function globally
debug('acme-love:nonce').log = customNonceLogger;

๐Ÿ“– Detailed documentation: docs/NONCE-MANAGER.md

๐Ÿ” Advanced Validators & Utilities

DNS Validation Functions

For advanced DNS-01 challenge handling, ACME Love provides powerful DNS validation utilities:

import {
  resolveAndValidateAcmeTxtAuthoritative,
  resolveAndValidateAcmeTxt,
  resolveNsToIPs,
  findZoneWithNs,
} from 'acme-love/validator';

// Authoritative DNS validation (queries actual NS servers)
const result = await resolveAndValidateAcmeTxtAuthoritative(
  '_acme-challenge.acme-love.com',
  'expected-challenge-value',
);

if (result.ok) {
  console.log('โœ… DNS challenge validated');
} else {
  console.log('โŒ DNS validation failed:', result.error);
}

// Standard DNS validation with fallback to public resolvers
const quickResult = await resolveAndValidateAcmeTxt(
  '_acme-challenge.acme-love.com',
  'expected-challenge-value',
);

HTTP Validation Functions

import { validateHttp01ChallengeByUrl, validateHttp01Challenge } from 'acme-love/validator';

// Direct URL validation
const result = await validateHttp01ChallengeByUrl(
  'http://acme-love.com/.well-known/acme-challenge/token123',
  'expected-key-authorization',
);

// Domain + token validation
const result2 = await validateHttp01Challenge(
  'acme-love.com',
  'token123',
  'expected-key-authorization',
);

CLI Configuration Details

When using the CLI, ACME Love automatically handles file organization and configuration:

# Default directory structure created by CLI
./certificates/
โ”œโ”€โ”€ acme-love.com/
โ”‚   โ”œโ”€โ”€ cert.pem          # Certificate chain
โ”‚   โ”œโ”€โ”€ cert-key.json     # Certificate private key (JWK format)
โ”‚   โ”œโ”€โ”€ cert.csr.pem      # Certificate signing request
โ”‚   โ””โ”€โ”€ order.json        # ACME order details
โ””โ”€โ”€ account-key.json      # ACME account keys (JWK format)

CLI Configuration Defaults:

  • Output directory: ./certificates/
  • Account key path: ./certificates/account-key.json
  • Nonce pool size: 64 (optimized for CLI operations)
  • Key algorithm: ECDSA P-256 (ES256)
  • File format: JWK for keys, PEM for certificates

๐Ÿ”ง CSR Generation

The createAcmeCsr helper generates everything needed for certificate finalization:

import { createAcmeCsr } from 'acme-love';

const { pem, derBase64Url, keys } = await createAcmeCsr(
  ['acme-love.com', 'www.acme-love.com'], // domains (first = CN)
  { kind: 'ec', namedCurve: 'P-256', hash: 'SHA-256' },
);

// pem: PEM-encoded CSR for storage
// derBase64Url: base64url DER for ACME finalize
// keys: Certificate private/public key pair

Supported Cryptographic Algorithms

ACME Love supports modern cryptographic algorithms via WebCrypto API:

import { generateKeyPair, type AcmeCertificateAlgorithm } from 'acme-love';

// ECDSA with P-256 curve (Recommended - smaller keys, faster)
const ecAlgo: AcmeCertificateAlgorithm = {
  kind: 'ec',
  namedCurve: 'P-256',
  hash: 'SHA-256',
};

// ECDSA with P-384 curve (Higher security)
const ec384Algo: AcmeCertificateAlgorithm = {
  kind: 'ec',
  namedCurve: 'P-384',
  hash: 'SHA-384',
};

// RSA 2048-bit (Widely compatible, larger keys)
const rsaAlgo: AcmeCertificateAlgorithm = {
  kind: 'rsa',
  modulusLength: 2048,
  hash: 'SHA-256',
};

// RSA 4096-bit (Maximum security, slower)
const rsa4096Algo: AcmeCertificateAlgorithm = {
  kind: 'rsa',
  modulusLength: 4096,
  hash: 'SHA-256',
};

// Generate key pair with chosen algorithm
const keyPair = await generateKeyPair(ecAlgo);

Algorithm Recommendations:

  • ECDSA P-256: Best balance of security, performance, and compatibility
  • ECDSA P-384: For applications requiring higher security
  • RSA 2048: For maximum compatibility with older systems
  • RSA 4096: For maximum security (slower operations)

Key Storage Formats:

  • JWK (JSON Web Key): Used for account keys and internal storage
  • PEM: Standard format for certificate keys and CSRs
  • DER: Binary format for ACME protocol communication

๐Ÿข Supported ACME Providers

Built-in directory presets for major Certificate Authorities:

import { provider } from 'acme-love';

// Let's Encrypt (No EAB required)
provider.letsencrypt.staging.directoryUrl;
provider.letsencrypt.production.directoryUrl;

// Buypass (EAB optional)
provider.buypass.staging.directoryUrl;
provider.buypass.production.directoryUrl;

// Google Trust Services (EAB required)
provider.google.staging.directoryUrl;
provider.google.production.directoryUrl;

// ZeroSSL (EAB required for new accounts)
provider.zerossl.production.directoryUrl;

EAB Requirements by Provider:

| Provider | EAB Required | Notes | | --------------------- | ------------ | --------------------------------------- | | Let's Encrypt | โŒ No | Free, automatic registration | | Buypass | โš ๏ธ Optional | Enhanced validation with EAB | | Google Trust Services | โœ… Required | Commercial service, register for access | | ZeroSSL | โœ… Required | Free tier available, EAB mandatory |

Use --eab-kid and --eab-hmac-key CLI options or the eab parameter in ensureRegistered() for providers that require External Account Binding.

๐Ÿ”ง Client Initialization

ACME Love supports two convenient ways to initialize the AcmeClient:

Method 1: Using Provider Presets (Recommended)

import { AcmeClient, provider } from 'acme-love';

// Using predefined provider entries (recommended)
const client = new AcmeClient(provider.letsencrypt.staging);
const client2 = new AcmeClient(provider.google.production);
const client3 = new AcmeClient(provider.zerossl.production);

// With configuration options
const client4 = new AcmeClient(provider.letsencrypt.production, {
  nonce: { maxPool: 64 },
});

Method 2: Using String URLs

import { AcmeClient } from 'acme-love';

// Using string URLs directly
const client = new AcmeClient('https://acme-staging-v02.api.letsencrypt.org/directory');
const client2 = new AcmeClient('https://dv.acme-v02.api.pki.goog/directory');

// Custom ACME directory
const client3 = new AcmeClient('https://my-custom-ca.com/acme/directory');

Benefits of Provider Presets

โœ… Type Safety: Full TypeScript support with autocomplete โœ… Validation: Pre-validated directory URLs โœ… Convenience: No need to remember complex URLs โœ… Consistency: Standardized configuration across projects โœ… Updates: Automatic URL updates with library updates

Recommendation: Use provider presets for standard CAs (Let's Encrypt, Google, etc.) and string URLs for custom or enterprise ACME directories.

๐ŸŽจ CLI Features Showcase

Beautiful Interactive Prompts

  • ๐ŸŽฎ Full interactive mode with guided setup
  • ๐ŸŒˆ Colorful, emoji-rich interface using @inquirer/prompts
  • ๐Ÿ”ง Environment selection (staging/production/custom)
  • ๐Ÿ“ Challenge type selection (DNS-01/HTTP-01)

Smart Error Handling

  • ๐Ÿ”ง Maintenance detection with helpful messages
  • ๐Ÿ“Š Links to service status pages
  • ๐Ÿ’ก User-friendly error explanations
  • ๐Ÿšจ Proper exit codes

Automatic Validation

  • ๐Ÿ” DNS record verification with authoritative lookups
  • ๐ŸŒ HTTP challenge validation with undici
  • โณ Retry logic with progress indicators
  • โœ… Success confirmation

๐Ÿ“– Documentation

๐Ÿ”ง Troubleshooting

Common Issues

WebCrypto API Errors

# Error: crypto.subtle is undefined
# Solution: Ensure Node.js โ‰ฅ 20 and secure context (HTTPS/localhost)

JWK Import/Export Issues

// โŒ Wrong algorithm specification
await crypto.subtle.importKey('jwk', jwkData, { name: 'ECDSA' }, false, ['sign']);

// โœ… Correct algorithm with namedCurve
await crypto.subtle.importKey('jwk', jwkData, { name: 'ECDSA', namedCurve: 'P-256' }, false, [
  'sign',
]);

Debug Logging Control

# Production: Debug disabled by default (no DEBUG environment variable)
node your-app.js

# Development: Enable specific debug output
DEBUG=acme-love:nonce node your-app.js

# Troubleshooting: Enable all debug output
DEBUG=acme-love:* node your-app.js
// Programmatic control for tests or specific environments
import { disableDebug } from 'acme-love';

// Ensure clean logs in production
if (process.env.NODE_ENV === 'production') {
  disableDebug();
}

DNS Challenge Validation Timeout

# If DNS propagation is slow, increase timeout in your waitFor function
await new Promise(resolve => setTimeout(resolve, 30000));  // Wait 30s

File Permission Errors

# Ensure write permissions for certificate output directory
chmod 755 ./certificates/

โšก Requirements

  • Node.js โ‰ฅ 20 (WebCrypto, modern URL, base64url support)
  • TypeScript โ‰ฅ 5 (for development)

๐Ÿš€ Performance & Stress Testing

๐Ÿ” Back to Top

ACME Love undergoes regular stress tests (Let's Encrypt staging) across multiple load tiers. Below are the latest consolidated results pulled from the stress report artifacts (Quick / Standard / Heavy). They demonstrate scalability, nonceโ€‘pool efficiency, and stability as order volume increases.

๐Ÿ”ข Consolidated Metrics (Latest Run)

| Tier | Accounts | Orders/Acc | Total Orders | Total Time | Avg Resp | P50 | P95 | P99 | Requests | Req/s | Orders/s | Success | New-Nonce | Nonce Eff. | Saved Req. | | ------------ | -------- | ---------- | ------------ | ---------- | -------- | --- | ---- | ---- | -------- | ----- | -------- | ------- | --------- | ---------- | ---------- | | Quick | 2 | 20 | 40 | 6.16s | 346ms | 239 | 741 | 821 | 93 | 15.1 | 6.50 | 100% | 13 | 86% | 80 | | Standard | 4 | 50 | 200 | 10.95s | 451ms | 250 | 1283 | 1630 | 450 | 41.1 | 18.27 | 100% | 50 | 89% | 400 | | Heavy | 4 | 200 | 800 | 32.79s | 494ms | 246 | 1338 | 1554 | 1652 | 50.4 | 24.40 | 100% | 52 | 97% | 1600 |

All tiers achieved 100% success. Heavy test maintains <500ms average latency and high nonce pool efficiency (97%).

๐Ÿงช Interpretation

  • 100% Success Rate: All test scenarios (Quick, Standard, Heavy) completed without errors.
  • Stable Performance: Average response time is <500ms even under heavy load (Heavy: 494ms, Standard: 451ms, Quick: 346ms).
  • Scalability: Requests/sec and Orders/sec increase with load, latency does not degrade.
  • Nonce Efficiency: Savings on new-nonce requests up to 97% (Heavy), significantly reducing load on the CA.
  • No Errors: Error Rate = 0% in all tests.
  • Good tail latency: p99 latency <1600ms even in Heavy tier, no abnormally slow requests.
  • Resource margin: The system handles increased load without loss of stability or efficiency.

โš™๏ธ Key Optimizations

  • Rate limiting + exponential backoff (automatic HTTP 503 / Retry-After handling)
  • Highโ€‘efficiency nonce pool (dynamic refill + minimal new-nonce calls)
  • Request coalescing & HTTP connection reuse
  • Structured debug logging (HTTP + nonce) via DEBUG env

๐Ÿ” Example High-Load Configuration

const client = new AcmeClient(directoryUrl, {
  nonce: {
    maxPool: 64,
    prefetchLowWater: 8,
    prefetchHighWater: 32,
  },
  // Optional: tune timeouts / retry strategies as needed
});

๐Ÿ“ˆ Detailed Reports

Comprehensive performance & reliability artifacts (humanโ€‘readable Markdown + machineโ€‘readable JSON):

Primary stress tiers:

Each stress test report includes: latency distribution (P50/P75/P90/P95/P99), throughput, nonce efficiency, savings, threshold matrix, environment metadata (git commit, Node version), and raw config for reproducibility.

๐Ÿƒ Running the Tests

Test Types

  • ๐Ÿ”ฌ Unit Tests: Mock-based testing of individual components
  • ๐ŸŒ Integration Tests: Real requests to Let's Encrypt staging
  • โšก Async Behavior Tests: Concurrent operations and memory leak prevention
  • ๐Ÿ”„ E2E Tests: Full workflow testing with staging environment
  • ๐Ÿš€ Stress Tests: High-volume production scenario validation (run separately)

# Run standard test suite (fast, excludes stress tests)
npm test

# Run specific test types
npm run test:unit           # Unit tests only (skips e2e & stress)
npm run test:e2e            # End-to-end tests (Let's Encrypt staging)
npm run test:e2e:ci         # E2E for CI (requires ACME_E2E_ENABLED=1)

# Run with coverage report
npm run test:coverage

# Focused component tests
npm run test:nonce-manager  # Nonce manager focused tests (in-band)
npm run test:rate-limiting  # Rate limiting behavior & backoff
npm run test:deadlock       # Deadlock detection / concurrency safety

# Stress tiers (instrumented performance runs)
npm run test:quick          # Quick tier (2 ร— 20 orders) ~7s
npm run test:standard       # Standard tier (4 ร— 50 orders) ~16s
npm run test:heavy          # Heavy tier (4 ร— 200 orders) ~60s

npm run test:deadlock       # Deadlock detection
# Full suite
npm run test:all            # EVERYTHING (includes stress) โ€“ slower

โœ… Resolution:

  • Implemented comprehensive rate limiting system with HTTP 503 detection
  • Added exponential backoff with Retry-After header support
  • Optimized nonce pool management with 98% efficiency
  • Unified debug logging system with printf-style formatting
  • Latest test results: 4 accounts ร— 200 orders = 800 operations completed successfully in 32 seconds

Current Status:

  • โœ… 100% success rate in heavy stress testing
  • โœ… Zero rate limit violations with automatic backoff
  • โœ… Production-ready performance (25+ req/s sustained)
  • โœ… Enterprise-scale validation (400 concurrent operations)
  • โœ… 10x Performance Improvement: Tests complete in 30-35s vs 5-10 minutes

๐Ÿงช Test Coverage

๐Ÿ” Back to Top

ACME Love maintains comprehensive test coverage to ensure reliability and quality:

Test Statistics

  • โœ… 60 Tests across 18 test suites
  • โœ… 100% Passing test rate
  • ๐Ÿ“Š Core Components Coverage:
    • csr.ts: 94.11% (cryptographic operations)
    • nonce-manager.ts: 68.03% (pooling & concurrent access)
    • acme-directory.ts: 83.33% (directory operations)
    • acme-client-core.ts: 93.75% (core client functionality)

Note: Stress tests are excluded from the default npm test command to keep CI/CD pipelines fast. They should be run manually or in dedicated test environments.

๐Ÿ”‘ Test Account Management

ACME Love includes a sophisticated test account management system to avoid Let's Encrypt rate limits during development and testing:

# Prepare accounts for all stress tests (run once)
npm run accounts prepare-stress

# List all test accounts
npm run accounts list

# Create specific account
npm run accounts create my-test-account

# Clean up old accounts (older than 24 hours)
npm run accounts cleanup 24

Benefits:

  • โœ… Avoid Let's Encrypt's 50 registrations per IP per 3 hours limit
  • โœ… Faster test execution (reuse existing accounts)
  • โœ… Isolated accounts per test type
  • โœ… Automatic git ignore protection

๐Ÿ“‹ Detailed account management guide: TEST-ACCOUNT-MANAGEMENT.md

๐Ÿ“„ License

ISC License - see LICENSE file for details.

๐Ÿค Contributing

Contributions are welcome! Please read our contributing guidelines and submit pull requests.


Made with โค๏ธ for the Node.js community

๐Ÿ” Back to Top | Report Issues | Request Features