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.
# 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:
| Variable | Description | Required |
|---|---|---|
SECRET_KEY | JWT signing key (use the generator above) | Yes |
ENCRYPTION_KEY | Fernet key for encrypting private keys at rest | Yes |
DATABASE_URL | PostgreSQL connection string. Defaults to SQLite for development | No |
DB_PASSWORD | PostgreSQL password (used by docker-compose) | For Docker |
CORS_ORIGINS | Allowed CORS origins (your public URL) | Yes |
FRONTEND_URL | Public URL of the frontend | Yes |
API_BASE_URL | Public URL of the API (for agent install scripts). Auto-detected if empty. | No |
SMTP_HOST | SMTP server for email notifications | No |
SMTP_PORT | SMTP port (default: 587) | No |
SMTP_USER | SMTP username | No |
SMTP_PASSWORD | SMTP password | No |
SMTP_FROM | Sender email address | No |
First Use
- Open the application and create an admin account
- Go to Providers and configure a DNS provider (e.g. Cloudflare)
- Go to Certificates → New certificate and request your first certificate
- (Optional) Set up Agents and install the deploy agent on your servers
Development Setup
Backend
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
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.
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
# 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
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
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
{
"api_token": "your-cloudflare-api-token"
}
Create an API token in Cloudflare with Zone:DNS:Edit permissions.
TransIP
{
"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
{
"api_token": "your-hetzner-dns-api-token"
}
Create an API token in the Hetzner DNS Console.
DigitalOcean
{
"api_token": "your-digitalocean-api-token"
}
Create a personal access token with read/write scope.
Vultr
{
"api_key": "your-vultr-api-key"
}
Create an API key in the Vultr customer portal.
OVH
{
"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
{
"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
{
"project_id": "your-gcp-project-id",
"service_account_json": "{...}"
}
Create a service account with the DNS Administrator role and export the JSON key.
Manual
{}
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
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:
sudo a2enmod proxy proxy_http ssl rewrite headers
sudo systemctl restart apache2
<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
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
CORS_ORIGINS and FRONTEND_URL in your .env to the public URL (e.g. https://certdax.example.com).
API Endpoints
Authentication
| Endpoint | Method | Description |
|---|---|---|
/api/auth/register | POST | Create first admin account |
/api/auth/login | POST | Login (returns JWT token) |
ACME Certificates
| Endpoint | Method | Description |
|---|---|---|
/api/certificates | GET | List ACME certificates |
/api/certificates/request | POST | Request new ACME certificate |
/api/certificates/{id}/renew | POST | Renew ACME certificate |
/api/providers/cas | GET | List Certificate Authorities |
/api/providers/dns | GET/POST | Manage DNS providers |
Self-Signed & CA-Signed Certificates
| Endpoint | Method | Description |
|---|---|---|
/api/self-signed | GET | List certificates (filter: ?is_ca=true, ?search=) |
/api/self-signed | POST | Create self-signed or CA-signed certificate |
/api/self-signed/{id} | GET | Get certificate details (incl. PEM) |
/api/self-signed/{id} | DELETE | Delete certificate (?force=true) |
/api/self-signed/{id}/renew | POST | Renew certificate (?validity_days=365) |
/api/self-signed/{id}/parsed | GET | Parsed X.509 certificate details |
/api/self-signed/{id}/download/zip | GET | Download cert + key as ZIP |
/api/self-signed/{id}/download/pem/{type} | GET | Download PEM (certificate, privatekey, combined, chain, ca) |
/api/self-signed/{id}/download/pfx | GET | Download as PFX/PKCS#12 |
Agents & Deployment
| Endpoint | Method | Description |
|---|---|---|
/api/agents | GET/POST | Manage deploy agents |
/api/agent-groups | GET/POST | Manage agent groups |
/api/agent/poll | GET | Agent: fetch pending deployments |
/api/agent/heartbeat | POST | Agent: heartbeat |
Self-Signed Certificate API Examples
Create a self-signed certificate:
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:
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:
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)
| Field | Type | Default | Description |
|---|---|---|---|
common_name | string | required | Certificate CN (e.g. myserver.local) |
san_domains | string[] | null | Additional Subject Alternative Names |
organization | string | null | Organization (O) |
organizational_unit | string | null | Organizational Unit (OU) |
country | string | null | Country code (C), e.g. NL |
state | string | null | State/Province (ST) |
locality | string | null | City (L) |
key_type | string | rsa | rsa or ec |
key_size | int | 4096 | RSA: 2048/4096, EC: 256/384 |
validity_days | int | 365 | Certificate validity (1–3650 days) |
is_ca | bool | false | Create a CA certificate |
ca_id | int | null | ID of CA to sign with (omit for self-signed) |
auto_renew | bool | false | Enable automatic renewal |
renewal_threshold_days | int | null | Days before expiry to auto-renew (default: 30) |
custom_oids | object[] | null | Custom 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
| Setting | Why |
|---|---|
ENCRYPTION_KEY | Must be identical across all replicas |
SECRET_KEY | Must be identical for JWT validation |
DATABASE_URL | Must point to a shared PostgreSQL instance |
Docker Swarm
# 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_PASSWORDin a K8s Secret - Use a
Deploymentwith multiple replicas for the backend - Point
DATABASE_URLto 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