CertMate is a powerful SSL certificate management system designed for modern infrastructure. Built with multi-DNS provider support, Docker containerization, and comprehensive REST API, it's the perfect solution for managing certificates across multiple datacenters and cloud environments.
๐ Quick Start โข ๐ Documentation โข ๐ง Installation โข ๐ DNS Providers โข ๏ฟฝ Backup & Recovery โข ๏ฟฝ๐ API Reference
CertMate solves the complexity of SSL certificate management in modern distributed architectures. Whether you're running a single application or managing certificates across multiple datacenters, CertMate provides:
- ๐ Zero-Downtime Automation - Certificates renew automatically 30 days before expiry
- ๐ Multi-Cloud Support - Works with 19 DNS providers (Cloudflare, AWS, Azure, GCP, Hetzner, Porkbun, GoDaddy, and more)
- ๐ Enterprise-Ready - Docker, Kubernetes, REST API, and monitoring built-in
- ๐ฆ Simple Integration - One-URL certificate downloads for easy automation
- ๐ Security-First - Bearer token authentication, secure file permissions, audit logging
- ๐พ Automatic Backup - Built-in backup and restore for settings and certificates
- Let's Encrypt Integration - Free, automated SSL certificates
- Wildcard Support - Single certificate for
*.example.comandexample.com - Multi-Domain Certificates - SAN certificates for multiple domains
- Automatic Renewal - Smart renewal 30 days before expiry
- Certificate Validation - Real-time SSL certificate status checking
- Multi-Account Support - Manage multiple accounts per provider for enterprise environments
- Cloudflare - Global CDN with edge locations worldwide (โจ Multi-Account)
- AWS Route53 - Amazon's scalable DNS service (โจ Multi-Account)
- Azure DNS - Microsoft's cloud DNS solution (โจ Multi-Account)
- Google Cloud DNS - Google's high-performance DNS (โจ Multi-Account)
- DigitalOcean - Cloud infrastructure DNS (โจ Multi-Account)
- PowerDNS - Open-source DNS server with REST API (โจ Multi-Account)
- Multi-Account Management - Support multiple accounts per DNS provider for enterprise workflows
- REST API - Complete programmatic control with Swagger/OpenAPI docs
- Web Dashboard - Modern, responsive UI built with Tailwind CSS
- Docker Ready - Full containerization with Docker Compose
- Kubernetes Compatible - Deploy in any Kubernetes cluster
- High Availability - Stateless design for horizontal scaling
- Monitoring Integration - Health checks and structured logging
- Automatic Backups - Settings and certificates backed up automatically on changes
- Manual Backup Creation - On-demand backup creation via web UI or API
- Comprehensive Coverage - Backs up DNS configurations, certificates, and application settings
- Retention Management - Configurable retention policies with automatic cleanup
- Easy Restore - Simple restore process from any backup point
- Download Support - Export backups for external storage and disaster recovery
- Bearer Token Authentication - Secure API access control
- File Permissions - Proper certificate file security (600/700)
- Audit Logging - Complete certificate lifecycle tracking
- Environment Variables - Secure credential management
- Rate Limit Handling - Let's Encrypt rate limit awareness
- One-URL Downloads - Simple certificate retrieval for automation
- Multiple Output Formats - PEM, ZIP, individual files
- SDK Examples - Python, Bash, Ansible, Terraform examples
- Webhook Support - Certificate renewal notifications
- Backup API - Programmatic backup creation and restoration
- Extensive Documentation - API docs, guides, and examples
CertMate supports 19 DNS providers through Let's Encrypt DNS-01 challenge via individual certbot plugins that provide reliable, well-tested DNS challenge support. Multi-account support is available for major providers, enabling enterprise-grade deployments with separate accounts for production, staging, and disaster recovery.
| Provider | Credentials Required | Multi-Account | Use Case | Status |
|---|---|---|---|---|
| ๐ถ Cloudflare | API Token | โ Yes | Global CDN, Free tier available | โ Stable |
| ๐ AWS Route53 | Access Key, Secret Key | โ Yes | AWS infrastructure, Enterprise | โ Stable |
| ๐ต Azure DNS | Service Principal credentials | โ Yes | Microsoft ecosystem | โ Stable |
| ๐ข Google Cloud DNS | Service Account JSON | โ Yes | Google Cloud Platform | โ Stable |
| ๐ท DigitalOcean | API Token | โ Yes | Cloud infrastructure | โ Stable |
| โซ PowerDNS | API URL, API Key | โ Yes | Self-hosted, On-premises | โ Stable |
| ๐ท RFC2136 | Nameserver, TSIG Key/Secret | โ Yes | Standard DNS update protocol | โ Stable |
| ๐ฃ Linode | API Key | โ Single | Cloud hosting | โ Stable |
| ๐ก Gandi | API Token | โ Single | Domain registrar | โ Stable |
| ๐ด OVH | API Credentials | โ Single | European hosting | โ Stable |
| ๐ข Namecheap | Username, API Key | โ Single | Domain registrar | โ Stable |
| ๐ฆ Vultr | API Key | โ Single | Global cloud infrastructure | โ Stable |
| ๐บ DNS Made Easy | API Key, Secret Key | โ Single | Enterprise DNS management | โ Stable |
| ๐ฃ NS1 | API Key | โ Single | Intelligent DNS platform | โ Stable |
| ** Hetzner** | API Token | โ Single | European cloud hosting | โ Stable |
| ๐ก Porkbun | API Key, Secret Key | โ Single | Domain registrar with DNS | โ Stable |
| ๐ข GoDaddy | API Key, Secret | โ Single | Popular domain registrar | โ Stable |
| ๐ต Hurricane Electric | Username, Password | โ Single | Free DNS hosting | โ Stable |
| ๐ถ Dynu | API Token | โ Single | Dynamic DNS service | โ Stable |
- Enterprise Multi-Account: Cloudflare, AWS Route53, Azure DNS, Google Cloud DNS, DigitalOcean, PowerDNS, RFC2136
- Cloud Providers: AWS Route53, Azure DNS, Google Cloud DNS, DigitalOcean, Linode, Vultr, Hetzner
- Enterprise DNS: Cloudflare, DNS Made Easy, NS1, PowerDNS
- Domain Registrars: Gandi, OVH, Namecheap, Porkbun, GoDaddy
- European Providers: OVH, Gandi, Hetzner
- Free Services: Hurricane Electric, Dynu
- Standard Protocols: RFC2136 (for BIND and compatible servers)
For supported providers, you can configure multiple accounts to enable:
- Environment Separation: Different accounts for production, staging, and development
- Multi-Region Management: Separate accounts for different geographical regions
- Team Isolation: Department-specific accounts with tailored permissions
- Disaster Recovery: Backup accounts for high-availability scenarios
- Permission Scoping: Accounts with minimal required permissions for security
๐ Detailed Setup Instructions: See DNS_PROVIDERS.md for provider-specific configuration.
๐ง Step-by-Step Installation: See INSTALLATION.md for complete setup guide.
๐ข Multi-Account Examples: See MULTI_ACCOUNT_EXAMPLES.md for enterprise configuration examples.
Get CertMate running in under 5 minutes with Docker Compose:
- Docker 20.10+
- Docker Compose 2.0+
- Domain with DNS managed by supported provider
# Clone the repository
git clone https://github.com/fabriziosalmi/certmate.git
cd certmate
# Copy environment template
cp .env.example .envEdit .env file with your credentials:
# ๐ Required: API Security
API_BEARER_TOKEN=your_super_secure_api_token_here_change_this
# ๐ DNS Provider Configuration (choose one or multiple)
# Option 1: Cloudflare (Recommended for beginners)
CLOUDFLARE_TOKEN=your_cloudflare_api_token_here
# Option 2: AWS Route53
# AWS_ACCESS_KEY_ID=your_aws_access_key
# AWS_SECRET_ACCESS_KEY=your_aws_secret_key
# AWS_DEFAULT_REGION=us-east-1
# Option 3: Azure DNS
# AZURE_SUBSCRIPTION_ID=your_azure_subscription_id
# AZURE_RESOURCE_GROUP=your_resource_group
# AZURE_TENANT_ID=your_tenant_id
# AZURE_CLIENT_ID=your_client_id
# AZURE_CLIENT_SECRET=your_client_secret
# Option 4: Google Cloud DNS
# GOOGLE_PROJECT_ID=your_gcp_project_id
# GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
# Option 5: PowerDNS
# POWERDNS_API_URL=https://your-powerdns-server:8081
# POWERDNS_API_KEY=your_powerdns_api_key
# โ๏ธ Optional: Application Settings
SECRET_KEY=your_flask_secret_key_here
FLASK_ENV=production
HOST=0.0.0.0
PORT=8000# Start all services
docker-compose up -d
# Check status
docker-compose ps
# View logs
docker-compose logs -f certmate| Service | URL | Description |
|---|---|---|
| Web Dashboard | http://localhost:8000 | Main certificate management interface |
| API Documentation | http://localhost:8000/docs/ | Interactive Swagger/OpenAPI docs |
| Alternative API Docs | http://localhost:8000/redoc/ | ReDoc documentation |
| Health Check | http://localhost:8000/health | Service health monitoring |
Using the Web Interface:
- Navigate to http://localhost:8000
- Go to Settings and configure your DNS provider
- Add your domain (e.g.
example.com) - Click "Create Certificate"
Using the API:
curl -X POST "http://localhost:8000/api/certificates/create" \
-H "Authorization: Bearer your_api_token_here" \
-H "Content-Type: application/json" \
-d '{"domain": "example.com"}'Choose the installation method that best fits your environment:
Perfect for production deployments with isolation and easy scaling. Supports multiple architectures: AMD64 (Intel/AMD), ARM64 (Apple Silicon, ARM servers), and ARM v7 (Raspberry Pi).
# Quick start with Docker Compose
git clone https://github.com/fabriziosalmi/certmate.git
cd certmate
cp .env.example .env
# Edit .env with your configuration
docker-compose up -dMulti-Platform Support:
# Build for multiple architectures (ARM64 + AMD64)
./build-multiplatform.sh
# Build and push to Docker Hub for all platforms
./build-multiplatform.sh -r YOUR_DOCKERHUB_USERNAME -p
# Use pre-built multi-platform image
docker run --platform linux/arm64 -d --name certmate --env-file .env -p 8000:8000 USERNAME/certmate:latest๐ Multi-Platform Guide: See DOCKER_MULTIPLATFORM.md for comprehensive multi-architecture setup instructions.
Ideal for development and testing environments.
# Create and activate virtual environment
python3 -m venv certmate-env
source certmate-env/bin/activate # On Windows: certmate-env\Scripts\activate
# Install dependencies
git clone https://github.com/fabriziosalmi/certmate.git
cd certmate
pip install -r requirements.txt
# Set environment variables
export API_BEARER_TOKEN="your_token_here"
export CLOUDFLARE_TOKEN="your_cloudflare_token"
# Run the application
python app.pyFor container orchestration and high availability deployments.
# Example Kubernetes deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: certmate
spec:
replicas: 2
selector:
matchLabels:
app: certmate
template:
metadata:
labels:
app: certmate
spec:
containers:
- name: certmate
image: certmate:latest
ports:
- containerPort: 8000
env:
- name: API_BEARER_TOKEN
valueFrom:
secretKeyRef:
name: certmate-secrets
key: api-token
volumeMounts:
- name: certificates
mountPath: /app/certificates
volumes:
- name: certificates
persistentVolumeClaim:
claimName: certmate-certificatesFor system-wide installation on Linux distributions.
# Install system dependencies (Ubuntu/Debian)
sudo apt update
sudo apt install python3 python3-pip python3-venv certbot openssl
# Clone and install
git clone https://github.com/fabriziosalmi/certmate.git
sudo mv certmate /opt/
cd /opt/certmate
sudo pip3 install -r requirements.txt
# Create systemd service (see Service Setup section below for detailed instructions)
sudo cp certmate.service /etc/systemd/system/
sudo systemctl enable certmate
sudo systemctl start certmate๐ Detailed Instructions: See INSTALLATION.md for complete setup guides for each method.
For production deployments, CertMate should run as a system service. This section provides comprehensive instructions for setting up CertMate with systemd on Linux distributions.
- Linux system with systemd
- Python 3.9 or higher
- Root/sudo access
Create a dedicated user for running CertMate:
# Create system user and group
sudo useradd --system --shell /bin/false --home-dir /opt/certmate --create-home certmate
# Set proper ownership
sudo chown -R certmate:certmate /opt/certmateSet up the application in /opt/certmate:
# If not already done, clone the repository
git clone https://github.com/fabriziosalmi/certmate.git
sudo mv certmate /opt/
cd /opt/certmate
# Create Python virtual environment
sudo -u certmate python3 -m venv venv
sudo -u certmate ./venv/bin/pip install -r requirements.txt
# Create necessary directories
sudo -u certmate mkdir -p certificates dataCreate environment file for the service:
# Create environment file
sudo tee /opt/certmate/.env > /dev/null <<EOF
# ๐ SECURITY: Change this token!
API_BEARER_TOKEN=your_super_secure_api_token_here_change_this
# Optional: Set specific host/port
HOST=127.0.0.1
PORT=8000
# Optional: Enable debug mode (not recommended for production)
FLASK_DEBUG=false
EOF
# Set proper permissions
sudo chown certmate:certmate /opt/certmate/.env
sudo chmod 600 /opt/certmate/.envInstall and configure the systemd service:
# Copy service file
sudo cp /opt/certmate/certmate.service /etc/systemd/system/
# Reload systemd configuration
sudo systemctl daemon-reload
# Enable service to start on boot
sudo systemctl enable certmate
# Start the service
sudo systemctl start certmateCheck that the service is running correctly:
# Check service status
sudo systemctl status certmate
# View recent logs
sudo journalctl -u certmate --lines=50
# Follow logs in real-time
sudo journalctl -u certmate -fCommon commands for managing the CertMate service:
# Start service
sudo systemctl start certmate
# Stop service
sudo systemctl stop certmate
# Restart service
sudo systemctl restart certmate
# Reload service configuration
sudo systemctl reload certmate
# Check if service is enabled
sudo systemctl is-enabled certmate
# Check if service is active
sudo systemctl is-active certmate
# Disable service from starting on boot
sudo systemctl disable certmateEnsure proper file permissions for security:
# Set ownership
sudo chown -R certmate:certmate /opt/certmate
# Set directory permissions
sudo chmod 755 /opt/certmate
sudo chmod 750 /opt/certmate/certificates /opt/certmate/data
# Set file permissions
sudo chmod 644 /opt/certmate/*.py /opt/certmate/*.md
sudo chmod 600 /opt/certmate/.env
sudo chmod 755 /opt/certmate/venv/bin/*- API Bearer Token: Always change the default API bearer token in
/opt/certmate/.env - File Permissions: The service runs with restricted permissions and limited filesystem access
- Network Access: The service binds to
0.0.0.0:8000by default - consider using a reverse proxy for production - Environment File: The
.envfile contains sensitive data and should be readable only by thecertmateuser - Certificates: Generated certificates are stored in
/opt/certmate/certificateswith restricted access
If the service fails to start:
- Check service status:
sudo systemctl status certmate - View logs:
sudo journalctl -u certmate --lines=100 - Verify permissions: Ensure the
certmateuser can read all necessary files - Test manually:
sudo -u certmate /opt/certmate/venv/bin/python /opt/certmate/app.py - Check dependencies:
sudo -u certmate /opt/certmate/venv/bin/python validate_dependencies.py
For more detailed installation instructions, see INSTALLATION.md.
CertMate provides a comprehensive REST API for programmatic certificate management. All endpoints require Bearer token authentication.
Include the Authorization header in all API requests:
Authorization: Bearer your_api_token_here# Health check
GET /health
# API documentation
GET /docs/ # Swagger UI
GET /redoc/ # ReDoc documentation# Get current settings
GET /api/settings
Authorization: Bearer your_token_here
# Update settings
POST /api/settings
Authorization: Bearer your_token_here
Content-Type: application/json
{
"dns_provider": "cloudflare",
"dns_providers": {
"cloudflare": {
"api_token": "your_cloudflare_token"
}
},
"domains": [
{
"domain": "example.com",
"dns_provider": "cloudflare"
}
],
"email": "admin@example.com",
"auto_renew": true
}# List all certificates
GET /api/certificates
Authorization: Bearer your_token_here
# Create new certificate
POST /api/certificates/create
Authorization: Bearer your_token_here
Content-Type: application/json
{
"domain": "example.com",
"dns_provider": "cloudflare", # Optional, uses default from settings
"account_id": "production" # Optional, specify which account to use
}
# Create certificate with specific account
POST /api/certificates/create
Authorization: Bearer your_token_here
Content-Type: application/json
{
"domain": "staging.example.com",
"dns_provider": "cloudflare",
"account_id": "staging"
}
# Renew certificate
POST /api/certificates/example.com/renew
Authorization: Bearer your_token_here
# Download certificate (ZIP format)
GET /api/certificates/example.com/download
Authorization: Bearer your_token_here
# Check certificate deployment status
GET /api/certificates/example.com/deployment-status
Authorization: Bearer your_token_here# Add multiple accounts for a provider
POST /api/settings/dns-providers/cloudflare/accounts
Authorization: Bearer your_token_here
Content-Type: application/json
{
"account_id": "production",
"config": {
"name": "Production Environment",
"description": "Main production Cloudflare account",
"api_token": "your_production_token_here"
}
}
# List all accounts for a provider
GET /api/settings/dns-providers/cloudflare/accounts
Authorization: Bearer your_token_here
# Set default account for a provider
PUT /api/settings/dns-providers/cloudflare/default-account
Authorization: Bearer your_token_here
Content-Type: application/json
{
"account_id": "production"
}
# Update account configuration
PUT /api/settings/dns-providers/cloudflare/accounts/staging
Authorization: Bearer your_token_here
Content-Type: application/json
{
"config": {
"name": "Staging & Testing",
"description": "Updated staging environment",
"api_token": "new_staging_token_here"
}
}The most powerful feature for infrastructure automation:
# Download certificates via simple URL pattern
GET /{domain}/tls
Authorization: Bearer your_token_hereThis endpoint returns a ZIP file containing all certificate files:
cert.pem- Server certificatechain.pem- Intermediate certificate chainfullchain.pem- Full certificate chain (cert + chain)privkey.pem- Private key
curl -H "Authorization: Bearer your_token_here" \
-o example.com-tls.zip \
https://your-certmate-server.com/example.com/tlsimport requests
import zipfile
from pathlib import Path
class CertMateClient:
def __init__(self, base_url, token):
self.base_url = base_url.rstrip('/')
self.headers = {"Authorization": f"Bearer {token}"}
def download_certificate(self, domain, extract_to=None):
"""Download and optionally extract certificate for domain"""
url = f"{self.base_url}/{domain}/tls"
response = requests.get(url, headers=self.headers)
response.raise_for_status()
zip_path = f"{domain}-tls.zip"
with open(zip_path, 'wb') as f:
f.write(response.content)
if extract_to:
Path(extract_to).mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(extract_to)
return zip_path
def list_certificates(self):
"""List all managed certificates"""
response = requests.get(f"{self.base_url}/api/certificates",
headers=self.headers)
response.raise_for_status()
return response.json()
def create_certificate(self, domain, dns_provider=None):
"""Create new certificate for domain"""
data = {"domain": domain}
if dns_provider:
data["dns_provider"] = dns_provider
response = requests.post(f"{self.base_url}/api/certificates/create",
json=data, headers=self.headers)
response.raise_for_status()
return response.json()
def renew_certificate(self, domain):
"""Renew existing certificate"""
response = requests.post(f"{self.base_url}/api/certificates/{domain}/renew",
headers=self.headers)
response.raise_for_status()
return response.json()
class CertMateClient:
def __init__(self, base_url, token):
self.base_url = base_url
self.token = token
self.headers = {"Authorization": f"Bearer {token}"}
def create_certificate(self, domain, dns_provider=None, account_id=None):
"""Create a new certificate with optional account specification"""
data = {"domain": domain}
if dns_provider:
data["dns_provider"] = dns_provider
if account_id:
data["account_id"] = account_id
response = requests.post(
f"{self.base_url}/api/certificates/create",
headers=self.headers,
json=data
)
return response.json()
def add_dns_account(self, provider, account_id, config):
"""Add a new DNS provider account"""
response = requests.post(
f"{self.base_url}/api/settings/dns-providers/{provider}/accounts",
headers=self.headers,
json={"account_id": account_id, "config": config}
)
return response.json()
def list_dns_accounts(self, provider):
"""List all accounts for a DNS provider"""
response = requests.get(
f"{self.base_url}/api/settings/dns-providers/{provider}/accounts",
headers=self.headers
)
return response.json()
def download_certificate(self, domain, extract_to=None):
"""Download certificate as ZIP file"""
response = requests.get(
f"{self.base_url}/{domain}/tls",
headers=self.headers
)
if extract_to and response.status_code == 200:
with zipfile.ZipFile(io.BytesIO(response.content)) as zip_file:
zip_file.extractall(extract_to)
print(f"Certificate extracted to {extract_to}")
return response
# Usage example
client = CertMateClient("https://certmate.company.com", "your_token_here")
# Add multiple Cloudflare accounts
client.add_dns_account("cloudflare", "production", {
"name": "Production Environment",
"description": "Main production account",
"api_token": "prod_token_here"
})
client.add_dns_account("cloudflare", "staging", {
"name": "Staging Environment",
"description": "Development and testing",
"api_token": "staging_token_here"
})
# Create certificates with specific accounts
client.create_certificate("api.company.com", "cloudflare", "production")
client.create_certificate("staging.company.com", "cloudflare", "staging")
# Download certificate
client.download_certificate("api.company.com", extract_to="/etc/ssl/certs/api/")Terraform Provider Example:
# Configure the CertMate provider
terraform {
required_providers {
certmate = {
source = "local/certmate"
version = "~> 1.0"
}
}
}
provider "certmate" {
endpoint = "https://certmate.company.com"
token = var.certmate_token
}
# Create certificates for multiple domains with different accounts
resource "certmate_certificate" "api" {
domain = "api.company.com"
dns_provider = "cloudflare"
account_id = "production"
}
resource "certmate_certificate" "web" {
domain = "web.company.com"
dns_provider = "route53"
account_id = "main-aws"
}
resource "certmate_certificate" "staging" {
domain = "staging.company.com"
dns_provider = "cloudflare"
account_id = "staging"
}
# Download certificates to local files
data "certmate_certificate_download" "api" {
domain = certmate_certificate.api.domain
}
# Use in nginx configuration
resource "kubernetes_secret" "api_tls" {
metadata {
name = "api-tls"
namespace = "default"
}
type = "kubernetes.io/tls"
data = {
"tls.crt" = data.certmate_certificate_download.api.fullchain_pem
"tls.key" = data.certmate_certificate_download.api.private_key_pem
}
}Bash Automation Script:
#!/bin/bash
set -euo pipefail
# Configuration
CERTMATE_URL="https://certmate.company.com"
API_TOKEN="${CERTMATE_TOKEN}"
DOMAIN="${1:-example.com}"
CERT_DIR="/etc/ssl/certs/${DOMAIN}"
BACKUP_DIR="/backup/certs/${DOMAIN}/$(date +%Y%m%d_%H%M%S)"
# Functions
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" >&2
}
create_backup() {
if [[ -d "$CERT_DIR" ]]; then
log "Creating backup of existing certificates"
mkdir -p "$BACKUP_DIR"
cp -r "$CERT_DIR"/* "$BACKUP_DIR/" || true
fi
}
download_certificate() {
log "Downloading certificate for ${DOMAIN}"
# Download with retry logic
for i in {1..3}; do
if curl -f -H "Authorization: Bearer $API_TOKEN" \
-o "${DOMAIN}-tls.zip" \
"$CERTMATE_URL/$DOMAIN/tls"; then
log "Certificate downloaded successfully"
return 0
else
log "Download attempt $i failed, retrying..."
sleep 5
fi
done
log "Failed to download certificate after 3 attempts"
return 1
}
extract_certificate() {
log "Extracting certificate to ${CERT_DIR}"
mkdir -p "$CERT_DIR"
unzip -o "${DOMAIN}-tls.zip" -d "$CERT_DIR"
# Set proper permissions
chmod 600 "$CERT_DIR"/*.pem
chown root:ssl-cert "$CERT_DIR"/*.pem
}
reload_services() {
log "Reloading web services"
systemctl reload nginx || log "Failed to reload nginx"
systemctl reload apache2 || log "Failed to reload apache2"
systemctl reload haproxy || log "Failed to reload haproxy"
}
cleanup() {
rm -f "${DOMAIN}-tls.zip"
}
# Main execution
main() {
log "Starting certificate update for ${DOMAIN}"
create_backup
download_certificate
extract_certificate
reload_services
cleanup
log "Certificate update completed for ${DOMAIN}"
}
# Trap cleanup on exit
trap cleanup EXIT
# Run main function
main "$@"Advanced Ansible Playbook:
---
- name: Manage SSL certificates with CertMate multi-account support
hosts: web_servers
vars:
certmate_url: "https://certmate.company.com"
certmate_token: "{{ vault_certmate_token }}"
tasks:
- name: Configure Cloudflare accounts
uri:
url: "{{ certmate_url }}/api/settings/dns-providers/cloudflare/accounts"
method: POST
headers:
Authorization: "Bearer {{ certmate_token }}"
Content-Type: "application/json"
body_format: json
body:
account_id: "{{ item.account_id }}"
config:
name: "{{ item.name }}"
description: "{{ item.description }}"
api_token: "{{ item.api_token }}"
loop:
- account_id: "production"
name: "Production Environment"
description: "Main production Cloudflare account"
api_token: "{{ vault_cloudflare_prod_token }}"
- account_id: "staging"
name: "Staging Environment"
description: "Development and testing account"
api_token: "{{ vault_cloudflare_staging_token }}"
- name: Create certificates with specific accounts
uri:
url: "{{ certmate_url }}/api/certificates/create"
method: POST
headers:
Authorization: "Bearer {{ certmate_token }}"
Content-Type: "application/json"
body_format: json
body:
domain: "{{ item.domain }}"
dns_provider: "{{ item.provider }}"
account_id: "{{ item.account_id }}"
loop:
- domain: "api.company.com"
provider: "cloudflare"
account_id: "production"
- domain: "staging.company.com"
provider: "cloudflare"
account_id: "staging"
- domain: "test.company.com"
provider: "route53"
account_id: "backup-aws"
- name: Download and deploy certificates
block:
- name: Download certificate
uri:
url: "{{ certmate_url }}/{{ item }}/tls"
headers:
Authorization: "Bearer {{ certmate_token }}"
dest: "/tmp/{{ item }}-tls.zip"
- name: Extract certificate
unarchive:
src: "/tmp/{{ item }}-tls.zip"
dest: "/etc/ssl/certs/{{ item }}/"
remote_src: yes
owner: root
group: ssl-cert
mode: '0640'
loop:
- "api.company.com"
- "staging.company.com"
- "test.company.com"become: yes vars: certmate_url: "https://certmate.company.com" api_token: "{{ vault_certmate_token }}" certificate_domains: - name: "api.company.com" dns_provider: "cloudflare" nginx_sites: ["api"] services_to_reload: ["nginx"] - name: "web.company.com" dns_provider: "route53" nginx_sites: ["web", "admin"] services_to_reload: ["nginx", "haproxy"]
tasks: - name: Create certificate directories file: path: "/etc/ssl/certs/{{ item.name }}" state: directory owner: root group: ssl-cert mode: '0750' loop: "{{ certificate_domains }}"
- name: Check certificate expiry
uri:
url: "{{ certmate_url }}/api/certificates/{{ item.name }}/deployment-status"
method: GET
headers:
Authorization: "Bearer {{ api_token }}"
register: cert_status
loop: "{{ certificate_domains }}"
- name: Create new certificates if needed
uri:
url: "{{ certmate_url }}/api/certificates/create"
method: POST
headers:
Authorization: "Bearer {{ api_token }}"
Content-Type: "application/json"
body_format: json
body:
domain: "{{ item.name }}"
dns_provider: "{{ item.dns_provider }}"
loop: "{{ certificate_domains }}"
when: cert_status.results[ansible_loop.index0].json.needs_renewal | default(false)
- name: Download certificates
uri:
url: "{{ certmate_url }}/{{ item.name }}/tls"
method: GET
headers:
Authorization: "Bearer {{ api_token }}"
dest: "/tmp/{{ item.name }}-tls.zip"
creates: "/tmp/{{ item.name }}-tls.zip"
loop: "{{ certificate_domains }}"
- name: Extract certificates
unarchive:
src: "/tmp/{{ item.name }}-tls.zip"
dest: "/etc/ssl/certs/{{ item.name }}/"
owner: root
group: ssl-cert
mode: '0640'
remote_src: yes
loop: "{{ certificate_domains }}"
notify:
- reload nginx
- reload haproxy
- restart services
- name: Verify certificate installation
openssl_certificate:
path: "/etc/ssl/certs/{{ item.name }}/fullchain.pem"
provider: assertonly
has_expired: no
valid_in: 86400 # Valid for at least 1 day
loop: "{{ certificate_domains }}"
- name: Update nginx SSL configuration
template:
src: "nginx-ssl.conf.j2"
dest: "/etc/nginx/sites-available/{{ item.1 }}"
backup: yes
loop: "{{ certificate_domains | subelements('nginx_sites') }}"
notify: reload nginx
- name: Cleanup temporary files
file:
path: "/tmp/{{ item.name }}-tls.zip"
state: absent
loop: "{{ certificate_domains }}"
handlers: - name: reload nginx systemd: name: nginx state: reloaded
- name: reload haproxy
systemd:
name: haproxy
state: reloaded
- name: restart services
systemd:
name: "{{ item }}"
state: restarted
loop: "{{ services_to_restart | default([]) }}"
## โ๏ธ Configuration Guide
### ๐ง Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `API_BEARER_TOKEN` | โ
| - | Bearer token for API authentication |
| `SECRET_KEY` | โ | auto-generated | Flask secret key for sessions |
| `HOST` | โ | `127.0.0.1` | Server bind address |
| `PORT` | โ | `8000` | Server port |
| `FLASK_ENV` | โ | `production` | Flask environment |
| `FLASK_DEBUG` | โ | `false` | Enable debug mode |
### ๐ DNS Provider Configuration
#### Cloudflare Setup
1. Go to [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens)
2. Click "Create Token" โ "Custom token"
3. Set permissions:
- **Zone**: `DNS:Edit` + `Zone:Read`
- **Zone Resources**: Include specific zones or all zones
4. Copy the generated token
```bash
# Environment variable
CLOUDFLARE_TOKEN=your_cloudflare_api_token_here
- Create IAM user with Route53 permissions
- Attach policy:
Route53FullAccessor custom policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"route53:ListHostedZones",
"route53:GetChange",
"route53:ChangeResourceRecordSets"
],
"Resource": "*"
}
]
}# Environment variables
AWS_ACCESS_KEY_ID=your_access_key_id
AWS_SECRET_ACCESS_KEY=your_secret_access_key
AWS_DEFAULT_REGION=us-east-1- Create Service Principal:
az ad sp create-for-rbac --name "CertMate" --role "DNS Zone Contributor" --scopes "/subscriptions/{subscription-id}/resourceGroups/{resource-group}"# Environment variables
AZURE_SUBSCRIPTION_ID=your_subscription_id
AZURE_RESOURCE_GROUP=your_resource_group_name
AZURE_TENANT_ID=your_tenant_id
AZURE_CLIENT_ID=your_client_id
AZURE_CLIENT_SECRET=your_client_secret- Create service account with DNS Administrator role
- Download JSON key file
# Environment variables
GOOGLE_PROJECT_ID=your_project_id
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json# Environment variables
POWERDNS_API_URL=https://your-powerdns-server:8081
POWERDNS_API_KEY=your_api_keycertmate/
โโโ ๐ app.py # Main Flask application
โโโ ๐ requirements.txt # Python dependencies
โโโ ๐ docker-compose.yml # Docker Compose configuration
โโโ ๐ Dockerfile # Container build instructions
โโโ ๐ nginx.conf # Nginx reverse proxy config
โโโ ๐ .env.example # Environment template
โโโ ๐ README.md # This documentation
โโโ ๐ INSTALLATION.md # Detailed installation guide
โโโ ๐ DNS_PROVIDERS.md # DNS provider configuration
โโโ ๐ CONTRIBUTING.md # Contribution guidelines
โโโ ๐ certificates/ # Certificate storage
โ โโโ ๐ {domain}/
โ โโโ ๐ cert.pem # Server certificate
โ โโโ ๐ chain.pem # Certificate chain
โ โโโ ๐ fullchain.pem # Full chain
โ โโโ ๐ privkey.pem # Private key
โโโ ๐ data/ # Application data
โ โโโ โ๏ธ settings.json # Persistent settings
โโโ ๐ logs/ # Application logs
โโโ ๐ letsencrypt/ # Let's Encrypt working directory
โ โโโ ๐ config/ # Certbot configuration
โ โโโ ๐ work/ # Certbot working files
โ โโโ ๐ logs/ # Certbot logs
โโโ ๐ templates/ # Web interface templates
โโโ ๐ index.html # Main dashboard
โโโ โ๏ธ settings.html # Settings page
- Strong Bearer Tokens: Use cryptographically secure tokens (32+ characters)
- Token Rotation: Regularly rotate API tokens
- Environment Variables: Never commit tokens to version control
- HTTPS Only: Always use HTTPS in production environments
- IP Restrictions: Implement firewall rules to restrict access
- File Permissions: Private keys stored with
600permissions - Directory Permissions: Certificate directories with
700permissions - Backup Encryption: Encrypt certificate backups
- Access Logging: Monitor certificate access patterns
# Example firewall rules (iptables)
# Allow only specific IPs to access CertMate
iptables -A INPUT -p tcp --dport 8000 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 8000 -s 192.168.0.0/16 -j ACCEPT
iptables -A INPUT -p tcp --dport 8000 -j DROP# docker-compose.prod.yml
version: '3.8'
services:
certmate:
image: certmate:latest
deploy:
replicas: 2
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
environment:
- FLASK_ENV=production
- GUNICORN_WORKERS=4
- GUNICORN_THREADS=2
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60supstream certmate_backend {
server certmate1:8000;
server certmate2:8000;
server certmate3:8000;
}
server {
listen 443 ssl http2;
server_name certmate.company.com;
ssl_certificate /etc/ssl/certs/certmate.company.com/fullchain.pem;
ssl_certificate_key /etc/ssl/certs/certmate.company.com/privkey.pem;
location / {
proxy_pass http://certmate_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# API rate limiting
limit_req zone=api burst=10 nodelay;
}
}CertMate provides comprehensive backup and recovery capabilities built directly into the application, ensuring your certificates and configuration data are always protected.
Automatic Backups:
- Settings Backup - Automatically created when DNS providers, domains, or application settings are modified
- Certificate Backup - Automatically created when new certificates are generated or renewed
- Retention Management - Configurable retention policy (default: 10 most recent backups)
- Automatic Cleanup - Old backups are automatically removed based on retention settings
Manual Backups:
- On-Demand Creation - Create backups anytime via the web interface or API
- Selective Backup - Choose to backup settings, certificates, or both
- Download Support - Export backups for external storage and disaster recovery
Access backup features from the Settings page:
<!-- Manual backup creation -->
<button onclick="createBackup('settings', this)">Create Settings Backup</button>
<button onclick="createBackup('certificates', this)">Create Certificate Backup</button>
<!-- View and manage existing backups -->
- Download backups for external storage
- Restore from any backup point
- View backup contents and metadata
- Delete specific backups manuallyCreate Backups:
# Create settings backup
curl -X POST "http://localhost:8000/api/backup/settings" \
-H "Authorization: Bearer your_token"
# Create certificate backup
curl -X POST "http://localhost:8000/api/backup/certificates" \
-H "Authorization: Bearer your_token"List and Download Backups:
# List all backups
curl -H "Authorization: Bearer your_token" \
"http://localhost:8000/api/backups"
# Download specific backup
curl -H "Authorization: Bearer your_token" \
"http://localhost:8000/api/backup/download/settings_20241225_120000.json" \
-o backup.jsonSettings Backup (JSON):
{
"timestamp": "2024-12-25T12:00:00Z",
"version": "1.0",
"dns_providers": {...},
"domains": [...],
"settings": {...}
}Certificate Backup (ZIP):
certificates_20241225_120000.zip
โโโ domain1.com/
โ โโโ cert.pem
โ โโโ chain.pem
โ โโโ fullchain.pem
โ โโโ privkey.pem
โโโ domain2.com/
โโโ cert.pem
โโโ chain.pem
โโโ fullchain.pem
โโโ privkey.pem
For additional protection, integrate with external backup systems:
Automated External Backup Script:
#!/bin/bash
# /opt/scripts/backup-certmate-external.sh
BACKUP_DIR="/backup/certmate/$(date +%Y%m%d_%H%M%S)"
CERT_DIR="/opt/certmate/certificates"
DATA_DIR="/opt/certmate/data"
BACKUP_STORAGE="/backup/certmate/backups"
RETENTION_DAYS=30
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Download latest backups via API
curl -H "Authorization: Bearer $API_TOKEN" \
"http://localhost:8000/api/backups/latest/settings" \
-o "$BACKUP_DIR/settings_backup.json"
curl -H "Authorization: Bearer $API_TOKEN" \
"http://localhost:8000/api/backups/latest/certificates" \
-o "$BACKUP_DIR/certificates_backup.zip"
# Backup certificates directory
tar -czf "$BACKUP_DIR/certificates.tar.gz" "$CERT_DIR"
# Backup application data
tar -czf "$BACKUP_DIR/data.tar.gz" "$DATA_DIR"
# Copy built-in backups
cp -r "$BACKUP_STORAGE"/* "$BACKUP_DIR/"
# Encrypt backups (optional)
gpg --cipher-algo AES256 --compress-algo 1 --symmetric \
--output "$BACKUP_DIR/certificates.tar.gz.gpg" \
"$BACKUP_DIR/certificates.tar.gz"
# Cleanup old backups
find /backup/certmate -type d -mtime +$RETENTION_DAYS -exec rm -rf {} \;
echo "External backup completed: $BACKUP_DIR"Web Interface Recovery:
- Navigate to Settings โ Backup Management
- Select the backup to restore from
- Choose restore type (settings, certificates, or both)
- Confirm restoration
- Application will restart if settings are restored
API Recovery:
# Restore settings from backup
curl -X POST "http://localhost:8000/api/backup/restore/settings" \
-H "Authorization: Bearer your_token" \
-H "Content-Type: application/json" \
-d '{"backup_id": "settings_20241225_120000"}'
# Restore certificates from backup
curl -X POST "http://localhost:8000/api/backup/restore/certificates" \
-H "Authorization: Bearer your_token" \
-H "Content-Type: application/json" \
-d '{"backup_id": "certificates_20241225_120000"}'Manual Recovery from External Backup:
#!/bin/bash
# /opt/scripts/backup-certmate.sh
BACKUP_DIR="/backup/certmate/$(date +%Y%m%d_%H%M%S)"
CERT_DIR="/opt/certmate/certificates"
DATA_DIR="/opt/certmate/data"
RETENTION_DAYS=30
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Backup certificates
tar -czf "$BACKUP_DIR/certificates.tar.gz" "$CERT_DIR"
# Backup application data
tar -czf "$BACKUP_DIR/data.tar.gz" "$DATA_DIR"
# Backup database if using external DB
# mysqldump certmate > "$BACKUP_DIR/database.sql"
# Encrypt backups
gpg --cipher-algo AES256 --compress-algo 1 --symmetric \
--output "$BACKUP_DIR/certificates.tar.gz.gpg" \
"$BACKUP_DIR/certificates.tar.gz"
# Cleanup old backups
find /backup/certmate -type d -mtime +$RETENTION_DAYS -exec rm -rf {} \;
echo "Backup completed: $BACKUP_DIR"#!/bin/bash
# Recovery from backup
BACKUP_DATE="20241225_120000"
BACKUP_DIR="/backup/certmate/$BACKUP_DATE"
# Stop services
docker-compose down
# Restore certificates
gpg --decrypt "$BACKUP_DIR/certificates.tar.gz.gpg" | \
tar -xzf - -C /opt/certmate/
# Restore data
tar -xzf "$BACKUP_DIR/data.tar.gz" -C /opt/certmate/
# Set permissions
chown -R 1000:1000 /opt/certmate/certificates
chmod -R 700 /opt/certmate/certificates
# Start services
docker-compose up -d
echo "Recovery completed from backup: $BACKUP_DATE"# Basic health check
curl -f http://localhost:8000/health
# Detailed health with auth
curl -H "Authorization: Bearer your_token" \
http://localhost:8000/api/certificates# Add to app.py for Prometheus monitoring
from prometheus_client import Counter, Histogram, generate_latest
# Metrics
certificate_requests = Counter('certmate_certificate_requests_total',
'Total certificate requests', ['domain', 'status'])
certificate_expiry = Histogram('certmate_certificate_expiry_days',
'Days until certificate expiry', ['domain'])
@app.route('/metrics')
def metrics():
return generate_latest()# docker-compose.logging.yml
version: '3.8'
services:
certmate:
logging:
driver: "fluentd"
options:
fluentd-address: localhost:24224
tag: certmate
fluentd-async-connect: "true"
fluentd:
image: fluent/fluentd:v1.14
volumes:
- ./fluentd/conf:/fluentd/etc
- ./logs:/var/log/fluentd
ports:
- "24224:24224"
- "24224:24224/udp"{
"dashboard": {
"title": "CertMate SSL Certificate Monitoring",
"panels": [
{
"title": "Certificate Expiry Status",
"targets": [
{
"expr": "certmate_certificate_expiry_days < 30",
"legendFormat": "Expiring Soon ({{domain}})"
}
],
"alert": {
"conditions": [
{
"query": {"queryType": "", "refId": "A"},
"reducer": {"type": "last", "params": []},
"evaluator": {"params": [30], "type": "lt"}
}
],
"executionErrorState": "alerting",
"frequency": "1h",
"handler": 1,
"name": "Certificate Expiring",
"noDataState": "no_data",
"notifications": []
}
}
]
}
}# Add webhook notification support
import requests
def send_webhook_notification(domain, event_type, details):
webhook_url = os.getenv('WEBHOOK_URL')
if not webhook_url:
return
payload = {
'domain': domain,
'event': event_type,
'timestamp': datetime.now().isoformat(),
'details': details
}
try:
requests.post(webhook_url, json=payload, timeout=10)
except Exception as e:
logger.error(f"Failed to send webhook: {e}")# Slack notification script
#!/bin/bash
SLACK_WEBHOOK="your_slack_webhook_url"
DOMAIN="$1"
STATUS="$2"
MESSAGE="Certificate for $DOMAIN: $STATUS"
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"๐ $MESSAGE\"}" \
"$SLACK_WEBHOOK"Issue: DNS validation failed
# Check DNS propagation
dig TXT _acme-challenge.example.com @8.8.8.8
# Verify DNS provider credentials
curl -H "Authorization: Bearer cf_token" \
"https://api.cloudflare.com/client/v4/user/tokens/verify"Issue: Rate limit exceeded
# Let's Encrypt rate limits:
# - 50 certificates per registered domain per week
# - 5 duplicate certificates per week
# - 300 new orders per account per 3 hours
# Check rate limit status
curl "https://crt.sh/?q=example.com&output=json" | jq lengthIssue: Permission denied accessing certificate files
# Fix file permissions
sudo chown -R certmate:certmate /opt/certmate/certificates
sudo chmod -R 700 /opt/certmate/certificates
sudo chmod 600 /opt/certmate/certificates/*/privkey.pemIssue: 401 Unauthorized
# Verify token format
curl -H "Authorization: Bearer your_token_here" \
http://localhost:8000/api/certificates
# Check token in settings
docker exec certmate cat /app/data/settings.json | jq .api_bearer_tokenIssue: Token not found
# Reset API token
docker exec -it certmate python -c "
import json
with open('/app/data/settings.json', 'r+') as f:
data = json.load(f)
data['api_bearer_token'] = 'new_secure_token_here'
f.seek(0)
json.dump(data, f, indent=2)
f.truncate()
"Issue: Container won't start
# Check logs
docker-compose logs certmate
# Verify environment variables
docker-compose config
# Check port conflicts
netstat -tulpn | grep :8000Issue: Volume mount issues
# Fix volume permissions
sudo chown -R 1000:1000 ./certificates ./data ./logs
# Check volume mounts
docker inspect certmate | jq '.[0].Mounts'Cloudflare:
# Verify API token permissions
curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer your_token_here"
# Check zone access
curl -X GET "https://api.cloudflare.com/client/v4/zones" \
-H "Authorization: Bearer your_token_here"AWS Route53:
# Test AWS credentials
aws sts get-caller-identity
# Check Route53 permissions
aws route53 list-hosted-zonesAzure DNS:
# Verify service principal
az login --service-principal \
-u $AZURE_CLIENT_ID \
-p $AZURE_CLIENT_SECRET \
--tenant $AZURE_TENANT_ID
# Check DNS zone access
az network dns zone listEnable debug logging for troubleshooting:
# Environment variable
FLASK_DEBUG=true
FLASK_ENV=development
# Or in Docker Compose
docker-compose -f docker-compose.yml -f docker-compose.debug.yml upBefore seeking help, please provide:
- CertMate version/commit hash
- DNS provider being used
- Error messages from logs
- Steps to reproduce the issue
- Environment details (Docker, Python version, OS)
# Collect system information
echo "=== CertMate Debug Info ==="
echo "Version: $(docker exec certmate python -c 'import app; print(getattr(app, "__version__", "unknown"))')"
echo "Python: $(docker exec certmate python --version)"
echo "OS: $(docker exec certmate cat /etc/os-release | head -2)"
echo "Certbot: $(docker exec certmate certbot --version)"
echo "DNS Plugins: $(docker exec certmate pip list | grep certbot-dns)"
echo "Certificates: $(docker exec certmate ls -la /app/certificates)"
echo "Settings: $(docker exec certmate cat /app/data/settings.json | jq .)"| Document | Description | Target Audience |
|---|---|---|
| README.md | Main documentation and quick start | All users |
| INSTALLATION.md | Detailed installation instructions | System administrators |
| DNS_PROVIDERS.md | DNS provider setup guides | DevOps engineers |
| CONTRIBUTING.md | Development and contribution guide | Developers |
| CODE_OF_CONDUCT.md | Community guidelines | Contributors |
- API Documentation: http://your-server:8000/docs/ (Swagger UI)
- Alternative API Docs: http://your-server:8000/redoc/ (ReDoc)
- GitHub Repository: https://github.com/fabriziosalmi/certmate
- Docker Hub: https://hub.docker.com/r/certmate/certmate
- Issue Tracker: https://github.com/fabriziosalmi/certmate/issues
Check out our examples repository for:
- Production deployment configurations
- Integration scripts for popular tools
- Terraform modules
- Kubernetes manifests
- CI/CD pipeline examples
- ๐ Certificate Templates - Predefined certificate configurations
- ๐ Advanced Analytics - Certificate usage and performance metrics
- ๐ ACME v2 Features - External account binding, certificate transparency
- ๐ Multi-Region Support - Geographic certificate distribution
- ๐ฑ Mobile App - iOS/Android certificate monitoring
- ๐ค AI-Powered Renewal - Intelligent renewal scheduling
- ๐ Plugin System - Custom DNS provider plugins
- ๐ Enterprise Dashboard - Multi-tenant certificate management
| Version | Release Date | Key Features |
|---|---|---|
| v2.0.0 | 2024-12-25 | Multi-DNS provider support, enhanced API |
| v1.5.0 | 2024-11-15 | Docker improvements, monitoring features |
| v1.0.0 | 2024-10-01 | Initial release with Cloudflare support |
We welcome contributions! Areas where we need help:
- Documentation - Tutorials, use cases, translations
- Testing - DNS provider testing, edge cases
- Integrations - New DNS providers, monitoring tools
- Features - UI improvements, API enhancements
We love contributions! CertMate is an open-source project and we welcome:
- ๐ Bug Reports - Help us identify and fix issues
- ๐ก Feature Requests - Suggest new functionality
- ๐ Documentation - Improve guides and examples
- ๐งช Testing - Test new features and edge cases
- ๐ป Code - Submit pull requests with improvements
# Fork and clone the repository
git clone https://github.com/fabriziosalmi/certmate.git
cd certmate
# Create development environment
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
pip install -r requirements-dev.txt
# Set up pre-commit hooks
pre-commit install
# Run tests
pytest
# Start development server
python app.py- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Thanks to all contributors who make CertMate better!
This project is licensed under the MIT License - see the LICENSE file for details.
- โ Commercial Use - Use in commercial projects
- โ Modification - Modify and distribute changes
- โ Distribution - Distribute the software
- โ Private Use - Use privately
- โ Liability - No warranty or liability
- โ Patent Grant - No patent rights granted
Made with โค๏ธ by the CertMate Team
โญ Star us on GitHub โข ๐ Report Bug โข ๐ก Request Feature