smartproxy/docs/acme-timing-fix.md

3.2 KiB

ACME Certificate Provisioning Timing Fix (v19.3.9)

Problem Description

In SmartProxy v19.3.8 and earlier, ACME certificate provisioning would start immediately during SmartProxy initialization, before the required ports were actually listening. This caused ACME HTTP-01 challenges to fail because the challenge port (typically port 80) was not ready to accept connections when Let's Encrypt tried to validate the challenge.

Root Cause

The certificate manager was initialized and immediately started provisioning certificates as part of the SmartProxy startup sequence:

  1. SmartProxy.start() called
  2. Certificate manager initialized
  3. Certificate provisioning started immediately (including ACME challenges)
  4. Port listeners started afterwards
  5. ACME challenges would fail because port 80 wasn't listening yet

This race condition meant that when Let's Encrypt tried to connect to port 80 to validate the HTTP-01 challenge, the connection would be refused.

Solution

The fix defers certificate provisioning until after all ports are listening and ready:

Changes to SmartCertManager

// Modified initialize() to skip automatic provisioning
public async initialize(): Promise<void> {
  // ... initialization code ...
  
  // Skip automatic certificate provisioning during initialization
  console.log('Certificate manager initialized. Deferring certificate provisioning until after ports are listening.');
  
  // Start renewal timer
  this.startRenewalTimer();
}

// Made provisionAllCertificates public to allow direct calling after ports are ready
public async provisionAllCertificates(): Promise<void> {
  // ... certificate provisioning code ...
}

Changes to SmartProxy

public async start() {
  // ... initialization code ...
  
  // Start port listeners using the PortManager
  await this.portManager.addPorts(listeningPorts);
  
  // Now that ports are listening, provision any required certificates
  if (this.certManager) {
    console.log('Starting certificate provisioning now that ports are ready');
    await this.certManager.provisionAllCertificates();
  }
  
  // ... rest of startup code ...
}

Timing Sequence

Before (v19.3.8 and earlier)

  1. Initialize certificate manager
  2. Start ACME provisioning immediately
  3. ACME challenge fails (port not ready)
  4. Start port listeners
  5. Port 80 now listening (too late)

After (v19.3.9)

  1. Initialize certificate manager (provisioning deferred)
  2. Start port listeners
  3. Port 80 now listening
  4. Start ACME provisioning
  5. ACME challenge succeeds

Configuration

No configuration changes are required. The timing fix is automatic and transparent to users.

Testing

The fix is verified by the test in test/test.acme-timing-simple.ts which ensures:

  1. Certificate manager is initialized first
  2. Ports start listening
  3. Certificate provisioning happens only after ports are ready

Impact

This fix ensures that:

  • ACME HTTP-01 challenges succeed on first attempt
  • No more "connection refused" errors during certificate provisioning
  • Certificate acquisition is more reliable
  • No manual retries needed for failed challenges

Migration

Simply update to SmartProxy v19.3.9 or later. The fix is backward compatible and requires no changes to existing code or configuration.