> ## Documentation Index
> Fetch the complete documentation index at: https://certplane.kippel.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Enroll the agent

> One-time enrollment that binds a host to a step-ca identity certificate using a bootstrap token.

Enrollment runs once per host and produces the `(identity.key, identity.cert)` pair the agent uses for every subsequent broker call.

## What enrollment does

`certplane-agent -c <config> enroll`:

1. Acquires a file lock at `<state_dir>/agent.lock`.
2. Refuses to run if `identity.cert` already exists.
3. Generates an ECDSA P-256 key at `identity.key` (mode `0600`) if missing, otherwise reuses it.
4. Reads the bootstrap token at `identity.bootstrap_token`.
5. Builds a CSR with `CN = identity.name` and submits it to `step-ca` at `identity.step_ca.url`, verifying the server using `identity.step_ca.fingerprint` or `identity.step_ca.root_ca_bundle`.
6. Writes the returned identity certificate to `identity.cert` and **removes the bootstrap token file**.

The lock and the existence check make `enroll` safe to run from Ansible — re-runs against an already-enrolled host fail fast.

## Generate a bootstrap token

On your `step-ca` host:

```bash theme={null}
step ca token edge01.h.int.example.com
```

The CN you pass must match `identity.name` in the agent config. Tokens are single-use and short-lived.

## Place the token on the host

```bash theme={null}
install -o certplane -g certplane -m 0600 /dev/stdin /etc/certplane/bootstrap-token <<< "$TOKEN"
```

The Ansible role does this with `no_log: true` and only when the identity certificate is missing.

## Agent identity config

The minimum fields needed for enrollment:

```yaml theme={null}
state_dir: /var/lib/certplane/agent

identity:
  name: edge01.h.int.example.com
  provider: step-ca
  cert: /var/lib/certplane/agent/identity.crt
  key:  /var/lib/certplane/agent/identity.key
  issuer_ca_bundle: /etc/certplane/ca/agent-identity-ca-bundle.crt
  bootstrap_token: /etc/certplane/bootstrap-token
  step_ca:
    url: https://ca.int.example.com
    fingerprint: "sha256:aabbcc..."
    # OR root_ca_bundle: /etc/certplane/ca/step-ca-root.crt
    timeout: 10s
```

You need either `step_ca.fingerprint` or `step_ca.root_ca_bundle`. The full schema is in [Agent configuration](/configuration/agent).

## Run enrollment

```bash theme={null}
certplane-agent -c /etc/certplane/agent.yml enroll
```

On success, the agent writes `identity.crt` and deletes the bootstrap token. The host is now ready to run the renewal loop.

## Validate the config without enrolling

```bash theme={null}
certplane-agent -c /etc/certplane/agent.yml check
```

`check` validates the YAML, applies defaults, runs the same `Validate()` logic the agent uses at startup, and confirms expected files are reachable. It does not contact `step-ca` or the broker.

## Troubleshooting

<AccordionGroup>
  <Accordion title="identity cert already exists at ...">
    Enrollment is one-shot. To re-enroll, delete `identity.cert` and `identity.key`, generate a fresh bootstrap token, and run `enroll` again.
  </Accordion>

  <Accordion title="reading bootstrap token: no such file or directory">
    The token file at `identity.bootstrap_token` is missing. With Ansible, this means the `certplane_agent_bootstrap_token` variable was not set on this host.
  </Accordion>

  <Accordion title="bootstrap token is empty">
    The token file exists but is whitespace-only. Regenerate it with `step ca token ...`.
  </Accordion>

  <Accordion title="enrolling with identity CA: x509: certificate signed by unknown authority">
    The `step-ca` server cert is not trusted. Either set `identity.step_ca.fingerprint` to the SHA-256 of the `step-ca` root, or set `identity.step_ca.root_ca_bundle` to a PEM file containing it.
  </Accordion>

  <Accordion title="enrollment succeeded but the next run fails authenticating to the broker">
    The broker's `server.mtls.agent_ca_bundle` must trust the CA that signed `identity.cert`. If you rotated the `step-ca` intermediate after deploying the broker, redeploy the bundle.
  </Accordion>
</AccordionGroup>

## Next: run the agent

[Running the agent](/setup/agent-run) covers the `run` subcommand, the `systemd` timer, and the renewal model.
