Add a TLS certificate
You upload a certificate or request one through ACME, then assign it to a site domain. External DNS ACME lets you publish the returned challenge with your DNS provider.
Note: Balancer is in beta. Endpoints, fields and behavior may change before general availability, and the feature must be enabled on your account before use. Contact your NetActuate account manager to request access.
Prerequisites
- Balancer access and a key in the BALANCER_API_KEY environment variable with permission for these operations.
- The API host shown in the Balancer portal. Replace
<your-api-host>in every example with that host. - A shell, curl and Python 3 for examples that build JSON from environment variables.
- Control of example.com, an HTTP group and domain, and local PEM certificate and private-key files for the upload option. Set DOMAIN_ID from your domain readback.
Resource identifiers come from your API responses. Set each variable when its value is available, before the first step that uses it.
| Environment variable | Value |
|---|---|
CERTIFICATE_PEM | Your local PEM contents. |
DOMAIN_ID | Returned resource identifier, or your selected scope identifier. |
GROUP_ID | Returned resource identifier, or your selected scope identifier. |
HTTP_LB_ID | Returned resource identifier, or your selected scope identifier. |
PRIVATE_KEY_PEM | Your local PEM contents. |
SSL_CERTIFICATE_ID | Returned resource identifier, or your selected scope identifier. |
For an identifier, enter the value returned for your resource and export it:
read -r -p 'DOMAIN_ID: ' DOMAIN_ID
export DOMAIN_ID
Read the upload files into environment variables before choosing the upload option:
export CERTIFICATE_PEM="$(cat certificate.pem)"
export PRIVATE_KEY_PEM="$(cat private-key.pem)"
Steps
-
Choose upload: read your local PEM files and submit them. Set CERTIFICATE_PEM and PRIVATE_KEY_PEM to their contents.
python3 - <<'PYBODY' | \
curl --silent --show-error --include \
--request POST \
--url "https://<your-api-host>/ssl-certificates" \
--header "x-api-key: $BALANCER_API_KEY" \
--header "Content-Type: application/json" \
--data-binary @-
import json, os
print(json.dumps({'domain': 'example.com', 'doLetsEncrypt': False, 'certificate': os.environ['CERTIFICATE_PEM'], 'privateKey': os.environ['PRIVATE_KEY_PEM']}))
PYBODYAPI reference: Creates an SSL certificate
Expected: HTTP 200. The following shows the response fields to check; additional data depends on your saved resources. Resource identifiers are illustrative.
{
"code": 200,
"data": {
"sslCertificateId": 1
}
}Set SSL_CERTIFICATE_ID to the returned sslCertificateId.
-
Or choose managed ACME publication instead of upload.
curl --silent --show-error --include \
--request POST \
--url "https://<your-api-host>/ssl-certificates" \
--header "x-api-key: $BALANCER_API_KEY" \
--header "Content-Type: application/json" \
--data-binary '{"domain":"example.com","doLetsEncrypt":true,"dnsChallengeMode":"managed"}'API reference: Creates an SSL certificate
Expected: HTTP 200. The following shows the response fields to check; additional data depends on your saved resources. Resource identifiers are illustrative.
{
"code": 200,
"data": {
"sslCertificateId": 1
}
}Set SSL_CERTIFICATE_ID to the returned sslCertificateId.
-
Or choose external DNS publication instead of either previous option.
curl --silent --show-error --include \
--request POST \
--url "https://<your-api-host>/ssl-certificates" \
--header "x-api-key: $BALANCER_API_KEY" \
--header "Content-Type: application/json" \
--data-binary '{"domain":"example.com","doLetsEncrypt":true,"dnsChallengeMode":"external"}'API reference: Creates an SSL certificate
Expected: HTTP 200. The following shows the response fields to check; additional data depends on your saved resources. Resource identifiers are illustrative.
{
"code": 200,
"data": {
"sslCertificateId": 1
}
}Set SSL_CERTIFICATE_ID to the returned sslCertificateId.
-
Read issuance state. For external mode, publish exactly the returned dnsChallenge.recordName and dnsChallenge.recordValue as a TXT record at your DNS provider.
curl --silent --show-error --include \
--request GET \
--url "https://<your-api-host>/ssl-certificates/$SSL_CERTIFICATE_ID" \
--header "x-api-key: $BALANCER_API_KEY"API reference: Get SSL certificate details
Expected: HTTP 200. The following shows the response fields to check; additional data depends on your saved resources. Resource identifiers are illustrative.
{
"code": 200,
"data": {
"sslCertificateId": 1
}
}Inspect isReady, acmeStatus, failedReason and dnsChallenge. TODO(verify): Provider-specific commands for publishing an external DNS challenge.
-
After confirming public TXT answers, request another challenge check.
curl --silent --show-error --include \
--request POST \
--url "https://<your-api-host>/ssl-certificates/$SSL_CERTIFICATE_ID/recheck-challenge" \
--header "x-api-key: $BALANCER_API_KEY"API reference: Request an ACME DNS challenge recheck
Expected: HTTP 200. The following shows the response fields to check; additional data depends on your saved resources. Resource identifiers are illustrative.
{
"code": 200,
"data": {
"sslCertificateId": 1
}
} -
When the certificate is ready, assign it to your domain.
python3 - <<'PYBODY' | \
curl --silent --show-error --include \
--request PATCH \
--url "https://<your-api-host>/http-loadbalancers/$HTTP_LB_ID/groups/$GROUP_ID/domains/$DOMAIN_ID" \
--header "x-api-key: $BALANCER_API_KEY" \
--header "Content-Type: application/json" \
--data-binary @-
import json, os
print(json.dumps({'sslCertificateId': int(os.environ['SSL_CERTIFICATE_ID'])}))
PYBODYAPI reference: Update an HTTP group domain configuration
Expected: HTTP 200. The following shows the response fields to check; additional data depends on your saved resources. Resource identifiers are illustrative.
{
"code": 200,
"result": "success",
"data": {
"domainId": 1
}
} -
Read the domain assignment.
curl --silent --show-error --include \
--request GET \
--url "https://<your-api-host>/http-loadbalancers/$HTTP_LB_ID/groups/$GROUP_ID/domains/$DOMAIN_ID" \
--header "x-api-key: $BALANCER_API_KEY"API reference: Get HTTP group domain
Expected: HTTP 200. The following shows the response fields to check; additional data depends on your saved resources. Resource identifiers are illustrative.
{
"code": 200,
"result": "success",
"data": {
"domainId": 1
}
}
Verify
Inspect the certificate presented by https://example.com. Confirm its domain coverage and expiry. For external ACME, compare authoritative TXT answers with the exact returned challenge. Certificate readiness and live deployment are separate checks.
Use your configured site domain in this traffic check:
curl --silent --show-error --include "https://example.com/"
Expected: the site behavior described above. The application response body and status depend on your origin and selected policies.
An API mutation saves desired configuration first. Readback and a live traffic check answer different questions. No fixed propagation time is established.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Challenge remains pending | TXT name/value, delegation, TTL or CAA prevents validation | Correct public DNS and use recheck-challenge. |
| Wrong certificate is presented | Domain assignment or deployment differs | Read the domain certificate assignment and inspect another controlled TLS connection. |
| Upload is rejected | Certificate and key are not accepted | Check the PEM material and domain coverage; keep private material out of support messages. |
Limits and defaults
| Setting | Limit or default |
|---|---|
| Domain | Maximum 255 characters |
| Certificate name | Maximum 64 characters |
| ACME default | doLetsEncrypt defaults to true |
| DNS challenge mode | Default managed; managed or external |
| Issuance and deployment time | No fixed deadline is established |
API status codes
| Status | Meaning |
|---|---|
| 200 | Request succeeds; inspect returned data. |
| 400 | Invalid arguments or configuration. |
| 401 | Authentication or authorization denied. |
| 404 | Resource or parent is unavailable. |
| 409 | Resource state conflicts with the operation. |
| 412 | Inspect the returned error details. |
| 500 | The request encounters a server error. |
Related API operations
DELETE /ssl-certificates/{sslCertificateId}GET /http-loadbalancers/{httpLbId}/groups/{groupId}/domains/{domainId}GET /ssl-certificatesGET /ssl-certificates/dns-challengesGET /ssl-certificates/{sslCertificateId}PATCH /http-loadbalancers/{httpLbId}/groups/{groupId}/domains/{domainId}PATCH /ssl-certificates/{sslCertificateId}POST /ssl-certificatesPOST /ssl-certificates/{sslCertificateId}/recheck-challengePOST /ssl-certificates/{sslCertificateId}/renew
API reference
Need Help?
Contact your account manager or use the Balancer portal.