Configuring Certificates on the APIsec Private Hosted Agent
Audience: APIsec customers deploying a private hosted agent against APIs that require client-certificate authentication (mTLS), a custom Certificate Authority (CA), or both.
Version: 1.0 — June 2026
Overview
The APIsec Private Hosted Agent supports certificate-based reachability and authentication for scan targets that:
- require mutual TLS (mTLS) - where the API server expects the client (the hosted agent) to present a client certificate.
- are served by a private Certificate Authority (CA) - where the API server's TLS certificate is signed by a CA that the hosted agent does not trust by default. or
- require both, which is common in enterprise environments where the gateway terminates TLS with a private CA and also enforces mTLS upstream.
Certificate handling is opt-in and is configured at container start time. Once enabled, the agent loads a JSON configuration file that maps each scan-target host to the certificate(s) the agent should present and trust when reaching it.
What you need before you start
| Item | Notes |
|---|---|
| The hosted agent's install command from the APIsec dashboard | "Download Script" under the Hosted Agents page. This is your starting point — you will add two extra lines to it. |
| A persistent location to hold your certificates | Two options: (a) a directory on the Docker host (e.g. /opt/apisec/certs on Linux, C:\apisec\certs on Windows) for single-host deployments; OR (b) an AWS EFS-mounted volume for fleet deployments on ECS / EKS where multiple agents share the same certificate set. Certificates must live on persistent storage — manually copying them into the container (docker cp) will not persist a restart. |
| Your client certificate file (if using mTLS) | Supported formats: .pfx or .p12 (PKCS#12, password-protected). PEM client certificates are not supported today — see the file-format reference below. |
| The passphrase for the client certificate (if any) | Required for the .pfx / .p12 you exported. PKCS#12 client certificates are conventionally password-protected. |
| Your private CA bundle file (if using a private CA) | Supported formats: .pem (the usual format), or PKCS#12 (.pfx / .p12) if your CA was exported in that format. |
| Knowledge of which target hostname(s) need which certificate | The hostname only — do not include http:// or https://. If the target listens on a non-standard port (anything other than 443 for HTTPS or 80 for HTTP), include the port too (e.g. api.example.com:8443). Wildcards are supported (see below). (see below). |
Step-by-step setup
Step 1 — Prepare the certificate directory on the host
Create a directory and copy the certificate files into it.
mkdir -p /opt/apisec/certs
cp client-cert.pfx /opt/apisec/certs/
cp customer-ca.pem /opt/apisec/certs/
Step 2 — Create the config.json file in that directory
The file is a JSON array, with one entry per scan-target host. Save it as config.json at the root of the directory you just created (not in a subdirectory).
The minimal example below configures mTLS for a single target host:
[
{
"host": "api.customer.example.com",
"filename": "client-cert.pfx",
"passphrase": "your-cert-passphrase"
}
]
See Configuration scenarios below for variants.
Step 3 — Modify the hosted-agent install command
Take the install command you downloaded from the APIsec dashboard. Find the docker run line and add the following two lines to it:
| Line to add | Purpose |
|---|---|
-v "/opt/apisec/certs:/app/certificates:ro" | Mount the certificate directory into the container at the default path the agent reads from. The :ro suffix mounts it read-only — recommended, since the agent only reads certificates and never writes them. |
-e ENABLE_CERTIFICATES=true | Tell the agent to load config.json at startup. Without this flag, the agent ignores any certificates. |
Option A — docker run
The modified docker run command looks like this (additions marked):
docker run -d --name "apisechostedagent-{brokerId}" \
--restart=unless-stopped --cpus=1 --memory=2g \
-v "apisec-agent-data-{brokerId}:/tmp" \
-v "/opt/apisec/certs:/app/certificates:ro" \ # ← ADDED (read-only)
-e MICRONAUT_ENVIRONMENTS=broker \
-e MANAGER_HOST_URL="https://api.apisecapps.com/v1" \
-e BROKER_NAME="{brokerName}" \
-e BROKER_ID="{brokerId}" \
-e BROKER_TYPE=DOCKER_LINUX \
-e BROKER_VERSION="{agentVersion}" \
-e BROKER_TOKEN="$ACCESS_TOKEN" \
-e ENABLE_CERTIFICATES=true \ # ← ADDED
apisec/hostedagent:latest
Option B — docker-compose
If you orchestrate the hosted agent via Docker Compose, add the equivalent two lines to your service definition:
services:
private-agent:
image: apisec/hostedagent:latest
container_name: apisechostedagent-{brokerId}
restart: unless-stopped
volumes:
- apisec-agent-data:/tmp
- /opt/apisec/certs:/app/certificates:ro # ← ADDED (read-only)
environment:
- MICRONAUT_ENVIRONMENTS=broker
- MANAGER_HOST_URL=https://api.apisecapps.com/v1
- BROKER_NAME={brokerName}
- BROKER_ID={brokerId}
- BROKER_TYPE=DOCKER_LINUX
- BROKER_VERSION={agentVersion}
- BROKER_TOKEN=${ACCESS_TOKEN}
- ENABLE_CERTIFICATES=true # ← ADDED
volumes:
apisec-agent-data:
Option C — AWS ECS / EKS with EFS
For fleet deployments where multiple hosted agents must share the same certificate set, mount your certificates from an AWS EFS file system rather than a host directory. In your ECS task definition (or EKS pod spec), add an EFS-backed volume and mount it at /app/certificates inside the agent container; set ENABLE_CERTIFICATES=true in the container environment. This pattern ensures that any agent task scheduled on any container host sees the same config.json and certificate files.
Step 4 — Run the modified install command
If the agent is already running, stop and remove it first
(docker stop apisechostedagent-\{brokerId\} then docker rm apisechostedagent-\{brokerId\}),
then start the new container with the modified command above.
Step 5 — Verify certificates loaded successfully
Check the container logs for the certificate-manager startup messages:
docker logs apisechostedagent-{brokerId} | grep certificate-manager
You should see lines like:
[certificate-manager] mTLS certificate manager initialized. Enabled: true
[certificate-manager] [host: api.customer.example.com] Validating certificate configuration.
[certificate-manager] [host: api.customer.example.com] Loaded client certificate: client-cert.pfx
[certificate-manager] Successfully loaded 1 certificate configuration(s).
If a certificate fails validation, the agent will not start. The container exits at startup; with the default restart policy enabled, Docker will keep restarting it and the same failure will recur until the underlying issue is fixed.
Check the container's logs to see why:
docker logs apisechostedagent-{brokerId} | grep certificate-manager
docker logs returns the output from the most recent start attempt even after the container has exited. Look for [certificate-manager] lines describing the failure. Typical messages include:
- Could not load certificate ... — wrong passphrase or corrupted certificate file
- Certificate configuration file not found — config.json is missing or misplaced
- Failed to parse certificate configuration file — invalid JSON in config.json
- Only .pfx, .p12 and .pem certificates are supported — unsupported file extensions
- Certificates directory not found at '/app/certificates/' — volume mount missing or pointing at the wrong host path
Example failure logs — what to look for
Excerpts of the actual log output for common failures. The mTLS certificate manager initialized. Enabled: true line fires first in all cases; the failure lines follow. Failures fall into two behavioural buckets:
Bucket A — agent starts but mTLS is silently disabled.
The container stays up, but no client certificates or private CAs are loaded. Any subsequent scan against a mTLS-protected target will fail downstream with TLS handshake errors, not with a clear "cert not configured" error. If you see one of these signatures, the container will not restart-loop — you must restart it yourself after fixing the configuration.
_Certificates directory missing at the mounted path:_
[certificate-manager] mTLS certificate manager initialized. Enabled: true
[certificate-manager] Certificates directory not found at '/app/certificates/'.
Root cause: Path does not exist or is not accessible.
Resolution: Set certificates.basePath to a valid directory containing config.json and certificate files (.pfx, .p12, .pem).
_config.json missing from the certificates directory:_
[certificate-manager] mTLS certificate manager initialized. Enabled: true
[certificate-manager] Certificate configuration file not found at '/app/certificates/config.json'.
Root cause: config.json is missing.
Resolution: Create config.json in the certificates directory with host entries (host, filename, passphrase, caFilename, caPassphrase).
_config.json present but malformed JSON:_
[certificate-manager] mTLS certificate manager initialized. Enabled: true
[certificate-manager] Failed to parse certificate configuration file '/app/certificates/config.json'.
Root cause: Unexpected character (',' (code 44)): was expecting double-quote to start field name
Resolution: Ensure config.json is valid JSON with array of host configs. See certificates/config.json.example for format.
Bucket B — container exits at startup; Docker's restart policy loops it until the underlying issue is fixed.
These are the classic "agent won't start" failures — validation of the certificate files themselves fails and the process terminates.
_Wrong passphrase or corrupted PFX/P12:_
[certificate-manager] mTLS certificate manager initialized. Enabled: true
[certificate-manager] [host: api.customer.example.com] Validating certificate configuration.
... startup error: Could not load certificate [client-cert.pfx]. Error: keystore password was incorrect
_Certificate file listed in config.json but absent from disk:_
[certificate-manager] mTLS certificate manager initialized. Enabled: true
[certificate-manager] [host: api.customer.example.com] Validating certificate configuration.
... startup error: Certificate [client-cert.pfx] not found
_Unsupported certificate file extension (e.g. .crt, .der):_
[certificate-manager] mTLS certificate manager initialized. Enabled: true
[certificate-manager] [host: api.customer.example.com] Validating certificate configuration.
... startup error: Only .pfx, .p12 and .pem certificates are supported
_Corrupted or wrong-format PEM:_
[certificate-manager] mTLS certificate manager initialized. Enabled: true
[certificate-manager] [host: api.customer.example.com] Validating certificate configuration.
... startup error: Could not load PEM certificate [customer-ca.pem]. Error: <parse error detail>
_config.json entry missing both filename and caFilename:_
[certificate-manager] mTLS certificate manager initialized. Enabled: true
[certificate-manager] [host: api.customer.example.com] Validating certificate configuration.
... startup error: No certificate specified for host: api.customer.example.com
Fix the underlying issue. If you only edited a file on the host (e.g., config.json or a certificate), the next automatic restart picks it up; otherwise, force a restart with
docker restart apisechostedagent-{brokerId}
If the issue was with the original Docker run command (e.g., an incorrect -v mount path), stop and remove the container, then re-run the install command. Re-check the logs to confirm the success messages above appear.
The APIsec dashboard's Hosted Agents → Certificates tab also displays the loaded certificates and their expiration dates once the agent has successfully reported its certificate health back to the platform.
Configuration scenarios
Scenario A — Customer API requires mTLS only
The API server requires the agent to present a client certificate. The server's own TLS certificate is signed by a well-known public CA (no custom trust needed).
[
{
"host": "api.customer.example.com",
"filename": "client-cert.pfx",
"passphrase": "your-cert-passphrase"
}
]
Scenario B — Customer API uses a private CA only
The API server's TLS certificate is signed by an internal CA that the agent does not trust by default. The server does not require mTLS.
[
{
"host": "api.customer.example.com",
"caFilename": "customer-ca.pem"
}
]
Scenario C — Customer API requires both mTLS and a private CA
The most common enterprise case: the gateway uses a private CA and requires the client to present a certificate.
[
{
"host": "api.customer.example.com",
"filename": "client-cert.pfx",
"passphrase": "your-cert-passphrase",
"caFilename": "customer-ca.pem"
}
]
Scenario D — One CA covers all hosts (wildcard fallback)
A single internal root CA signs all of the customer's internal API endpoints. Instead of listing every host, use the * wildcard.
[
{
"host": "*",
"caFilename": "internal-root-ca.pem"
}
]
Scenario E — Mixed: one host needs mTLS, everything else needs a private CA
Specific hosts get explicit entries; the wildcard catches everything else.
[
{
"host": "api.customer.example.com",
"filename": "client-cert.pfx",
"passphrase": "your-cert-passphrase",
"caFilename": "customer-ca.pem"
},
{
"host": "*",
"caFilename": "internal-root-ca.pem"
}
]
Specific-host entries always take precedence over the wildcard. The wildcard is consulted only when no specific entry matches the target hostname.
Configuration reference
config.json schema
The file is a JSON array of objects. Each object has the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
host | string | Yes | Target hostname (and port, if non-standard). Use * as a wildcard for "any host not otherwise listed". Do not include http://, https://, or paths. Include the port only when the target API listens on a non-standard port (anything other than 443 for HTTPS or 80 for HTTP) — for example, api.example.com:8443 if the API runs on port 8443; just api.example.com if it's the default 443. The agent matches the host (and port, if present) exactly, so any mismatch here will cause the entry to be silently skipped during scanning. |
filename | string | Conditional | The client-certificate filename (for mTLS). Relative to the certificates directory. Must be present if the target requires mTLS. |
passphrase | string | Conditional | Passphrase for the client certificate. Required for password-protected .pfx/.p12. Omit or set to empty string if not needed. |
caFilename | string | Conditional | The CA bundle filename (for trusting a private CA). Relative to the certificates directory. Must be present if the target's server certificate is not signed by a publicly trusted CA. |
caPassphrase | string | Conditional | Passphrase for the CA bundle. Usually empty (most CA bundles are plain PEM). |
Each entry must have at least one of filename or caFilename set. Entries with neither are rejected at agent startup.
Supported certificate file formats
| Format | Extension | Passphrase | Notes |
|---|---|---|---|
| PKCS#12 | .pfx, .p12 | Usually required | The only format supported for client certificates. Also accepted for CA bundles if your CA was exported this way. |
| PEM | .pem | Usually not required | Supported for CA bundles only. NOT supported for client certificates. A .pem client cert may pass the agent's startup check, but the scan will fail at runtime. |
Other formats (.cer, .crt, .der, .jks) are not supported — convert them to one of the formats above before use.
Environment variables
| Variable | Default | Purpose |
|---|---|---|
ENABLE_CERTIFICATES | false | Set to true to activate the certificate manager. Without this, config.json is ignored entirely. |
CERTIFICATES_BASE_PATH | /app/certificates/ | Directory inside the container where config.json and the certificate files live. Override only if you need to mount the directory at a different path. The trailing / is part of the default — preserve it. |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Log says Certificates directory not found at /app/certificates/ | Volume mount missing or pointing at the wrong host path | Verify the -v "/host/path:/app/certificates" line in the docker run command. The host path must exist and contain config.json. |
Log says Certificate configuration file not found | config.json is missing or in a subdirectory of the mounted volume | config.json must be at the root of the mounted directory, not nested. |
Log says Failed to parse certificate configuration file | Invalid JSON syntax | Use a JSON validator. Common causes: trailing comma, missing quotes, wrong file encoding. |
Log says Could not load certificate [...]. Error: keystore password was incorrect | Wrong passphrase | Check the passphrase value and re-test. |
Log says Hostname should not start with 'http://' or 'https://' | Scheme included in host | Remove the scheme — provide hostname only. |
Log says Only .pfx, .p12 and .pem certificates are supported | Unsupported file extension | Convert your certificate to one of the supported formats — .pfx or .p12 for client certificates; .pem only for CA bundles. The startup check accepts .pem for either type, but the scan will fail at runtime if .pem is used as a client certificate. |
Log says No certificate for specified host: ... | An entry has neither filename nor caFilename set | Set at least one. |
| Agent starts but scan to a specific host still fails with TLS handshake errors | The hostname (and port, if non-standard) in config.json does not exactly match what the agent connects to. Common causes: hostname mismatch (api.customer.com vs api1.customer.com); or the target URL has a non-standard port (api.customer.com:8443), but the host only includes the hostname. | Use the exact hostname (with :port if non-default), or use the * wildcard if a single entry should cover multiple hosts. |
Agent loaded certs correctly but does not pick up changes to config.json | Certificates are loaded once at container startup | Restart the container after changing config.json or any certificate file: docker restart apisechostedagent-{brokerId} |
Frequently Asked Questions
Q. Can I add or change certificates without restarting the agent?
No. Certificates and config.json are loaded once when the container starts. After any change, run docker restart apisechostedagent-{brokerId} to pick up the new configuration.
Q. Can I combine a client certificate with a Bearer Token or API Key?
Yes. The client certificate is used for the TLS handshake (mTLS), and the Bearer Token / API Key is sent in the request Authorization header. Both authentication mechanisms work together without conflict — the certificate authenticates the agent's transport, and the token authenticates the request. Configure the token under the application's authentication profile in the dashboard as usual; the agent will present the matching certificate from config.json for the target host AND inject the token header.
Q. What happens if I have multiple hosted agents in my fleet and only one of them has the certificate mounted?
The APIsec platform does not inspect each agent's certificate set when choosing where to route a scan. Routing is based on (a) the preferred agent if one is configured on the instance, and (b) the last-known reachability record otherwise.
In a fleet where only some agents have the certificates mounted, the customer needs to make sure the agent that will run the scan is the one with certificates available:
- Preferred agent. If a preferred agent is set on the instance, either clear it or set it to the agent with the certificates mounted. The platform will continue routing scans through the preferred agent — it does not skip agents that lack certs.
- Reachability. Re-run the reachability check on the instance after adding certificates. If reachability was confirmed by a different agent, or confirmed before the certs were mounted, the platform's routing record is stale and scans may still land on an agent without the cert.
An agent without the required certificate will fail the TLS handshake; there is no automatic retry through a different agent. Both steps above are the customer's responsibility before scanning a certificate-protected target in a mixed-coverage fleet.
Q. Will the certificates persist across container restarts and re-deploys?
Yes — only if mounted via a Docker volume or an EFS-mounted volume as described above. If you manually copy certificates into a running container with docker cp, they will be lost when the container stops. Always use the -v mount (or the Compose/ECS/EKS equivalent) so that container restarts, image upgrades, and host reboots do not result in losing access to your certificates.
Q. Where can I see the certificates the agent has loaded and when they expire?
In the APIsec dashboard, open the Hosted Agents page, expand the agent, and select the Certificates tab. The dashboard receives a periodic certificate health report from the agent and surfaces the certificate's subject, issuer, and expiration date.
Q. What happens if a certificate expires while the agent is running?
The agent will continue to use the certificate until the API server rejects it. APIsec strongly recommends rotating certificates before their expiration date — the Certificates tab in the dashboard shows days-to-expiry and will alert when a certificate is approaching expiry.
Q. Can I store multiple client certificates for different hosts?
Yes. Add one entry per host in config.json, each referencing a different filename. All certificate files live in the same mounted directory.
Q. Are passphrases stored securely?
Passphrases live in plain text inside config.json, which is read from the mounted host directory. APIsec recommends restricting host-filesystem permissions on the directory (chmod 600 config.json, chown to the user that runs Docker) and treating the directory the same way you would treat any other secret-bearing path.
Q. Does the certificate manager work for both Linux and Windows hosted-agent containers?
Yes. The environment variables and config.json schema are identical across both. The only difference is the host-path syntax in the volume mount (/opt/apisec/certs on Linux, C:\apisec\certs on Windows).
Q. Does this affect the agent's outbound connection to the APIsec SaaS platform?
No. Certificate configuration applies only to the agent's outbound connections to your scan targets. The agent's own connection to the APIsec management plane uses the platform's standard TLS — no custom certificate configuration needed there.
Q. What if my target host has a wildcard certificate (e.g. *.customer.com)?
The host field in config.json matches the exact hostname of the target API, not the certificate's Common Name or SAN. If you have multiple subdomains under one wildcard certificate and they all share the same client cert / CA trust, use the * wildcard in config.json (or list each subdomain explicitly).
Support
If certificate configuration is failing and the log messages do not point to a clear fix, contact your APIsec Customer Success representative with:
- The output of docker logs apisechostedagent-{brokerId} | grep certificate-manager
- The (redacted) config.json file — replace passphrases with *** before sharing
- The Docker inspect output for the container (docker inspect apisechostedagent-{brokerId} | grep -A2 Mounts) so we can confirm the volume mount is correct