Introduction
cert-ctrl is an open-source certificate management platform that simplifies SSL/TLS certificate deployment across multiple machines and environments.
What Problems Does cert-ctrl Solve?
Managing SSL/TLS certificates becomes complex when you need to:
- Deploy certificates to multiple servers automatically
- Trust self-signed certificates across development environments
- Keep certificates synchronized across WSL, remote SSH hosts, and local machines
- Automate certificate renewal and distribution
cert-ctrl solves these challenges with a central management server and lightweight agents installed on target machines.
Use Case 1: Local Development with Self-Signed Certificates
The Problem
When developing HTTPS applications locally, you need:
- A self-signed Certificate Authority (CA) to issue certificates
- The CA trusted by browsers, command-line tools, and various environments
- Certificate distribution across mixed environments (Windows, WSL, remote SSH, Docker containers)
Traditional approach is painful:
# Manual steps on EVERY machine:
# 1. Copy CA certificate file manually
# 2. Install in system trust store (different commands per OS)
# 3. Configure applications individually
# 4. Repeat when CA changes
The cert-ctrl Solution
One-time setup:
- Install cert-ctrl server (generates and manages your self-signed CA)
- Install lightweight agent on each machine that needs HTTPS access
- Agents automatically fetch and trust the CA certificate
Architecture:
┌─────────────────────────────────────────────────────────────┐
│ cert-ctrl Server │
│ • Self-signed CA generator │
│ • Certificate issuer for *.localhost.dev │
│ • Central management dashboard │
└─────────────┬───────────────────────────────────────────────┘
│
│ (Agents pull CA & certificates)
│
┌─────────┼─────────┬──────────────┬─────────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌─────────┐
│ Host │ │ WSL │ │ Remote │ │ Docker │ │ macOS │
│Windows │ │ Ubuntu │ │ SSH │ │ │ │ Dev │
└────────┘ └────────┘ └────────┘ └────────┘ └─────────┘
Agent Agent Agent Agent Agent
Real-World Example
Scenario: You're developing a web app on Windows with backend in WSL, and testing from a remote SSH machine.
# 1. Install cert-ctrl server (one-time)
docker run -d -p 443:443 certctrl/server
# 2. Install agent on Windows host
certctrl-agent install --server https://certctrl.local
# 3. Install agent in WSL
certctrl-agent install --server https://certctrl.local
# 4. Install agent on remote SSH machine
ssh dev-server
certctrl-agent install --server https://certctrl.local
Result: All machines now trust your self-signed CA!
# From Windows browser
https://myapp.localhost.dev ✅ Trusted
# From WSL command line
curl https://myapp.localhost.dev ✅ Works
# From remote SSH
ssh dev-server
curl https://myapp.localhost.dev ✅ Works
Benefits
✅ One-time setup per machine (agent auto-installs CA)
✅ Works everywhere: Browsers, curl, wget, Docker, Python requests, Node.js
✅ Automatic updates: When CA rotates, agents fetch new version
✅ Mixed environments: Windows ↔ WSL ↔ Linux ↔ macOS
✅ No manual certificate copying
Use Case 2: Production Certificates from Let's Encrypt / ZeroSSL
The Problem
When using public SSL certificates in production:
- Certificates expire every 90 days (Let's Encrypt)
- Must be renewed and deployed to all servers
- Each server needs the private key and certificate chain
- Manual deployment is error-prone and causes downtime
Traditional approach:
# Every 90 days, on EVERY server:
ssh server1.example.com
sudo certbot renew
sudo systemctl reload nginx
ssh server2.example.com
sudo certbot renew
sudo systemctl reload nginx
# ... repeat for dozens of servers 😫
The cert-ctrl Solution
Architecture:
┌──────────────────────────────────────────────────────────┐
│ cert-ctrl Management Server │
│ • ACME client (talks to Let's Encrypt/ZeroSSL) │
│ • Automatic DNS-01 challenge (Cloudflare, Aliyun DNS) │
│ • Certificate storage & encryption │
│ • Auto-renewal scheduler │
└────────────┬─────────────────────────────────────────────┘
│
│ (Agents pull certificates securely)
│
┌────────┼────────┬─────────┬─────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ Web │ │ API │ │ Load │ │ Edge │ │ CDN │
│ Server │ │ Server │ │Balancer│ │ Node │ │ Origin │
│ Nginx │ │ Node.js│ │ HAProxy│ │ │ │ │
└────────┘ └────────┘ └────────┘ └────────┘ └────────┘
Agent Agent Agent Agent Agent
Step-by-Step Flow
1. Initial Setup (one-time per domain)
# On cert-ctrl server dashboard:
1. Add domain: example.com
2. Get your unique public-id (e.g., abc123def)
3. Add CNAME record in your DNS:
Name: _acme-challenge.example.com
Type: CNAME
Target: abc123def.acme.cjj365.cc
4. Select CA: Let's Encrypt (or ZeroSSL)
5. Click "Issue Certificate"
The server automatically:
- Generates private key
- Creates ACME order with Let's Encrypt
- Solves DNS-01 challenge automatically:
- Creates TXT record on cert-ctrl's domain:
abc123def.acme.cjj365.cc - Let's Encrypt follows your CNAME and verifies the TXT record
- Automatically removes TXT record after validation
- Creates TXT record on cert-ctrl's domain:
- Receives signed certificate
- Encrypts private key for secure storage
You never need to provide DNS API keys! The CNAME delegation allows cert-ctrl to manage challenges on its own domain.
2. Agent Installation on Servers
Install agent on every server that needs the certificate:
# Web server 1
ssh web1.example.com
certctrl-agent install \
--server https://cjj365.cc \
--device-name web1 \
--service nginx
# Web server 2
ssh web2.example.com
certctrl-agent install \
--server https://cjj365.cc \
--device-name web2 \
--service nginx
# API server
ssh api.example.com
certctrl-agent install \
--server https://cjj365.cc \
--device-name api1 \
--service node
3. Assign Certificate to Devices
From the web dashboard:
Certificates → example.com → Assign to Devices
☑ web1
☑ web2
☑ api1
[Save]
Agents automatically:
- Detect new certificate assignment
- Pull encrypted certificate & private key
- Decrypt using device-specific keys
- Install to appropriate location (
/etc/nginx/ssl/,/etc/pki/tls/, etc.) - Reload web server (nginx, apache, node.js)
4. Automatic Renewal (background process)
Day 0: Certificate issued (valid 90 days)
Day 60: cert-ctrl checks expiry → auto-renews
Agents detect new version → auto-deploy
Web servers reload → zero downtime ✅
Day 90: Old cert expires (but already replaced!)
Real-World Example
Company: E-commerce site with 10 web servers behind a load balancer
Before cert-ctrl:
- DevOps engineer spent 2 hours every 90 days
- Certificate renewal caused 15-minute downtime
- Missed renewal → site down for 6 hours (emergency fix)
After cert-ctrl:
- One-time setup: 30 minutes
- Automatic renewal: 0 engineer time
- Deployment: Instant, zero downtime
- Missed renewal: Impossible (alerts configured)
Benefits
✅ Automatic renewal: 30 days before expiry (configurable)
✅ Zero downtime: Rolling deployment across servers
✅ Secure distribution: Encrypted private keys, device-specific decryption
✅ Centralized management: One dashboard for all certificates
✅ Multi-cloud support: Works with AWS, GCP, Azure, on-premise
✅ DNS provider agnostic: Cloudflare, Aliyun, Route53, etc.
✅ Multiple CAs: Let's Encrypt, ZeroSSL, or bring your own
Key Features
🔐 Security First
- End-to-end encryption for private keys
- Device-specific key wrapping (X25519)
- Master key encryption fallback
- OAuth 2.0 / Passkey authentication
🤖 Automation
- ACME protocol (RFC 8555) client
- DNS-01 challenge automation
- Scheduled certificate renewal
- Agent-based deployment
🌍 Multi-Environment
- Development (self-signed CA)
- Staging (Let's Encrypt staging)
- Production (Let's Encrypt, ZeroSSL)
📊 Observability
- Certificate expiry monitoring
- Deployment status tracking
- Audit logs for all operations
- Webhook notifications
🔌 Integrations
- Web Servers: Nginx, Apache, Caddy, HAProxy
- DNS Providers: Cloudflare, Aliyun DNS, AWS Route53
- Certificate Authorities: Let's Encrypt, ZeroSSL
- Authentication: GitHub OAuth, WeChat, Passkeys
Quick Start
For Local Development (Use Case 1)
# 1. Start cert-ctrl server
docker-compose up -d
# 2. Install agent on your machine
curl -fsSL https://get.certctrl.dev | sh
certctrl-agent register --server https://localhost:8443
# 3. Your self-signed CA is now trusted!
curl https://myapp.localhost.dev
For Production (Use Case 2)
# 1. Deploy cert-ctrl server
# (See deployment guide)
# 2. Issue certificate via dashboard
# Dashboard → Certificates → New Certificate
# Domain: example.com
# DNS: Cloudflare (enter API token)
# CA: Let's Encrypt
# 3. Install agents on servers
certctrl-agent install --server https://cjj365.cc
# 4. Assign certificate to devices
# Dashboard → Assign to Devices
Architecture Overview
┌─────────────────────────────────────────────────────────────┐
│ cert-ctrl Server │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ ACME Client │ │ Certificate │ │ Web │ │
│ │ (RFC 8555) │ │ Storage │ │ Dashboard │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ DNS Provider │ │ Encryption │ │ API │ │
│ │ Adapters │ │ (libsodium)│ │ (REST/WS) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
▲
│ HTTPS / WebSocket
▼
┌──────────────────┐
│ cert-ctrl Agent │
│ (Lightweight) │
│ │
│ • Long-polling │
│ • Auto-update │
│ • Service reload │
└──────────────────┘
Alternative: Agentless Deployment via REST API
For users who cannot or prefer not to install agents, cert-ctrl provides a REST API for direct certificate retrieval. This approach is simpler but comes with security trade-offs.
⚠️ Security Considerations
Agentless mode is LESS SECURE than agent-based deployment:
- ❌ No device-specific encryption: Private keys are not wrapped per-device
- ❌ Server-side decryption required: Server must decrypt keys before transmission
- ❌ Manual renewal: You must poll and deploy certificates yourself
- ⚠️ TLS-only protection: Security relies entirely on HTTPS transport layer
Only use agentless mode if:
- You have strict infrastructure policies preventing agent installation
- You're integrating with existing automation systems (Ansible, Terraform)
- You need temporary access for migration/testing
- You understand and accept the security implications
Prerequisites
Certificate Policy Configuration:
When creating a certificate, you must configure:
-
Key Distribution Policy: Set to
MASTER_ONLY- This disables device-specific key wrapping
- Private key is encrypted with master key only
-
Server Decrypt Export: Enable this flag
- Allows server to decrypt and return plaintext private key
- Without this flag, API will return 403 EXPORT_FORBIDDEN
Example configuration in dashboard:
Certificate Settings:
├─ Domain: example.com
├─ Private Key Policy: MASTER_ONLY ⚠️
└─ Allow Server Export: ☑ Enabled ⚠️
API Usage Example
1. Authenticate and get session
# Login to get session cookie
curl -X POST https://cjj365.cc/auth/general \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"secret"}' \
-c cookies.txt
# Session cookie (cjj365=<session_id>) is now stored in cookies.txt
2. List available certificates
curl -X GET https://cjj365.cc/apiv1/users/1/certificates \
-b cookies.txt
Response:
{
"data": [
{
"id": 1001,
"domain_name": "example.com",
"sans": ["www.example.com"],
"policy": "MASTER_ONLY",
"server_decrypt_export": true,
"verified": true,
"serial_number": "04:A1:B2:C3:...",
"not_after": 1744300000000
}
]
}
3. Download certificate bundle (plaintext)
# Get certificate + plaintext private key
curl -X GET https://cjj365.cc/apiv1/users/1/certificates/1001/export?pack=download \
-b cookies.txt \
-o cert_bundle.json
Response format:
{
"data": {
"enc_scheme": "plaintext",
"certificate_pem": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n",
"private_key_der_b64": "MIIEvQIBADANBgkqhkiG9w0BAQ..."
}
}
4. Deploy to server
# Extract and deploy
cat cert_bundle.json | jq -r '.data.certificate_pem' > /etc/nginx/ssl/cert.pem
# Decode base64 DER private key and convert to PEM
cat cert_bundle.json | jq -r '.data.private_key_der_b64' | \
base64 -d | \
openssl pkey -inform DER -outform PEM -out /etc/nginx/ssl/key.pem
# Set permissions
chmod 600 /etc/nginx/ssl/key.pem
chmod 644 /etc/nginx/ssl/cert.pem
# Reload web server
sudo systemctl reload nginx
Automation Example (Cron Job)
#!/bin/bash
# /usr/local/bin/cert-renewal.sh
CERT_API="https://cjj365.cc"
USER_ID=1
CERT_ID=1001
COOKIE_FILE="/tmp/certctrl-cookies.txt"
# Login
curl -s -X POST "$CERT_API/auth/general" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$EMAIL\",\"password\":\"$PASSWORD\"}" \
-c "$COOKIE_FILE" > /dev/null
# Check certificate expiry
CERT_INFO=$(curl -s -X GET "$CERT_API/apiv1/users/$USER_ID/certificates/$CERT_ID" \
-b "$COOKIE_FILE")
NOT_AFTER=$(echo "$CERT_INFO" | jq -r '.data.not_after')
NOW=$(date +%s%3N) # milliseconds
DAYS_LEFT=$(( ($NOT_AFTER - $NOW) / 86400000 ))
if [ $DAYS_LEFT -lt 30 ]; then
echo "Certificate expires in $DAYS_LEFT days, downloading..."
# Download bundle
curl -s -X GET "$CERT_API/apiv1/users/$USER_ID/certificates/$CERT_ID/export" \
-b "$COOKIE_FILE" > /tmp/cert_bundle.json
# Deploy (as shown above)
# ... deployment logic ...
echo "Certificate renewed and deployed"
fi
Add to crontab:
# Check daily at 3 AM
0 3 * * * /usr/local/bin/cert-renewal.sh
Comparison: Agent vs Agentless
| Feature | Agent-Based | Agentless API |
|---|---|---|
| Security | ✅ Device-specific encryption | ⚠️ Master key only |
| Setup Complexity | Medium (install agent) | Low (script only) |
| Auto-Renewal | ✅ Automatic (server-side) | ✅ Automatic (server-side) |
| Auto-Deployment | ✅ Push to devices | ❌ Manual polling required |
| Zero-Downtime | ✅ Rolling updates | ⚠️ Depends on polling script |
| Private Key Exposure | ✅ Never leaves device plaintext | ⚠️ Transmitted over TLS |
| Infrastructure Requirements | Agent runtime | HTTP client + cron |
| Best For | Production, high-security | Testing, legacy systems |
API Reference
For complete REST API documentation, including authentication, error handling, and all available endpoints, see:
Key endpoints for agentless usage:
POST /auth/general- LoginGET /apiv1/users/:user_id/certificates- List certificatesGET /apiv1/users/:user_id/certificates/:cert_id/export- Download bundlePOST /auth/refresh- Refresh authentication token
Next Steps
- Installation Guide - Deploy cert-ctrl server
- Certificate Issuance - Issue your first certificate
- Private Key Policy - Understand security models
- DNS-01 Challenge - Technical validation details
- API Reference - REST API documentation
Community & Support
- 💬 GitHub Discussions: Ask questions, share tips
- 🐛 Issue Tracker: Report bugs, request features
- 📖 Documentation: Comprehensive guides and API reference
- 🎓 Examples: Sample configurations and use cases
Ready to get started? → Installation Guide