Skip to content

Add servers without the dashboard

Installing the agent normally starts in the dashboard: Add server, copy the command, run it. When servers come and go on their own — Terraform applies, autoscaling groups, golden images — use one of two mechanisms instead.

Create, then install Enrollment key
Who knows about the host first Your automation, which calls the API Nobody: the host registers itself when it boots
Key on the host The per-server agent key only An enrollment key
Best for Terraform, Ansible, CI Images, cloud-init, autoscaling groups

Mechanism 1: create the server, then run its install command

Section titled “Mechanism 1: create the server, then run its install command”

POST /v1/servers creates the server record and returns the agent’s key and the exact install command. It needs a key with the servers:write scope (the Automation preset).

Terminal window
curl -fsS -X POST https://api.monitorable.io/v1/servers \
-H "Authorization: Bearer $MONITORABLE_API_KEY" \
-H "Idempotency-Key: web-01-2026-11" \
-H "Content-Type: application/json" \
-d '{"name": "web-01"}'

The 201 response carries the server, the agent_api_key (shown once) and the install_command to run as root on the host. If the plan’s server limit is reached the call answers 402 plan_limit_reached.

Send an Idempotency-Key (up to 64 characters) so a retried call does not create a second server: repeating it with the same body returns the same server with 200, without an agent_api_key or an install_command (both fields are omitted from the JSON) and with a recovery hint. If the first response was lost, mint a fresh key with POST /v1/servers/{id}/agent-key:rotate. See Rate limits and retries.

Pair it with DELETE /v1/servers/{id} when the host goes away. With Terraform, the monitorable_server resource does both: it creates the server, keeps the agent key and install command, and deletes the server when you destroy it. The provider sends the Idempotency-Key for you. Run the install command on the host with a remote-exec provisioner:

resource "monitorable_server" "web" {
name = "web-01"
}
resource "null_resource" "web_agent" {
depends_on = [monitorable_server.web]
triggers = {
server_id = monitorable_server.web.id
}
connection {
type = "ssh"
host = "203.0.113.10"
user = "ubuntu"
}
# install_command embeds the agent's API key and is sensitive. It is null
# for an imported server, so this applies only to servers Terraform created.
provisioner "remote-exec" {
inline = [monitorable_server.web.install_command]
}
}

The Terraform provider guide covers installing the provider, the scopes the key needs, alert templates, maintenance mutes, import, and keeping the state safe.

An enrollment key can register servers and nothing else. Create one under Team → API keys with the Enrollment preset, and put it in the image or the user data. Each host then calls Monitorable when it first boots, receives its own agent key, writes it to /etc/monitorable/agent.env exactly as the dashboard’s command does, and starts the agent.

Run the installer as root with the key:

Terminal window
curl --proto '=https' --tlsv1.2 -fsSL https://get.monitorable.net/install.sh | sudo sh -s -- --enroll-key=<enrollment-key>

Each run creates a new server. That is deliberate: hostnames are not unique in every fleet and a machine id is identical on cloned images, so Monitorable never guesses which host this is. Re-running the command on a host that already runs the agent adds another server and leaves the old one stale — to update an enrolled host, re-run the key-free command from Updating and uninstalling.

Put the instance id into --server-ref and each instance re-enrolls as itself instead of adding a server. Use this as the instance’s user data; it works on any cloud that runs cloud-init, as long as each instance has its own instance id (an image cloned with a fixed NoCloud instance-id would make hosts share one):

## template: jinja
#cloud-config
# cloud-init fills in the instance id below
runcmd:
- "curl --proto '=https' --tlsv1.2 -fsSL https://get.monitorable.net/install.sh | sh -s -- --enroll-key=<enrollment-key> --server-ref={{ v1.instance_id }}"

The first line must be exactly ## template: jinja: it is what makes cloud-init replace {{ v1.instance_id }} before running the command. cloud-init runs commands as root, so there is no sudo.

--server-ref: the host’s stable identity

Section titled “--server-ref: the host’s stable identity”

--server-ref is the identifier your automation already has for the host: an instance id, a Terraform resource id, an inventory name. Monitorable stores it with the server.

  • When the installer runs with a --server-ref the organization has not seen, it creates the server.
  • When the organization already has a server with that reference, the installer re-enrolls it: same server, new agent key.
  • Without --server-ref, every run creates a new server. There is no default, by design.

The installer accepts a reference of up to 128 characters made of letters, digits, ., _, :, / and -; use that form. (The API itself only refuses whitespace and control characters and more than 128 characters.) A reference is unique per organization: the same value in two organizations is two servers. Use something that is unique across everything the organization monitors and that stays the same for the life of the host:

Where the host runs --server-ref value
Any cloud with cloud-init {{ v1.instance_id }} (jinja user data, above)
AWS the instance-id from the IMDSv2 metadata service
Hetzner http://169.254.169.254/hetzner/v1/metadata/instance-id
DigitalOcean http://169.254.169.254/metadata/v1/id
Proxmox the VMID, prefixed with the cluster name so it stays unique (pve1-101)
Terraform self.id of the resource
Ansible inventory_hostname

On AWS, read the id with IMDSv2 and pass it in:

Terminal window
TOKEN=$(curl -fsS -X PUT http://169.254.169.254/latest/api/token \
-H "X-aws-ec2-metadata-token-ttl-seconds: 60")
REF=$(curl -fsS -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/instance-id)
curl --proto '=https' --tlsv1.2 -fsSL https://get.monitorable.net/install.sh | sh -s -- --enroll-key=<enrollment-key> --server-ref="$REF"

On Hetzner and DigitalOcean, read the id from the metadata URL in the table and pass it as --server-ref. On Hetzner:

Terminal window
REF=$(curl -fsS http://169.254.169.254/hetzner/v1/metadata/instance-id)
curl --proto '=https' --tlsv1.2 -fsSL https://get.monitorable.net/install.sh | sh -s -- --enroll-key=<enrollment-key> --server-ref="$REF"

On DigitalOcean:

Terminal window
REF=$(curl -fsS http://169.254.169.254/metadata/v1/id)
curl --proto '=https' --tlsv1.2 -fsSL https://get.monitorable.net/install.sh | sh -s -- --enroll-key=<enrollment-key> --server-ref="$REF"
- name: Enroll the host in Monitorable
ansible.builtin.shell: >-
set -o pipefail &&
curl --proto '=https' --tlsv1.2 -fsSL https://get.monitorable.net/install.sh
| sh -s -- --server-ref={{ inventory_hostname }}
args:
executable: /bin/bash
creates: /etc/monitorable/agent.env
environment:
MONITORABLE_ENROLL_KEY: "{{ monitorable_enroll_key }}"
become: true
no_log: true

creates skips the task on hosts that are already installed. Keep monitorable_enroll_key in Ansible Vault.

Option What it does
--enroll-key=<key> The enrollment key. Required to enroll.
--server-ref=<ref> The host’s stable identity (above). Optional; without it every run adds a server.
--name=<name> The server’s name in the dashboard. Defaults to the host’s hostname. The installer accepts up to 100 characters: letters, digits, spaces, ., _ and -.
MONITORABLE_ENROLL_KEY Environment variable that replaces --enroll-key, so the key is not in the process list or sudo’s log.

With the environment variable, open a root shell (sudo -s), export the key there, then run the installer:

Terminal window
export MONITORABLE_ENROLL_KEY=<enrollment-key>
curl --proto '=https' --tlsv1.2 -fsSL https://get.monitorable.net/install.sh | sh -s -- --server-ref="$REF"

When --server-ref matches a server the organization already has, the server keeps its name, alert template, mute state and history — enrolling never renames. The agent key is replaced and the previous agent key stops working, so the old installation (if it is still running) stops reporting. If two hosts enroll with the same --server-ref at the same moment, they end up with one server, and only the host that finished last holds the working key. Every enrollment, a first one or a re-enrollment, is recorded in the audit log (Team → API keys) as enroll.enroll with the server’s id. The server’s provenance (Enrolled via <key name>) is written when the server is created and does not change on a re-enrollment.

Enrolling a new server counts against the plan’s server limit. When the limit is reached the installer prints

❌ Plan limit reached: <detail from the API>
Remove a server or upgrade the plan, then re-run.

and exits with status 1, having installed nothing. Re-enrolling a known --server-ref adds no server, so it still works when the organization is at its limit. See pricing for the plans.

Enrollment is limited to 10 per minute per key, in a fixed one-minute window. A host that is over the limit waits for the Retry-After the API sends and retries; the installer keeps retrying for about four minutes. Hosts that are waiting all come back when the window resets, so one key enrolls roughly 40–50 hosts that boot at the same moment. A host that runs out of retries exits with status 1 and “re-run in a minute”, having installed nothing, and cloud-init does not run the command again. Stagger larger simultaneous boots, or give groups of hosts their own enrollment key.

The installer calls POST /v1/enroll with {"name": "…", "server_ref": "…"} (server_ref optional). The response carries server_id, name, agent_api_key (shown once), ingest_endpoint and rotated: 201 for a new server, 200 with rotated: true for a re-enrollment, whose name is the existing server’s. name is required and validated on every call, a re-enrollment included (1–100 characters after trimming), and server_ref is trimmed, at most 128 characters, with no whitespace or control characters; a bad value answers 400 invalid_argument. The key needs the servers:enroll scope and the admin role. The endpoint ignores Idempotency-Key — server_ref is what prevents duplicates. Its errors are listed on Errors.

An enrollment key can only register servers — it cannot read, rename or delete anything. The installer neither writes it to disk nor prints it. Revoke it from Team → API keys when the image or user data that carries it is retired; servers already enrolled keep reporting with their own agent keys.