Usage
certpost has two commands: certpost-server (the infrastructure service) and certpost (the client tool).
Server
Setup
Create a data directory and run the interactive setup wizard:
certpost-server setup -d /var/lib/certpost
The wizard prompts for:
- Base domain — e.g.
example.com - ACME DNS provider — the provider used for Let's Encrypt DNS-01 challenges (TXT records)
- Records DNS provider — the provider used for A/CNAME records (can be the same or different)
- Server settings — bind address and port
For each DNS provider, you'll be asked to choose Cloudflare or Technitium and enter the relevant credentials:
- Cloudflare — API token (from Cloudflare profile > API Tokens) and Zone ID (from domain overview page)
- Technitium — server URL, API token, and zone name
An admin key is generated automatically. All fields can be skipped and filled in later by editing config.json.
Starting the server
certpost-server run -d /var/lib/certpost
The --data-dir (-d) flag is required — there is no default location.
Server options (run)
| Option | Description |
|---|---|
--data-dir, -d |
Data directory (required) |
--port, -p |
Port to listen on (default: 8443) |
--host, -H |
Host to bind to (default: 0.0.0.0) |
--demo |
Stub out DNS calls and disable ACME renewal (beta builds only) |
Demo mode
--demo is intended for local GUI preview. It replaces both DNS providers with a no-op stub and skips ACME initialisation and the renewal goroutine, so the admin panel and APIs are fully interactive but no external services (Cloudflare, Technitium, Let's Encrypt) are ever contacted — even if you click Add / Remove / Rotate. The flag is only registered on beta builds; stable releases reject it.
Configuration
The config.json in your data directory. Use a single dns key when one provider handles everything:
{
"base_domain": "example.com",
"admin_key": "auto-generated-login-key",
"bind": "0.0.0.0",
"port": 8443,
"dns": {
"provider": "cloudflare",
"api_token": "your-cloudflare-api-token",
"zone_id": "your-zone-id"
}
}
For split configurations (e.g. Cloudflare for ACME challenges, Technitium for domain records), use dns_acme and dns_records instead:
{
"base_domain": "example.com",
"admin_key": "auto-generated-login-key",
"bind": "0.0.0.0",
"port": 8443,
"dns_acme": {
"provider": "cloudflare",
"api_token": "your-cloudflare-api-token",
"zone_id": "your-zone-id"
},
"dns_records": {
"provider": "technitium",
"server_url": "https://dns.example.com",
"api_token": "your-technitium-api-token",
"zone": "example.com"
}
}
| Provider | Required fields |
|---|---|
| cloudflare | api_token, zone_id |
| technitium | server_url, api_token, zone |
Legacy configs with flat cloudflare_api_token and cloudflare_zone_id keys are automatically migrated on first startup.
Admin panel
Open http://localhost:8443 and log in with your admin key (printed on server startup). The admin panel has two tabs:
Domains — add subdomains with a target (IP address or CNAME hostname). certpost will:
- Create an A or CNAME record via the configured DNS provider
- Issue a Let's Encrypt certificate via DNS-01 challenge
- Generate a per-domain API token
Domains are listed as thin rows showing status and expiry, sorted by name by default. Sort toggles at the top let you switch between Name and Expires — click again to reverse direction; the active sort shows ▲ / ▼. A text filter narrows the list by case-insensitive substring match on the subdomain — press Esc or click the × inside the filter input to clear it. Sort and filter persist alongside the export preferences. Click a row to expand it and see:
- Target — IP address or CNAME (editable)
- Certificate expiry date
- API token (masked by default, Show/Copy/Rotate buttons)
- Download button for
.crtand.keyfiles - Remove button
Bulk export
Each row has a checkbox. Once one or more domains are ticked, a bulk-action bar appears with an Export… button that opens a modal. The "Select all" checkbox operates on whatever is currently visible in the list — combine with the filter to narrow your selection, then export. Pick a format:
| Format | Output |
|---|---|
| Fetch (JSON) | A ready-to-use certpost fetch config. Single selection writes the flat domain/token form; multiple selections write a domains map. |
| Fetch CLI | One certpost fetch -s … -t … -d … line per selected domain. |
| Proxy (JSON) | A certpost proxy config with a routes map and a 127.0.0.1:8080 placeholder backend per domain (edit before use). |
| CSV | token, domain — note the space after the comma (not strict RFC 4180; strip leading whitespace on import if your tool needs it). |
The server URL defaults to the page's own origin but can be overridden. Your chosen format and server URL are persisted to prefs.json in the data directory and remembered across browser sessions.
Logs — real-time server logs showing ACME operations, DNS changes, errors, and certificate issuance progress.
API
Public endpoints (no authentication)
| Endpoint | Description |
|---|---|
GET /api/version |
Product name, API version, server version |
GET /api/spec |
OpenAPI 3.0 specification (JSON) |
GET /api/help |
Human-readable API documentation (text) |
Certificate retrieval (per-domain bearer token)
GET /api/cert/<domain>
Authorization: Bearer <per-domain-token>
Response:
{
"cert_pem": "-----BEGIN CERTIFICATE-----\n...",
"chain_pem": "-----BEGIN CERTIFICATE-----\n...",
"key_pem": "-----BEGIN RSA PRIVATE KEY-----\n...",
"expires_at": "2026-06-13T03:45:38+00:00",
"issued_at": "2026-03-15T04:43:29+00:00"
}
Token info
GET /api/token-info
Authorization: Bearer <per-domain-token>
Returns the domain associated with the token:
{
"domain": "app.example.com"
}
Client
The client has four subcommands. Running certpost with no command shows help.
certpost fetch
Download certificates and save as files.
# One-shot fetch — domain is resolved from the token
certpost fetch -s http://certpost:8443 -t <token>
# Domain passed explicitly (must match the token's domain)
certpost fetch -s http://certpost:8443 -t <token> -d app.example.com
# Save to a specific directory
certpost fetch -s http://certpost:8443 -t <token> -o /etc/ssl/certs
# Auto-refresh every 24 hours
certpost fetch -s http://certpost:8443 -t <token> --refresh 24
# Using a config file (single or multi-domain)
certpost fetch -c fetch.json
Fetch options
| Option | Description |
|---|---|
--server, -s |
certpost server URL |
--token, -t |
Per-domain API token |
--domain, -d |
Domain to fetch certificate for (optional — resolved from token if omitted) |
--output-dir, -o |
Directory to save files (default: current directory) |
--refresh |
Re-fetch interval in hours (0 = once, default: 0) |
--config, -c |
JSON config file (alternative to CLI flags) |
Fetch config file format — single domain
{
"server": "http://certpost:8443",
"domain": "app.example.com",
"token": "your-api-token",
"output_dir": "/etc/ssl/certs",
"refresh_hours": 24
}
Fetch config file format — multiple domains
Add a domains map to fetch several certificates per cycle. When domains is
present, the top-level domain/token fields are ignored.
{
"server": "http://certpost:8443",
"output_dir": "/etc/ssl/certs",
"refresh_hours": 24,
"domains": {
"app.example.com": "token-for-app",
"api.example.com": "token-for-api"
}
}
Each cycle fetches every listed domain. Individual failures are logged but do not stop the other fetches or halt the refresh loop.
Output files
| File | Contents |
|---|---|
app.example.com.crt |
Server certificate + intermediate chain (PEM) |
app.example.com.key |
Private key (PEM, mode 0600) |
certpost proxy
TLS termination proxy with SNI routing. Fetches certificates from the server, terminates TLS, and forwards plaintext to backend servers. Certificates are refreshed automatically.
certpost proxy -c proxy.json
Proxy options
| Option | Description |
|---|---|
--config, -c |
JSON config file (required) |
--listen |
Listen address, overrides config (default: 0.0.0.0:443) |
Proxy config file format
{
"server": "http://certpost:8443",
"listen": "0.0.0.0:443",
"refresh_hours": 24,
"routes": {
"app.example.com": {
"token": "per-domain-api-token",
"backend": "127.0.0.1:8080"
},
"api.example.com": {
"token": "another-api-token",
"backend": "127.0.0.1:9090"
}
}
}
| Field | Description |
|---|---|
server |
certpost server URL |
listen |
Address and port to listen on (or just a port number) |
refresh_hours |
How often to re-fetch certificates (default: 24) |
routes |
Map of domain → backend with per-domain token |
The proxy:
- Fetches all certificates on startup
- Listens for TLS connections
- Uses SNI to select the correct certificate
- Forwards decrypted traffic to the backend
- Refreshes certificates in the background
Certificate data is loaded directly into memory via tls.X509KeyPair — no temporary files.
certpost init
Interactive wizard to generate a config file for fetch or proxy mode.
certpost init # Generates certpost.json
certpost init -o myconfig.json # Custom output path
The wizard:
- Asks whether you want a fetch or proxy config
- Prompts for the server URL
- For fetch and proxy: prompts for refresh interval and one or more domains (enter an empty token to finish)
- Auto-resolves domains from API tokens (via
/api/token-info) - Writes a single-domain config when only one domain is added and a multi-domain form when more are added
- Validates the configuration against the server before saving
certpost sample-config
Print an example config to stdout — useful when you want a template without running the interactive wizard.
certpost sample-config fetch # single-domain fetch config
certpost sample-config fetch-multi # multi-domain fetch config
certpost sample-config proxy # proxy config
certpost sample-config proxy -o proxy.json # write to file instead
| Kind | Description |
|---|---|
fetch |
Single-domain fetch config |
fetch-multi |
Multi-domain fetch config (uses domains map) |
proxy |
TLS termination proxy config |
Security
Note
Private keys are stored in JSON files in the data directory. Protect this directory with appropriate filesystem permissions.
- The admin panel is protected by an admin key login with cookie-based auth
- "Remember me" sets a persistent cookie; without it the cookie expires when the browser closes
- Certificate retrieval uses per-domain bearer tokens (not a shared token)
- The TLS proxy loads certificates directly into memory via
tls.X509KeyPair— no temp files - Tokens use lowercase alphanumeric characters only (40 characters)