Issue a certificate from a private CA
8 minute read
Private CAs enable organizations to issue trusted certificates for internal services, devices, and users without relying on public certificate authorities. With the DigiCert Private CA API, you can automate certificate issuance workflows and integrate certificate management into your existing infrastructure.
In this tutorial, you will:
- List available CAs to identify your issuing CA.
- Get CA templates to select the appropriate certificate profile.
- Generate a certificate signing request (CSR) for your server.
- Submit the certificate request to your private CA.
- Download the issued certificate.
- Validate the certificate chain using OpenSSL.
Before you begin
Before you begin, make sure you have:
API token with the following permissions:
VIEW_CM_CA- View CA details and list available CAsVIEW_CM_CUSTOM_TEMPLATE- View certificate templatesCREATE_CM_CERTIFICATE- Issue certificates from a CAVIEW_CM_CERTIFICATE- View and download issued certificates
openssl version to verify.Endpoint overview
| Method | Path | Description |
|---|---|---|
| GET | /ca | List all available CAs |
| GET | /template | Get certificate templates |
| POST | /certificate | Submit certificate request |
| GET | /certificate/{id} | Get certificate details |
| GET | /ca/{id}/download | Download CA certificate |
Step 1: List available CAs
Before you can issue a certificate, you need to identify the CA that will sign it. Use this request to list all CAs you have access to and find an active issuing CA.
Request:
curl -X GET "https://demo.one.digicert.com/ca" \
-H "x-api-key: SERVICE_API_TOKEN" \
| jq '.'import requests
response = requests.get(
"https://demo.one.digicert.com/ca",
headers={"x-api-key": "SERVICE_API_TOKEN"}
)
print(f"Status: {response.status_code}")
if response.status_code != 204:
print(response.json())import java.net.http.*;
import java.net.URI;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://demo.one.digicert.com/ca"))
.header("x-api-key", "SERVICE_API_TOKEN")
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + response.statusCode());
System.out.println(response.body());using System.Net.Http;
using System.Text;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", "SERVICE_API_TOKEN");
var response = await client.GetAsync("https://demo.one.digicert.com/ca");
Console.WriteLine($"Status: {(int)response.StatusCode}");
Console.WriteLine(await response.Content.ReadAsStringAsync());Successful response (200 OK):
{
"id": "01234567-89ab-cdef-0123-456789abcdef",
"status": "active",
"_note": "Response schema not fully documented in API spec"
}
From the response, identify a CA with status set to active and cert_type set to intermediate (issuing CAs are typically intermediate CAs). Save the id value. You will need this in Step 4 when you submit the certificate request, and in Step 6 when you download the CA certificate for chain validation.
Step 2: Get CA templates
Certificate templates define the properties and extensions included in issued certificates. Each template is configured for specific use cases such as TLS server authentication, client authentication, or code signing.
Request:
curl -X GET "https://demo.one.digicert.com/template" \
-H "x-api-key: SERVICE_API_TOKEN" \
| jq '.'import requests
response = requests.get(
"https://demo.one.digicert.com/template",
headers={"x-api-key": "SERVICE_API_TOKEN"}
)
print(f"Status: {response.status_code}")
if response.status_code != 204:
print(response.json())import java.net.http.*;
import java.net.URI;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://demo.one.digicert.com/template"))
.header("x-api-key", "SERVICE_API_TOKEN")
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + response.statusCode());
System.out.println(response.body());using System.Net.Http;
using System.Text;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", "SERVICE_API_TOKEN");
var response = await client.GetAsync("https://demo.one.digicert.com/template");
Console.WriteLine($"Status: {(int)response.StatusCode}");
Console.WriteLine(await response.Content.ReadAsStringAsync());Successful response (200 OK):
{
"id": "01234567-89ab-cdef-0123-456789abcdef",
"status": "active",
"_note": "Response schema not fully documented in API spec"
}
From the response, identify a template with cert_type set to end_entity and active set to true. For server certificates, look for templates with names indicating TLS or server authentication. Save the id value. You will need this in Step 4 when you submit the certificate request.
Step 3: Generate a CSR
A certificate signing request (CSR) contains the public key and identity information for your certificate. You generate the CSR locally using OpenSSL, which also creates the corresponding private key.
Command:
openssl req -new -newkey rsa:2048 -nodes \
-keyout server.key \
-out server.csr \
-subj "/CN=server.example.internal/O=Example Corp/C=US"
This command generates:
server.key- Your private key (keep this secure and never share it)server.csr- The CSR to submit to the CA
Adjust the -subj values to match your server’s identity:
CN- Common name (typically the server’s fully qualified domain name)O- Organization nameC- Two-letter country code
[!IMPORTANT] Protect your private key. Anyone with access to this file can impersonate your server.
To verify your CSR before submitting it:
openssl req -text -noout -in server.csr
Step 4: Submit the certificate request
Submit your CSR to the private CA for signing. The CA validates the request against the selected template and issues the certificate.
Request:
curl -X POST "https://demo.one.digicert.com/certificate" \
-H "x-api-key: SERVICE_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"ca_id": "01234567-89ab-cdef-0123-456789abcdef",
"template_id": "01234567-89ab-cdef-0123-456789abcdef",
"csr": "CSR_VALUE",
"validity": "01234567-89ab-cdef-0123-456789abcdef"
}' \
| jq '.'import requests
response = requests.post(
"https://demo.one.digicert.com/certificate",
headers={"x-api-key": "SERVICE_API_TOKEN", "Content-Type": "application/json"},
json={
"ca_id": "01234567-89ab-cdef-0123-456789abcdef",
"template_id": "01234567-89ab-cdef-0123-456789abcdef",
"csr": "CSR_VALUE",
"validity": "01234567-89ab-cdef-0123-456789abcdef"
}
)
print(f"Status: {response.status_code}")
if response.status_code != 204:
print(response.json())import java.net.http.*;
import java.net.URI;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://demo.one.digicert.com/certificate"))
.header("x-api-key", "SERVICE_API_TOKEN")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"ca_id\": \"01234567-89ab-cdef-0123-456789abcdef\", \"template_id\": \"01234567-89ab-cdef-0123-456789abcdef\", \"csr\": \"CSR_VALUE\", \"validity\": \"01234567-89ab-cdef-0123-456789abcdef\"}"))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + response.statusCode());
System.out.println(response.body());using System.Net.Http;
using System.Text;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", "SERVICE_API_TOKEN");
var content = new StringContent(@"{
""ca_id"": ""01234567-89ab-cdef-0123-456789abcdef"",
""template_id"": ""01234567-89ab-cdef-0123-456789abcdef"",
""csr"": ""CSR_VALUE"",
""validity"": ""01234567-89ab-cdef-0123-456789abcdef""
}",
Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://demo.one.digicert.com/certificate", content);
Console.WriteLine($"Status: {(int)response.StatusCode}");
Console.WriteLine(await response.Content.ReadAsStringAsync());Replace CA_ID with the CA ID from Step 1 and TEMPLATE_ID with the template ID from Step 2. The csr field should contain the PEM-encoded CSR content from your server.csr file.
Successful response (200 OK):
{
"id": "01234567-89ab-cdef-0123-456789abcdef",
"status": "active",
"_note": "Response schema not fully documented in API spec"
}
From the response, save the certificate id. You will need this in Step 5 to download the certificate.
Step 5: Download the certificate
Retrieve the issued certificate using the certificate ID from the previous step. The response includes the certificate and optionally the certificate chain.
Request:
curl -X GET "https://demo.one.digicert.com/certificate/01234567-89ab-cdef-0123-456789abcdef" \
-H "x-api-key: SERVICE_API_TOKEN" \
| jq '.'import requests
CERT_ID = "01234567-89ab-cdef-0123-456789abcdef"
response = requests.get(
f"https://demo.one.digicert.com/certificate/{CERT_ID}",
headers={"x-api-key": "SERVICE_API_TOKEN"}
)
print(f"Status: {response.status_code}")
if response.status_code != 204:
print(response.json())import java.net.http.*;
import java.net.URI;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://demo.one.digicert.com/certificate/01234567-89ab-cdef-0123-456789abcdef"))
.header("x-api-key", "SERVICE_API_TOKEN")
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + response.statusCode());
System.out.println(response.body());using System.Net.Http;
using System.Text;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", "SERVICE_API_TOKEN");
var response = await client.GetAsync("https://demo.one.digicert.com/certificate/01234567-89ab-cdef-0123-456789abcdef");
Console.WriteLine($"Status: {(int)response.StatusCode}");
Console.WriteLine(await response.Content.ReadAsStringAsync());Replace {id} with the certificate ID from Step 4.
Successful response (200 OK):
{
"id": "01234567-89ab-cdef-0123-456789abcdef",
"status": "active",
"_note": "Response schema not fully documented in API spec"
}
From the response, extract the certificate from the blob field. The certificate is base64-encoded. Decode it and save to a file:
echo "CERTIFICATE_BLOB_CONTENT" | base64 -d > server.crt
If the response includes a chain array, save those certificates as well for chain validation.
Step 6: Validate the certificate chain
Verify that your issued certificate properly chains to your private CA. This confirms the certificate will be trusted by systems that trust your CA.
First, download the CA certificate:
Request:
curl -X GET "https://demo.one.digicert.com/ca/01234567-89ab-cdef-0123-456789abcdef/download?format=pem" \
-H "x-api-key: SERVICE_API_TOKEN" \
| jq '.'import requests
CA_ID = "01234567-89ab-cdef-0123-456789abcdef"
response = requests.get(
f"https://demo.one.digicert.com/ca/{CA_ID}/download",
headers={"x-api-key": "SERVICE_API_TOKEN"},
params={"format": "pem"}
)
print(f"Status: {response.status_code}")
if response.status_code != 204:
print(response.json())import java.net.http.*;
import java.net.URI;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://demo.one.digicert.com/ca/01234567-89ab-cdef-0123-456789abcdef/download?format=pem"))
.header("x-api-key", "SERVICE_API_TOKEN")
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + response.statusCode());
System.out.println(response.body());using System.Net.Http;
using System.Text;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", "SERVICE_API_TOKEN");
var response = await client.GetAsync("https://demo.one.digicert.com/ca/01234567-89ab-cdef-0123-456789abcdef/download?format=pem");
Console.WriteLine($"Status: {(int)response.StatusCode}");
Console.WriteLine(await response.Content.ReadAsStringAsync());Replace {id} with the CA ID from Step 1.
Save the CA certificate response to a file named ca.crt.
Validate the certificate chain:
openssl verify -CAfile ca.crt server.crt
A successful validation displays:
server.crt: OK
If your CA is an intermediate CA, you may need to include the full chain:
openssl verify -CAfile root.crt -untrusted ca.crt server.crt
You can also view the certificate details:
openssl x509 -in server.crt -text -noout
Verify that the Issuer field matches your CA’s subject and the Subject matches your CSR.
Common errors and solutions
For general API errors (authentication, rate limits), see the API error reference.
invalid_ca_status
{
"errors": [
{
"code": "invalid_ca_status",
"message": "CA is not in active status"
}
]
}
The specified CA is not active. Use Step 1 to list CAs and select one with status set to active.
invalid_template
{
"errors": [
{
"code": "invalid_template",
"message": "Template not found or not active"
}
]
}
The specified template ID is invalid or the template is deactivated. Use Step 2 to list templates and select one with active set to true.
invalid_csr
{
"errors": [
{
"code": "invalid_csr",
"message": "CSR format is invalid or cannot be parsed"
}
]
}
The CSR is malformed or not properly PEM-encoded. Verify your CSR using openssl req -text -noout -in server.csr. Ensure you include the full PEM content including the -----BEGIN CERTIFICATE REQUEST----- and -----END CERTIFICATE REQUEST----- lines.
template_constraint_violation
{
"errors": [
{
"code": "template_constraint_violation",
"message": "CSR does not meet template requirements"
}
]
}
The CSR attributes do not match template constraints. Check template requirements such as key size, algorithm, or subject field requirements. Regenerate the CSR with compliant values.