Documentation

Everything you need to install, configure, and run CertDax.

Quick Start (Docker Compose)

The fastest way to get CertDax running is with Docker Compose.

Terminal
# 1. Clone and configure
git clone https://github.com/squadramunter/CertDax.git
cd CertDax
cp .env.example .env

# 2. Generate required secrets
python3 -c "import secrets; print('SECRET_KEY=' + secrets.token_urlsafe(64))"
python3 -c "import base64, os; print('ENCRYPTION_KEY=' + base64.urlsafe_b64encode(os.urandom(32)).decode())"
python3 -c "import secrets; print('DB_PASSWORD=' + secrets.token_urlsafe(32))"

# 3. Edit .env with the generated values and your domain
nano .env

# 4. Start the application
docker compose up -d

# 5. Open your browser and create the first admin account

Environment Configuration

Copy .env.example to .env and configure the following variables:

VariableDescriptionRequired
SECRET_KEYJWT signing key (use the generator above)Yes
ENCRYPTION_KEYFernet key for encrypting private keys at restYes
DATABASE_URLPostgreSQL connection string. Defaults to SQLite for developmentNo
DB_PASSWORDPostgreSQL password (used by docker-compose)For Docker
CORS_ORIGINSAllowed CORS origins (your public URL)Yes
FRONTEND_URLPublic URL of the frontendYes
API_BASE_URLPublic URL of the API (for agent install scripts). Auto-detected if empty.No
SMTP_HOSTSMTP server for email notificationsNo
SMTP_PORTSMTP port (default: 587)No
SMTP_USERSMTP usernameNo
SMTP_PASSWORDSMTP passwordNo
SMTP_FROMSender email addressNo

First Use

  1. Open the application and create an admin account
  2. Go to Providers and configure a DNS provider (e.g. Cloudflare)
  3. Go to CertificatesNew certificate and request your first certificate
  4. (Optional) Set up Agents and install the deploy agent on your servers

Development Setup

Backend

Terminal
cd backend
python3 -m venv venv
source venv/bin/activate
pip3 install -r requirements.txt

# Create a .env for development
cat > .env << EOF
SECRET_KEY=$(python -c "import secrets; print(secrets.token_urlsafe(64))")
CORS_ORIGINS=http://localhost:5173
FRONTEND_URL=http://localhost:5173
DEBUG=true
EOF

uvicorn app.main:app --reload --port 8000

Frontend

Terminal
cd frontend
npm install
npm run dev

Open http://localhost:5173 in your browser.

Deploy Agent — Quick Install

The deploy agent is a statically compiled Go binary that runs on any Linux distribution without dependencies. Available for amd64, arm64, arm, and 386 architectures.

Terminal (target server, as root)
cd agent/
sudo ./install.sh

This automatically detects the architecture, copies the binary to /usr/local/bin/certdax-agent, creates the config directory, and installs the systemd service.

Manual Installation

Terminal
# Choose the correct binary for your architecture
# Options: certdax-agent-linux-amd64, -arm64, -arm, -386
sudo install -m 755 dist/certdax-agent-linux-amd64 /usr/local/bin/certdax-agent

# Create config directory and configure
sudo mkdir -p /etc/certdax
sudo cp config.example.yaml /etc/certdax/config.yaml
sudo chmod 600 /etc/certdax/config.yaml
sudo nano /etc/certdax/config.yaml

# Install systemd service
sudo cp certdax-agent.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now certdax-agent

Usage Without Config File

Terminal
certdax-agent --api-url https://certdax.example.com --token YOUR_AGENT_TOKEN

# Or via environment variables
export CERTDAX_API_URL=https://certdax.example.com
export CERTDAX_AGENT_TOKEN=your-token
certdax-agent

Building From Source

Terminal
cd agent/

# Build for all platforms
make all

# Or build for current platform only
make build

# Binaries are in dist/
ls -la dist/

DNS Provider Configuration

Cloudflare

JSON
{
  "api_token": "your-cloudflare-api-token"
}

Create an API token in Cloudflare with Zone:DNS:Edit permissions.

TransIP

JSON
{
  "login": "your-transip-login",
  "private_key": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
}

Generate a key pair in the TransIP control panel.

Hetzner

JSON
{
  "api_token": "your-hetzner-dns-api-token"
}

Create an API token in the Hetzner DNS Console.

DigitalOcean

JSON
{
  "api_token": "your-digitalocean-api-token"
}

Create a personal access token with read/write scope.

Vultr

JSON
{
  "api_key": "your-vultr-api-key"
}

Create an API key in the Vultr customer portal.

OVH

JSON
{
  "endpoint": "ovh-eu",
  "application_key": "your-app-key",
  "application_secret": "your-app-secret",
  "consumer_key": "your-consumer-key"
}

Generate credentials at https://api.ovh.com/createToken/.

AWS Route 53

JSON
{
  "access_key_id": "AKIAIOSFODNN7EXAMPLE",
  "secret_access_key": "your-secret-access-key",
  "region": "us-east-1"
}

Create an IAM user with route53:ChangeResourceRecordSets and route53:ListHostedZones permissions.

Google Cloud DNS

JSON
{
  "project_id": "your-gcp-project-id",
  "service_account_json": "{...}"
}

Create a service account with the DNS Administrator role and export the JSON key.

Manual

JSON
{}

With manual validation, DNS records are shown in the server logs.

Reverse Proxy

By default CertDax listens on port 80 (HTTP). Place a reverse proxy in front for SSL termination.

Nginx

nginx.conf
server {
    listen 443 ssl http2;
    server_name certdax.example.com;

    ssl_certificate     /etc/ssl/certs/certdax.pem;
    ssl_certificate_key /etc/ssl/private/certdax.key;

    location / {
        proxy_pass http://127.0.0.1:80;
        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;
        proxy_read_timeout 300s;
        client_max_body_size 10m;
    }
}

server {
    listen 80;
    server_name certdax.example.com;
    return 301 https://$host$request_uri;
}

Apache2

Enable the required modules first:

Terminal
sudo a2enmod proxy proxy_http ssl rewrite headers
sudo systemctl restart apache2
apache.conf
<VirtualHost *:443>
    ServerName certdax.example.com

    SSLEngine On
    SSLCertificateFile    /etc/ssl/certs/certdax.pem
    SSLCertificateKeyFile /etc/ssl/private/certdax.key

    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:80/
    ProxyPassReverse / http://127.0.0.1:80/

    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Forwarded-Port "443"
</VirtualHost>

<VirtualHost *:80>
    ServerName certdax.example.com
    RewriteEngine On
    RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
</VirtualHost>

HAProxy

haproxy.cfg
frontend https_in
    bind *:443 ssl crt /etc/haproxy/certs/certdax.pem
    bind *:80
    http-request redirect scheme https unless { ssl_fc }

    default_backend certdax

backend certdax
    option httpchk GET /health
    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    server certdax 127.0.0.1:80 check
Note: Set CORS_ORIGINS and FRONTEND_URL in your .env to the public URL (e.g. https://certdax.example.com).

API Endpoints

Authentication

EndpointMethodDescription
/api/auth/registerPOSTCreate first admin account
/api/auth/loginPOSTLogin (returns JWT token)

ACME Certificates

EndpointMethodDescription
/api/certificatesGETList ACME certificates
/api/certificates/requestPOSTRequest new ACME certificate
/api/certificates/{id}/renewPOSTRenew ACME certificate
/api/providers/casGETList Certificate Authorities
/api/providers/dnsGET/POSTManage DNS providers

Self-Signed & CA-Signed Certificates

EndpointMethodDescription
/api/self-signedGETList certificates (filter: ?is_ca=true, ?search=)
/api/self-signedPOSTCreate self-signed or CA-signed certificate
/api/self-signed/{id}GETGet certificate details (incl. PEM)
/api/self-signed/{id}DELETEDelete certificate (?force=true)
/api/self-signed/{id}/renewPOSTRenew certificate (?validity_days=365)
/api/self-signed/{id}/parsedGETParsed X.509 certificate details
/api/self-signed/{id}/download/zipGETDownload cert + key as ZIP
/api/self-signed/{id}/download/pem/{type}GETDownload PEM (certificate, privatekey, combined, chain, ca)
/api/self-signed/{id}/download/pfxGETDownload as PFX/PKCS#12

Agents & Deployment

EndpointMethodDescription
/api/agentsGET/POSTManage deploy agents
/api/agent-groupsGET/POSTManage agent groups
/api/agent/pollGETAgent: fetch pending deployments
/api/agent/heartbeatPOSTAgent: heartbeat

Self-Signed Certificate API Examples

Create a self-signed certificate:

Terminal
curl -X POST https://certdax.example.com/api/self-signed \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "common_name": "myserver.local",
    "san_domains": ["*.myserver.local"],
    "organization": "MyCompany",
    "country": "NL",
    "key_type": "rsa",
    "key_size": 4096,
    "validity_days": 365
  }'

Create a CA certificate:

Terminal
curl -X POST https://certdax.example.com/api/self-signed \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "common_name": "My Internal CA",
    "organization": "MyCompany",
    "country": "NL",
    "key_type": "rsa",
    "key_size": 4096,
    "validity_days": 3650,
    "is_ca": true
  }'

Sign a certificate with an existing CA:

Terminal
curl -X POST https://certdax.example.com/api/self-signed \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "common_name": "app.internal",
    "san_domains": ["app.internal", "api.internal"],
    "organization": "MyCompany",
    "key_type": "rsa",
    "key_size": 4096,
    "validity_days": 365,
    "ca_id": 1
  }'

Set ca_id to the ID of a certificate created with "is_ca": true. The certificate will be signed by that CA instead of being self-signed. The CA chain is automatically included in ZIP and PFX downloads.

Request Body Reference (POST /api/self-signed)

FieldTypeDefaultDescription
common_namestringrequiredCertificate CN (e.g. myserver.local)
san_domainsstring[]nullAdditional Subject Alternative Names
organizationstringnullOrganization (O)
organizational_unitstringnullOrganizational Unit (OU)
countrystringnullCountry code (C), e.g. NL
statestringnullState/Province (ST)
localitystringnullCity (L)
key_typestringrsarsa or ec
key_sizeint4096RSA: 2048/4096, EC: 256/384
validity_daysint365Certificate validity (1–3650 days)
is_caboolfalseCreate a CA certificate
ca_idintnullID of CA to sign with (omit for self-signed)
auto_renewboolfalseEnable automatic renewal
renewal_threshold_daysintnullDays before expiry to auto-renew (default: 30)
custom_oidsobject[]nullCustom OIDs: [{"oid": "1.3.6...", "value": "desc"}]

Scaling (Multi-Node)

CertDax supports horizontal scaling with multiple backend replicas:

  • Distributed locking — Scheduled tasks use database-backed locks so only one instance executes them
  • Atomic status transitions — Certificate processing uses atomic database updates to prevent race conditions
  • Stateless API — JWT authentication is stateless; any replica can serve any request
  • PostgreSQL required — SQLite only supports single-node; use PostgreSQL for multi-node

Requirements for multi-node

SettingWhy
ENCRYPTION_KEYMust be identical across all replicas
SECRET_KEYMust be identical for JWT validation
DATABASE_URLMust point to a shared PostgreSQL instance

Docker Swarm

Terminal
# Build and push images
docker compose build
docker tag certdax-backend registry.example.com/certdax-backend:latest
docker tag certdax-frontend registry.example.com/certdax-frontend:latest
docker push registry.example.com/certdax-backend:latest
docker push registry.example.com/certdax-frontend:latest

# Deploy as a stack (scales backend replicas)
docker stack deploy -c docker-compose.yml certdax
docker service scale certdax_backend=3

Kubernetes

Use the Docker images with a standard deployment. Key points:

  • Store SECRET_KEY, ENCRYPTION_KEY, DB_PASSWORD in a K8s Secret
  • Use a Deployment with multiple replicas for the backend
  • Point DATABASE_URL to a managed PostgreSQL (e.g. CloudSQL, RDS)

Security

  • Private keys encrypted at rest with Fernet (AES-128-CBC + HMAC)
  • User passwords hashed with bcrypt
  • Agent tokens hashed with SHA-256
  • JWT tokens for web authentication
  • API key authentication for programmatic access
  • CORS configurable per environment
  • Swagger/OpenAPI docs disabled in production
  • Non-root container user for backend
  • Distributed locking for cluster-safe scheduled tasks