# Register & Install the Agent

## Installation

When adding a pool, you will be asked whether the pool will be for HAProxy nodes.

**Only select the HAProxy Pool Template if the backend server(s) are currently running, or will be running, HAProxy.**

**For all other server types**—including cPanel, OpenLiteSpeed, MySql/MariaDB, and general-purpose Linux hosts—select **Generic Linux**.

Choosing the correct template ensures that the UI enables the appropriate features and management tools for that server type. If you accidentally select the wrong template, remove any members from the pool and recreate the pool on the correct template.

### Recommended: one-shot from the dashboard

<p class="callout info">You can access the UI using [https://serversctl.com/app](https://serversctl.com/app) or [https://balctl.com/app](https://balctl.com/app). Only the public websites are different.</p>

1. Register an account at [https://serversctl.com/app/](https://serversctl.com/app/)
2. Add a new pool. [https://serversctl.com/app/sites](https://serversctl.com/app/sites)
    - Servers can be pooled together. For example, cPanel Servers, LiteSpeed, MariaDB/MySQL all use the **Generic Linux template,** or HAProxy servers use the dedicated **HAProxy template**.
3. Select **Add Member.** The modal generates a paste-ready command that: 
    - Ensures `unzip` + `python3` (via apt or dnf/yum).
    - Downloads `<a href="https://download.serversctl.com/agent.zip">https://download.serversctl.com/agent.zip</a>`.
    - Runs `balctl-agent.sh --enrol --key … --hostname … --api-base …`.
    - Writes `<span class="md-inline-path-prefix">/etc/balctl/</span><span class="md-inline-path-filename">agent.env</span>`, runs `--update`, enables systemd.
4. The ServersCTL UI will now start to report agent information. 
    - See troubleshooting if you have problems.

<p class="callout success">To add further members to a pool. Keep using the Add Member button.</p>

### Files after install

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-path-role-%2Fusr%2Flocal"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><table><thead class="bg-muted/80" data-streamdown="table-header"><tr class="border-border border-b" data-streamdown="table-row"><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Path</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Role</th></tr></thead><tbody class="divide-y divide-border bg-muted/40" data-streamdown="table-body"><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">`<span class="md-inline-path-prefix">/usr/local/bin/</span><span class="md-inline-path-filename">balctl_heartbeat.py</span>`</div></td><td><div class="md-table-cell-content">Agent binary</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">`<span class="md-inline-path-prefix">/etc/systemd/system/</span><span class="md-inline-path-filename">balctl-heartbeat.service</span>`</div></td><td><div class="md-table-cell-content">systemd unit</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">`<span class="md-inline-path-prefix">/etc/balctl/</span><span class="md-inline-path-filename">agent.env</span>`</div></td><td><div class="md-table-cell-content">Secrets + config (`chmod 600`)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">`<span class="md-inline-path-prefix">/var/lib/balctl/</span>`</div></td><td><div class="md-table-cell-content">State stamps (e.g. `<span class="md-inline-path-filename">.haproxy-provisioned</span>`)</div></td></tr></tbody></table>

</div></div></div>### Bundle contents (`<span class="md-inline-path-filename">agent.zip</span>`)

Flat zip: `<span class="md-inline-path-filename">balctl_heartbeat.py</span>`, `<span class="md-inline-path-filename">balctl-agent.sh</span>`, `<span class="md-inline-path-filename">balctl-heartbeat.env.example</span>`, `<span class="md-inline-path-filename">balctl-heartbeat.service</span>`, `<span class="md-inline-path-filename">README.md</span>`, `LICENSE`, `<span class="md-inline-path-filename">INSTALL_VM.txt</span>`.

---

## Configuration (`<span class="md-inline-path-prefix">/etc/balctl/</span><span class="md-inline-path-filename">agent.env</span>`)

If your configured hostname is different from the hostname sent to ServersCTL, use `BALCTL_HOSTNAME`

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-variable-required-de"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><table><thead class="bg-muted/80" data-streamdown="table-header"><tr class="border-border border-b" data-streamdown="table-row"><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Variable</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Required</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Default</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Purpose</th></tr></thead><tbody class="divide-y divide-border bg-muted/40" data-streamdown="table-body"><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">`BALCTL_ENROLLMENT_SECRET`</div></td><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Yes</span> (heartbeat)</div></td><td><div class="md-table-cell-content">—</div></td><td><div class="md-table-cell-content">48 hex chars from modal</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">`BALCTL_API_BASE`</div></td><td><div class="md-table-cell-content">No</div></td><td><div class="md-table-cell-content">`https://serversctl.com`</div></td><td><div class="md-table-cell-content">Control plane origin</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">`BALCTL_INTERVAL_SEC`</div></td><td><div class="md-table-cell-content">No</div></td><td><div class="md-table-cell-content">`1`</div></td><td><div class="md-table-cell-content">Bootstrap interval only; control plane returns authoritative `heartbeatIntervalSec` (1–60s)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">`BALCTL_HOSTNAME`</div></td><td><div class="md-table-cell-content">No</div></td><td><div class="md-table-cell-content">OS hostname/FQDN</div></td><td><div class="md-table-cell-content">Override reported hostname</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">`BALCTL_DECLARE_IP`</div></td><td><div class="md-table-cell-content">No</div></td><td><div class="md-table-cell-content">—</div></td><td><div class="md-table-cell-content">Fixed IPv4 in heartbeat JSON (for DNS failover)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">`BALCTL_PROBE_PUBLIC_IP`</div></td><td><div class="md-table-cell-content">No</div></td><td><div class="md-table-cell-content">off</div></td><td><div class="md-table-cell-content">Discover public IPv4 via HTTPS each heartbeat</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">`BALCTL_PUBLIC_IP_URL`</div></td><td><div class="md-table-cell-content">No</div></td><td><div class="md-table-cell-content">`https://api.ipify.org`</div></td><td><div class="md-table-cell-content">Probe URL</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">`BALCTL_UPDATE_URL`</div></td><td><div class="md-table-cell-content">No</div></td><td><div class="md-table-cell-content">`https://download.serversctl.com/agent.zip`</div></td><td><div class="md-table-cell-content">Self-update zip</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">`BALCTL_PROVISION_HAPROXY`</div></td><td><div class="md-table-cell-content">No</div></td><td><div class="md-table-cell-content">off</div></td><td><div class="md-table-cell-content">One-shot HAProxy install on first start (root)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">`BALCTL_ALLOW_AGENT_DOWNGRADE`</div></td><td><div class="md-table-cell-content">No</div></td><td><div class="md-table-cell-content">off</div></td><td><div class="md-table-cell-content">Allow installing older agent from zip (not recommended)</div></td></tr></tbody></table>

</div></div></div>Systemd loads this via `EnvironmentFile=/etc/balctl/agent.env`. Manual `sudo python3 …` runs merge missing vars from the same file.