Skip to main content

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:

  1. Install cert-ctrl server (generates and manages your self-signed CA)
  2. Install lightweight agent on each machine that needs HTTPS access
  3. 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
  • 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:

  1. Key Distribution Policy: Set to MASTER_ONLY

    • This disables device-specific key wrapping
    • Private key is encrypted with master key only
  2. 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

FeatureAgent-BasedAgentless API
Security✅ Device-specific encryption⚠️ Master key only
Setup ComplexityMedium (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 RequirementsAgent runtimeHTTP client + cron
Best ForProduction, high-securityTesting, legacy systems

API Reference

For complete REST API documentation, including authentication, error handling, and all available endpoints, see:

HTTP API Reference

Key endpoints for agentless usage:

  • POST /auth/general - Login
  • GET /apiv1/users/:user_id/certificates - List certificates
  • GET /apiv1/users/:user_id/certificates/:cert_id/export - Download bundle
  • POST /auth/refresh - Refresh authentication token

Next Steps


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