SSL Certificate Management (F3.4) β
Feature: F3.4 β SSL Certificate Management
Status: π’ COMPLETED
Package:@vexlyx/api,@vexlyx/dashboard,@vexlyx/shared
Prisma Models:Domain,Certificate
Infrastructure: Traefik v3.4 ACME (Let's Encrypt), Pythonssl_manager.py
1. Overview β
SSL Certificate Management provides automated and custom TLS certificate provisioning, validation, encryption, renewal, and expiry monitoring across all domains managed in Vexlyx.
Drawing from best practices in modern cloud control panels (Coolify, CloudPanel, CapRover) and solving the common "silent renewal failure" problem, Vexlyx provides:
- Automated Let's Encrypt Provisioning: Traefik v3 automatically fulfills ACME HTTP-01 challenges on port 80/443 with persistent certificate storage in
docker/traefik/acme.json. - Wildcard & Custom Certificate Support: Users can upload manual custom certificates (PEM certificate chain and private key) with client- and server-side pairing validation and AES-256-GCM encrypted private key storage at rest.
- Offline / Development Environment Fallback: Seamless self-signed certificate generation with Subject Alternative Names (SANs) for
.localhostdomains and test suites, allowing complete HTTPS testing without public domain or internet dependencies. - Proactive Certificate Expiry Monitoring (7-Day Alert Threshold): Backend monitoring evaluates certificate lifespan. When a certificate is within 7 days of expiration, a prominent alert is raised and status transitions to
EXPIRING_SOON. - HTTPS Redirection & Security Controls: One-click toggle for Traefik 301 permanent HTTPS redirection and auto-renewal controls.
2. Architecture & Data Flow β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Next.js Dashboard UI β
β - /domains (Global domain overview & SSL status badge) β
β - /domains/[id]/ssl (Dedicated SSL Certificate page) β
β - Expiry Alert Banner (<= 7 days remaining warning) β
β - Validity Progress Countdown, Key-Value attributes grid β
β - Custom Certificate Upload Dialog (PEM validation) β
β - HTTPS Redirection & Auto-Renewal toggles β
ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββ
β HTTP / JSON
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Fastify API Server β
β - GET /api/domains/ssl/alerts (Batch expiry audit) β
β - GET /api/domains/:id/ssl (Get certificate) β
β - POST /api/domains/:id/ssl/provision (Auto Let's Encryptβ
β - POST /api/domains/:id/ssl/upload (Custom cert upload) β
β - POST /api/domains/:id/ssl/renew (Force renewal) β
β - PATCH /api/domains/:id/ssl/settings (Toggles) β
β - DELETE /api/domains/:id/ssl (Disable SSL) β
βββββββββββββββββ¬βββββββββββββββββββββββββββββββ¬βββββββββββββββ
β β
βΌ βΌ
βββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββ
β PostgreSQL (Prisma) β β system/python/ β
β - model Domain β β ssl_manager.py β
β - model Certificate β β - self-signed dev certs β
β * type (LETS_ENCRYPT, etc) β β - key pair validation β
β * status (ACTIVE, etc) β β - acme.json parser β
β * validFrom, validTo β βββββββββββββββ¬ββββββββββββββ
β * commonName, sans β β Writes files
β * encryptedKey (AES-256) β βΌ
β * forceHttps, autoRenew β βββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββ β Traefik v3 Container β
β - Ports :80 and :443 β
β - websecure entrypoint β
β - certResolver letsencryptβ
β - /etc/traefik/certs/ β
βββββββββββββββββββββββββββββ3. Database Schema β
enum CertType {
LETS_ENCRYPT
CUSTOM
SELF_SIGNED
}
enum CertStatus {
PENDING
ACTIVE
EXPIRING_SOON
EXPIRED
ERROR
}
model Certificate {
id String @id @default(cuid())
domainId String @unique @map("domain_id")
type CertType @default(LETS_ENCRYPT)
status CertStatus @default(PENDING)
issuer String?
commonName String @map("common_name")
sans String[] @default([])
validFrom DateTime? @map("valid_from")
validTo DateTime? @map("valid_to")
autoRenew Boolean @default(true) @map("auto_renew")
forceHttps Boolean @default(true) @map("force_https")
certPath String? @map("cert_path")
keyPath String? @map("key_path")
encryptedKey String? @map("encrypted_key")
serialNumber String? @map("serial_number")
errorMessage String? @map("error_message")
lastCheckedAt DateTime? @map("last_checked_at")
lastRenewedAt DateTime? @map("last_renewed_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
domain Domain @relation(fields: [domainId], references: [id], onDelete: Cascade)
@@index([status])
@@index([validTo])
@@map("certificates")
}4. Traefik Dynamic Router Configuration β
When SSL is enabled for a domain, Vexlyx automatically updates docker/traefik/dynamic/domain-{domainId}.yml:
# Auto-generated by Vexlyx with SSL for domain: app.example.com
# Project: web-app (cmtl9...)
http:
routers:
domain-cmtl9...-http:
rule: Host(`app.example.com`)
priority: 100
entryPoints:
- web
middlewares:
- redirect-to-https-cmtl9...
service: service-cmtl9...
domain-cmtl9...-https:
rule: Host(`app.example.com`)
priority: 100
entryPoints:
- websecure
tls:
certResolver: letsencrypt
service: service-cmtl9...
middlewares:
redirect-to-https-cmtl9...:
redirectScheme:
scheme: https
permanent: true
services:
service-cmtl9...:
loadBalancer:
servers:
- url: "http://vexlyx-web-app-app-1:3000"For custom certificates, the TLS router config references the mounted certificate files:
tls:
certificates:
- certFile: /etc/traefik/certs/domain-cmtl9....crt
keyFile: /etc/traefik/certs/domain-cmtl9....key5. Expiry Monitoring & Alert Policy β
- Active Window (
> 7 days):- Status remains
ACTIVE. - Green badge displayed on dashboard with days remaining counter.
- Status remains
- Alert Threshold (
<= 7 days):- Status transitions to
EXPIRING_SOON. - Prominent amber alert banner displayed on
/domains/[id]/ssland card badge on/domains. - Informs administrator that certificate renewal is imminent.
- Status transitions to
- Expired (
<= 0 days):- Status transitions to
EXPIRED. - Rose alert banner displayed with one-click renew trigger.
- Status transitions to
6. How to Test β
Run the automated test suite:
python tests/test_ssl_management.pyExpected output:
test_01_self_signed_cert_generation ... ok
test_02_cert_parsing_and_expiry_detection ... ok
test_03_validate_key_pair_matching_and_mismatch ... ok
test_docker_compose_traefik_ports ... ok
test_expiry_thresholds ... ok
test_traefik_static_config ... ok
test_01_unauthenticated_endpoints_return_401 ... ok
----------------------------------------------------------------------
Ran 7 tests in 11.523s
OK