smartproxy/docs/certificate-management.md

9.3 KiB

Certificate Management in SmartProxy v19+

Overview

SmartProxy v19+ enhances certificate management with support for both global and route-level ACME configuration. This guide covers the updated certificate management system, which now supports flexible configuration hierarchies.

Key Changes from Previous Versions

v19.0.0 Changes

  • Global ACME configuration: Set default ACME settings for all routes with certificate: 'auto'
  • Configuration hierarchy: Top-level ACME settings serve as defaults, route-level settings override
  • Better error messages: Clear guidance when ACME configuration is missing
  • Improved validation: Configuration validation warns about common issues

v18.0.0 Changes (from v17)

  • No backward compatibility: Clean break from the legacy certificate system
  • No separate Port80Handler: ACME challenges handled as regular SmartProxy routes
  • Unified route-based configuration: Certificates configured directly in route definitions
  • Direct integration with @push.rocks/smartacme: Leverages SmartAcme's built-in capabilities

Configuration

Global ACME Configuration (New in v19+)

Set default ACME settings at the top level that apply to all routes with certificate: 'auto':

const proxy = new SmartProxy({
  // Global ACME defaults
  acme: {
    email: 'ssl@example.com',         // Required for Let's Encrypt
    useProduction: false,             // Use staging by default
    port: 80,                         // Port for HTTP-01 challenges
    renewThresholdDays: 30,           // Renew 30 days before expiry
    certificateStore: './certs',      // Certificate storage directory
    autoRenew: true,                  // Enable automatic renewal
    renewCheckIntervalHours: 24       // Check for renewals daily
  },
  
  routes: [
    // Routes using certificate: 'auto' will inherit global settings
    {
      name: 'website',
      match: { ports: 443, domains: 'example.com' },
      action: {
        type: 'forward',
        target: { host: 'localhost', port: 8080 },
        tls: {
          mode: 'terminate',
          certificate: 'auto'  // Uses global ACME configuration
        }
      }
    }
  ]
});

Route-Level Certificate Configuration

Certificates are now configured at the route level using the tls property:

const route: IRouteConfig = {
  name: 'secure-website',
  match: {
    ports: 443,
    domains: ['example.com', 'www.example.com']
  },
  action: {
    type: 'forward',
    target: { host: 'localhost', port: 8080 },
    tls: {
      mode: 'terminate',
      certificate: 'auto',  // Use ACME (Let's Encrypt)
      acme: {
        email: 'admin@example.com',
        useProduction: true,
        renewBeforeDays: 30
      }
    }
  }
};

Static Certificate Configuration

For manually managed certificates:

const route: IRouteConfig = {
  name: 'api-endpoint',
  match: {
    ports: 443,
    domains: 'api.example.com'
  },
  action: {
    type: 'forward',
    target: { host: 'localhost', port: 9000 },
    tls: {
      mode: 'terminate',
      certificate: {
        certFile: './certs/api.crt',
        keyFile: './certs/api.key',
        ca: '...' // Optional CA chain
      }
    }
  }
};

TLS Modes

SmartProxy supports three TLS modes:

  1. terminate: Decrypt TLS at the proxy and forward plain HTTP
  2. passthrough: Pass encrypted TLS traffic directly to the backend
  3. terminate-and-reencrypt: Decrypt at proxy, then re-encrypt to backend

Certificate Storage

Certificates are stored in the ./certs directory by default:

./certs/
├── route-name/
│   ├── cert.pem
│   ├── key.pem
│   ├── ca.pem (if available)
│   └── meta.json

ACME Integration

How It Works

  1. SmartProxy creates a high-priority route for ACME challenges
  2. When ACME server makes requests to /.well-known/acme-challenge/*, SmartProxy handles them automatically
  3. Certificates are obtained and stored locally
  4. Automatic renewal checks every 12 hours

Configuration Options

export interface IRouteAcme {
  email: string;                    // Contact email for ACME account
  useProduction?: boolean;          // Use production servers (default: false)
  challengePort?: number;           // Port for HTTP-01 challenges (default: 80)
  renewBeforeDays?: number;         // Days before expiry to renew (default: 30)
}

Advanced Usage

Manual Certificate Operations

// Get certificate status
const status = proxy.getCertificateStatus('route-name');
console.log(status);
// {
//   domain: 'example.com',
//   status: 'valid',
//   source: 'acme',
//   expiryDate: Date,
//   issueDate: Date
// }

// Force certificate renewal
await proxy.renewCertificate('route-name');

// Manually provision a certificate
await proxy.provisionCertificate('route-name');

Events

SmartProxy emits certificate-related events:

proxy.on('certificate:issued', (event) => {
  console.log(`New certificate for ${event.domain}`);
});

proxy.on('certificate:renewed', (event) => {
  console.log(`Certificate renewed for ${event.domain}`);
});

proxy.on('certificate:expiring', (event) => {
  console.log(`Certificate expiring soon for ${event.domain}`);
});

Migration from Previous Versions

Before (v17 and earlier)

// Old approach with Port80Handler
const smartproxy = new SmartProxy({
  port: 443,
  acme: {
    enabled: true,
    accountEmail: 'admin@example.com',
    // ... other ACME options
  }
});

// Certificate provisioning was automatic or via certProvisionFunction

After (v18+)

// New approach with route-based configuration
const smartproxy = new SmartProxy({
  routes: [{
    match: { ports: 443, domains: 'example.com' },
    action: {
      type: 'forward',
      target: { host: 'localhost', port: 8080 },
      tls: {
        mode: 'terminate',
        certificate: 'auto',
        acme: {
          email: 'admin@example.com',
          useProduction: true
        }
      }
    }
  }]
});

Troubleshooting

Common Issues

  1. Certificate not provisioning: Ensure port 80 is accessible for ACME challenges
  2. ACME rate limits: Use staging environment for testing
  3. Permission errors: Ensure the certificate directory is writable

Debug Mode

Enable detailed logging to troubleshoot certificate issues:

const proxy = new SmartProxy({
  enableDetailedLogging: true,
  // ... other options
});

Dynamic Route Updates

When routes are updated dynamically using updateRoutes(), SmartProxy maintains certificate management continuity:

// Update routes with new domains
await proxy.updateRoutes([
  {
    name: 'new-domain',
    match: { ports: 443, domains: 'newsite.example.com' },
    action: {
      type: 'forward',
      target: { host: 'localhost', port: 8080 },
      tls: {
        mode: 'terminate',
        certificate: 'auto'  // Will use global ACME config
      }
    }
  }
]);

Important Notes on Route Updates

  1. Certificate Manager Recreation: When routes are updated, the certificate manager is recreated to reflect the new configuration
  2. ACME Callbacks Preserved: The ACME route update callback is automatically preserved during route updates
  3. Existing Certificates: Certificates already provisioned are retained in the certificate store
  4. New Route Certificates: New routes with certificate: 'auto' will trigger certificate provisioning

ACME Challenge Route Lifecycle

SmartProxy v19.2.3+ implements an improved challenge route lifecycle to prevent port conflicts:

  1. Single Challenge Route: The ACME challenge route on port 80 is added once during initialization, not per certificate
  2. Persistent During Provisioning: The challenge route remains active throughout the entire certificate provisioning process
  3. Concurrency Protection: Certificate provisioning is serialized to prevent race conditions
  4. Automatic Cleanup: The challenge route is automatically removed when the certificate manager stops

Troubleshooting Port 80 Conflicts

If you encounter "EADDRINUSE" errors on port 80:

  1. Check Existing Services: Ensure no other service is using port 80
  2. Verify Configuration: Confirm your ACME configuration specifies the correct port
  3. Monitor Logs: Check for "Challenge route already active" messages
  4. Restart Clean: If issues persist, restart SmartProxy to reset state

Route Update Best Practices

  1. Batch Updates: Update multiple routes in a single updateRoutes() call for efficiency
  2. Monitor Certificate Status: Check certificate status after route updates
  3. Handle ACME Errors: Implement error handling for certificate provisioning failures
  4. Test Updates: Test route updates in staging environment first
  5. Check Port Availability: Ensure port 80 is available before enabling ACME

Best Practices

  1. Always test with staging ACME servers first
  2. Set up monitoring for certificate expiration
  3. Use meaningful route names for easier certificate management
  4. Store static certificates securely with appropriate permissions
  5. Implement certificate status monitoring in production
  6. Batch route updates when possible to minimize disruption
  7. Monitor certificate provisioning after route updates