Agent configuration reference
Where the config lives
The agent reads config.json from its state directory, which the installer creates at /opt/tokentimer-agent/state/config.json with mode 0600, owned by the tokentimer-agent user.
The directory is resolved in this order:
- an explicit path passed to the agent,
- the
TOKENTIMER_AGENT_CONFIG_DIRenvironment variable, - the OS default.
Restart the agent after editing: sudo systemctl restart tokentimer-agent.
Every field is validated at load time and a malformed value aborts startup with a descriptive error naming the field. This is deliberate: a typo in an allowlist must never be silently ignored, because silently ignoring it is what would permit unsafe work.
Connection
| Field | Type | Default | Description |
|---|---|---|---|
serverUrl | string | required | Control-plane base URL. Must be https: unless allowInsecureLocalHttp is set and the host is loopback. |
agentId | string | null | null | Set by the agent after registration. Do not hand-edit. |
protocolVersion | string | current | Agent protocol version advertised at registration. |
heartbeatIntervalMs | integer | 30000 | Heartbeat frequency. Also how quickly the agent notices retirement and signing-key rotation. |
pollIntervalMs | integer | 15000 | How often the agent polls for claimable jobs. |
caBundlePath | string | null | null | PEM CA bundle used to trust the control plane, for a private CA or a TLS-inspecting egress proxy. Overridden by TOKENTIMER_AGENT_CA_BUNDLE. |
allowInsecureLocalHttp | boolean | false | Permits plain http: for loopback hosts only. Development use. |
Capability declaration
These tell the control plane what this agent is willing and able to do, and are what job routing matches against:
| Field | Type | Default | Description |
|---|---|---|---|
declaredTargetSelectors | string[] | [] | Targets (hosts, domains) this agent handles. |
declaredCommandProfileNames | string[] | [] | Command profiles this agent offers, for example a certbot or acme.sh profile. |
Declaring a capability does not grant it. The local policy block still has to allow the concrete command, path, CA endpoint, and DNS zone.
Execution
| Field | Type | Default | Description |
|---|---|---|---|
execution.enabled | boolean | false | Master switch for doing real work. While false the agent still registers, heartbeats, and reports. |
execution.dryRun | boolean | true | Plan and report the steps a job would run, with no filesystem or process side effects. |
execution.keysDir | string | <configDir>/keys | Where locally generated private keys are written, mode 0600. Keys never leave the host. |
execution.replayStorePath | string | <configDir>/replay-store.json | Persisted nonce cache that makes replayed jobs unexecutable across restarts. |
execution.outboxDir | string | <configDir>/outbox | Durable queue for results that could not be delivered yet. |
execution.clockDriftToleranceMs | integer | 30000 | Accepted skew when validating a signed job's validity window. |
A fresh install has enabled: false and dryRun: true. Setting enabled: true alone still yields dry-run planning. Real execution requires enabled: true and dryRun: false. Validate a rollout in dry-run first; it exercises the entire trust chain (claim, signature verification, replay check, policy evaluation) without touching the host.
Policy (default-deny)
The policy block is the agent's own authorization boundary. It always wins over control-plane intent: a job that exceeds it is rejected locally, and the rejection is reported back as evidence.
| Field | Type | Description |
|---|---|---|
policy.allowedCommands | object | Map of profile name to { argv: [...] }. The argv template is the only shape the agent will execute. |
policy.allowedPaths | string[] | Absolute paths the agent may write certificates into. |
policy.allowedCaEndpoints | string[] | ACME CA endpoints the agent may talk to, including internal PKI. |
policy.allowedDnsZones | string[] | DNS zones the agent may publish challenge records in. |
policy.allowedDnsProviders | string[] | DNS provider ids, exact-match. See DNS-01 providers. |
{
"policy": {
"allowedCommands": {
"certbot-csr": { "argv": ["/usr/bin/certbot", "certonly", "--csr"] }
},
"allowedPaths": ["/etc/nginx/certs"],
"allowedCaEndpoints": ["https://acme-v02.api.letsencrypt.org/directory"],
"allowedDnsZones": ["example.com"],
"allowedDnsProviders": ["cloudflare"]
}
}
Requests to export private key material are rejected unconditionally. There is no policy field, config flag, or control-plane setting that permits it. That is the zero-custody invariant, not a default you can relax.
policy.allowedPaths and the systemd ReadWritePaths list are two independent gates, and a deploy needs both. The installer's --write-path manages the systemd side; this field manages the agent side.
Trust pinning
| Field | Type | Default | Description |
|---|---|---|---|
pinnedSigningKey | object | null | null | { signingKeyId, publicKeyPem } of the control-plane job-signing key the agent trusts. |
The agent pins the signing key on first contact and then refuses jobs signed by anything else. It is written by the agent, not by you. During a rotation the agent re-pins automatically after learning about the new key on heartbeat; see Rotate the job-signing key.
Discovery
| Field | Type | Default | Description |
|---|---|---|---|
discovery.directories | string[] | none | Directories scanned for existing certificates to report into inventory. |
discovery.intervalMs | integer | 3600000 | Scan interval. |
Discovery is read-only: it reports what it finds and never modifies or moves certificates.
DNS and ACME
| Field | Type | Default | Description |
|---|---|---|---|
dnsProviders | object | null | null | Map of provider id to { credentialsFile } plus the reserved zoneProviderMap. |
dnsPropagation | object | see below | Propagation wait behaviour after publishing and cleanup. |
acmeAccounts | object | null | null | Map of account reference to { credentialsFile }. |
Credentials are always referenced by absolute path to a 0600 file, never inlined into config.json. The agent refuses a credentials file that is group- or other-readable on POSIX hosts.
Full per-provider fields, zone routing, and propagation tuning: DNS-01 providers.
Minimal working example
{
"serverUrl": "https://tokentimer.example.com",
"declaredTargetSelectors": ["web01.example.com"],
"declaredCommandProfileNames": ["certbot-csr"],
"execution": {
"enabled": true,
"dryRun": false
},
"policy": {
"allowedCommands": {
"certbot-csr": { "argv": ["/usr/bin/certbot", "certonly", "--csr"] }
},
"allowedPaths": ["/etc/nginx/certs"],
"allowedCaEndpoints": ["https://acme-v02.api.letsencrypt.org/directory"],
"allowedDnsZones": ["example.com"],
"allowedDnsProviders": ["cloudflare"]
},
"dnsProviders": {
"cloudflare": { "credentialsFile": "/etc/tokentimer-agent/dns/cloudflare.json" }
}
}
Related
- Install and upgrade an agent - installer flags that write parts of this file.
- DNS-01 providers - provider credentials and propagation.
- Automation and executors - how jobs reach the agent.