Skip to main content

Install and upgrade an agent

Goal

Install the native TokenTimer agent on a host that holds certificates, so renewals, deployments, reloads, and verification run automatically under your control.

Which integration do you want?

This page covers the native agent, which does the certificate work for you. If you only want to report on renewals your own scripts already perform, use Connect an external executor instead.

What gets installed

The agent is a zero-dependency Node.js daemon. It is outbound-only: it opens HTTPS connections to TokenTimer and never listens on a port, so it needs no inbound firewall rule.

PathPurpose
/opt/tokentimer-agent/appThe agent package, read-only at runtime
/opt/tokentimer-agent/stateConfig dir, mode 0700, owned by the agent user
.../state/config.jsonAgent configuration, mode 0600
.../state/credentialPer-agent credential, written by the agent at registration
.../state/bootstrap.envBootstrap token, deleted automatically after first successful registration
.../state/acme/certbot and acme.sh config, work, and log directories
/etc/systemd/system/tokentimer-agent.service.d/override.confGenerated ReadWritePaths allowlist
/etc/polkit-1/rules.d/50-tokentimer-agent.rulesOptional, only when you allow service reloads

The systemd unit is hardened (ProtectSystem=strict, NoNewPrivileges=true), and the agent runs as a dedicated tokentimer-agent system user rather than root.

Requirements

RequirementDetail
OSLinux with systemd. The installer generates a systemd unit and a polkit rule, so it does not support non-systemd Linux, macOS, or Windows.
RuntimeNode.js 22 or later (>=22.0.0 <25.0.0)
Privilegessudo/root to run the installer (creates a system user, unit file, and directories)
NetworkOutbound HTTPS from the host to TokenTimer. No inbound access needed.
PlanA plan that includes CertOps agents
Non-systemd hosts

There is no packaged installer for macOS, Windows, or systemd-less Linux. The agent itself is portable Node.js and can be run manually (node /path/to/app/bin/tokentimer-agent.js with TOKENTIMER_AGENT_CONFIG_DIR set), but you are then responsible for the service supervision, sandboxing, and file permissions that the systemd unit normally provides.

Step 1 - Run the installer

Copy the agent package to the host, then run the installer from the package directory. It does not need a token yet: it pauses at a hidden interactive prompt asking for one, so the secret never reaches your shell history or the process list. Create the token in step 2 below and paste it there.

sudo ./scripts/install-agent.sh \
--api-url https://app.tokentimer.ch \
--workspace-id <your workspace id>

The installer prompts for the bootstrap token when neither TOKENTIMER_AGENT_BOOTSTRAP_TOKEN nor --bootstrap-token is set. For unattended installs, prefer the environment variable instead of waiting on the prompt:

sudo TOKENTIMER_AGENT_BOOTSTRAP_TOKEN=ttboot_... ./scripts/install-agent.sh \
--api-url https://app.tokentimer.ch \
--workspace-id <your workspace id>
Avoid --bootstrap-token on shared hosts

It works, but arguments are visible in process listings while the installer runs. The prompt and the environment variable both avoid that exposure.

Run it with --dry-run first to print every action without touching the system:

sudo ./scripts/install-agent.sh --api-url https://app.tokentimer.ch \
--workspace-id <id> --dry-run

Useful options

OptionUse it for
--write-path PATHAn absolute directory the agent may write certificates into. Repeatable. Never grants all of /etc.
--write-paths-file FILEOne absolute path per line, merged with --write-path
--reload-service NAMEAllow systemctl reload for nginx, apache/apache2, httpd, or haproxy via a generated polkit rule
--ca-bundle PATHPEM bundle when the agent must trust an extra CA (for example a TLS-inspecting egress proxy)
--allow-insecure-local-httpNot applicable on Cloud; loopback-only development option
--uninstallStop and remove the service, app dir, unit, drop-in, and polkit rule

A typical nginx host:

sudo ./scripts/install-agent.sh \
--api-url https://app.tokentimer.ch \
--workspace-id <id> \
--write-path /etc/letsencrypt \
--write-path /etc/nginx/certs \
--reload-service nginx
ReadWritePaths is not the same as filesystem permissions

--write-path only lets the systemd sandbox reach a directory. The tokentimer-agent user must still be able to write there. Grant that before enabling deploy jobs, for example:

sudo setfacl -m u:tokentimer-agent:rwx /etc/nginx/certs

Without this, deploys fail with a permission error even though the path is allowlisted.

Polkit is used rather than sudoers deliberately: the unit keeps NoNewPrivileges=true, under which sudo cannot escalate at all.

Step 2 - Create a bootstrap token

  1. Open Certificate operations at /certops/operations.
  2. In the Deploy an agent panel, create a bootstrap token.
  3. Copy it and paste it at the installer's prompt from step 1. It is a single-use ttboot_ token, shown exactly once, and it is consumed at the agent's first registration.

Step 3 - Verify registration

systemctl status tokentimer-agent
journalctl -u tokentimer-agent -f

You should see the agent register and begin heartbeating, and the Agent fleet panel on the Certificate operations page flip it to registered. The fleet panel shows status, last heartbeat, clock drift, and version compatibility.

Confirm the bootstrap token file is gone once registration succeeded:

sudo ls -l /opt/tokentimer-agent/state/

bootstrap.env should no longer be present; the agent removes it after the first successful registration and keeps only the per-agent credential. Removal is best-effort, so if the file is still there (for example because of a read-only or oddly-permissioned state dir), delete it yourself once the agent is registered.

Step 4 - Start in dry-run, then enable execution

The agent ships with execution.dryRun set to true and execution.enabled set to false, so a fresh install exercises the whole trust chain (claim, signature verification, policy evaluation, planning) and reports what it would do, with no filesystem or process side effects.

Leave it that way until you have seen a job plan correctly, then enable real execution in config.json:

{
"execution": {
"enabled": true,
"dryRun": false
}
}

Restart the agent after editing: sudo systemctl restart tokentimer-agent.

Upgrading

The agent does not self-update, which is deliberate: an execution-plane process that can update itself is an execution-plane process an attacker can update.

  1. Copy the new package to the host.
  2. Re-run install-agent.sh with the same flags. The state directory, credential, and keys are preserved, so the agent keeps its identity and does not need a new bootstrap token.
  3. Confirm the new version in the Agent fleet panel.

To force a fleet upgrade, contact support so the minimum accepted agent version can be raised for your workspace; older agents are then blocked at registration and heartbeat rather than silently continuing to claim work.

Retiring and uninstalling

Retire the agent from the Agent fleet panel first. A retired agent exits cleanly on its next heartbeat instead of being killed mid-job. Then, on the host:

sudo ./scripts/install-agent.sh --uninstall

This removes the service, app directory, unit, drop-in, and polkit rule. The state directory and system user are preserved so you cannot destroy keys by accident. Remove them deliberately once you are sure:

sudo rm -rf /opt/tokentimer-agent
sudo userdel tokentimer-agent

Troubleshooting

SymptomLikely causeFix
Installer refuses the API URLURL is not https:Use https://app.tokentimer.ch
Registration fails as unauthorizedBootstrap token already used, expired, or revokedTokens are single-use; create a new one in the dashboard
Registration fails and CertOps routes 404CERTOPS_ENABLED is off platform-wide, or the API URL/route path is wrongContact support to confirm CertOps availability; double-check the API URL passed to the installer
Agent blocked at registration or heartbeatVersion outside the accepted windowUpgrade the agent to the current release
TLS handshake failure to TokenTimerEgress proxy performing TLS inspection with a CA the agent does not trustReinstall with --ca-bundle, or set caBundlePath in config.json
Deploy fails with a permission errorPath allowlisted but not writable by the agent userGrant write access (setfacl) to tokentimer-agent
Reload failsService not allowlisted at install timeReinstall with --reload-service NAME
Clock drift warnings in the fleet panelHost clock skewFix NTP; signed jobs enforce a validity window
Jobs plan but never change anythingStill in dry-runSet execution.enabled: true and execution.dryRun: false, then restart