10 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:
- terminate: Decrypt TLS at the proxy and forward plain HTTP
- passthrough: Pass encrypted TLS traffic directly to the backend
- 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
- SmartProxy creates a high-priority route for ACME challenges
- When ACME server makes requests to
/.well-known/acme-challenge/*
, SmartProxy handles them automatically - Certificates are obtained and stored locally
- 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 (v19+)
// New approach with global ACME configuration
const smartproxy = new SmartProxy({
// Global ACME defaults (v19+)
acme: {
email: 'ssl@bleu.de',
useProduction: true,
port: 80 // Or 8080 for non-privileged
},
routes: [{
match: { ports: 443, domains: 'example.com' },
action: {
type: 'forward',
target: { host: 'localhost', port: 8080 },
tls: {
mode: 'terminate',
certificate: 'auto' // Uses global ACME settings
}
}
}]
});
Troubleshooting
Common Issues
- Certificate not provisioning: Ensure the ACME challenge port (80 or configured port) is accessible
- ACME rate limits: Use staging environment for testing (
useProduction: false
) - Permission errors: Ensure the certificate directory is writable
- Invalid email domain: ACME servers may reject certain email domains (e.g., example.com). Use a real email domain
- Port binding errors: Use higher ports (e.g., 8080) if running without root privileges
Using Non-Privileged Ports
For development or non-root environments:
const proxy = new SmartProxy({
acme: {
email: 'ssl@bleu.de',
port: 8080, // Use 8080 instead of 80
useProduction: false
},
routes: [
{
match: { ports: 8443, domains: 'example.com' },
action: {
type: 'forward',
target: { host: 'localhost', port: 3000 },
tls: {
mode: 'terminate',
certificate: 'auto'
}
}
}
]
});
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
- Certificate Manager Recreation: When routes are updated, the certificate manager is recreated to reflect the new configuration
- ACME Callbacks Preserved: The ACME route update callback is automatically preserved during route updates
- Existing Certificates: Certificates already provisioned are retained in the certificate store
- 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:
- Single Challenge Route: The ACME challenge route on port 80 is added once during initialization, not per certificate
- Persistent During Provisioning: The challenge route remains active throughout the entire certificate provisioning process
- Concurrency Protection: Certificate provisioning is serialized to prevent race conditions
- 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:
- Check Existing Services: Ensure no other service is using port 80
- Verify Configuration: Confirm your ACME configuration specifies the correct port
- Monitor Logs: Check for "Challenge route already active" messages
- Restart Clean: If issues persist, restart SmartProxy to reset state
Route Update Best Practices
- Batch Updates: Update multiple routes in a single
updateRoutes()
call for efficiency - Monitor Certificate Status: Check certificate status after route updates
- Handle ACME Errors: Implement error handling for certificate provisioning failures
- Test Updates: Test route updates in staging environment first
- Check Port Availability: Ensure port 80 is available before enabling ACME
Best Practices
- Always test with staging ACME servers first
- Set up monitoring for certificate expiration
- Use meaningful route names for easier certificate management
- Store static certificates securely with appropriate permissions
- Implement certificate status monitoring in production
- Batch route updates when possible to minimize disruption
- Monitor certificate provisioning after route updates