# Getting Started

Get started with ServersCTL in minutes. Install the free Linux agent using the one-shot command from the control panel and begin monitoring, managing, and protecting your servers from a single unified platform.

# Introduction

#### A message from the developers.

ServersCTL started life as a solution to a problem many of us face: HAProxy is often the single point of failure in an otherwise resilient infrastructure.

The original goal was simple. Build a way to monitor HAProxy instances and automatically move traffic if a load balancer becomes unavailable without VRRP or a floating/failover IP address. Once the first working version was in place, it became clear that the underlying concept was far more powerful than we had anticipated.

By combining a lightweight agent with infrastructure-focused automation, we found ourselves solving many of the day-to-day challenges that infrastructure managers encounter. Monitoring, backups, disaster recovery, DNS failover, cPanel replication, service management, and operational visibility could all be brought together into a single platform.

[ServersCTL](https://serversctl.com) is not intended to replace the tools you already use. Instead, it aims to sit alongside them, providing practical automation and visibility where it matters most.

Today, we are releasing the Linux agent free of charge for anyone to use. Our hope is that it saves you time, reduces operational headaches, helps keep your services online, and perhaps one day saves your bacon when you need a backup or a failover plan.

We release this with no limits on the number of pools you can have or the number of servers you may manage. If you find this product useful, support us by upgrading your account, contributing ideas or even reporting a bug. Everything helps.

We hope you find it useful.

#### What the agent is

The ServerCTL/BalCTL agent is a small <span class="font-semibold">Python 3.9+</span> process (stdlib only, no pip packages) that runs on each <span class="font-semibold">pool member Linux VM</span>. It:

1. <span class="font-semibold">Heartbeats</span> to the control plane over outbound HTTPS (proves liveness, reports host/HAProxy state).
2. <span class="font-semibold">Pulls jobs</span> from the control plane after each successful heartbeat (install, reload, backup, restore, firewall, etc.).
3. <span class="font-semibold">Optionally self-updates</span> from a published zip bundle.

It is deployed as a <span class="font-semibold">systemd</span> service: `<span class="md-inline-path-filename">balctl-heartbeat.service</span>`, with secrets in `<span class="md-inline-path-prefix">/etc/balctl/</span><span class="md-inline-path-filename">agent.env</span>`.

<span class="font-semibold">Current release:</span> check `AGENT_VERSION` in `<span class="md-inline-path-prefix">agents/</span><span class="md-inline-path-filename">balctl_heartbeat.py</span>` or `python3 balctl_heartbeat.py --version`.

# Requirements

### Server Stack

#### Operating system

- <span class="font-semibold">Supported:</span> Debian/Ubuntu and <span class="font-semibold">RHEL family</span> (AlmaLinux, Rocky, CentOS).
- <span class="font-semibold">Init:</span> <span class="font-semibold">systemd</span> (required — agent is designed as a systemd unit).
- <span class="font-semibold">Architecture:</span> Linux x86\_64 (typical VPS; agent uses standard distro package managers).

#### Runtime dependencies  


<div class="ui-scroll-area" id="bkmrk-component-required-n"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><table><thead class="bg-muted/80"><tr class="border-border border-b"><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm">Component</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm">Required</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm">Notes</th></tr></thead><tbody class="divide-y divide-border bg-muted/40"><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">Python 3.9+</span></div></td><td><div class="md-table-cell-content">Yes</div></td><td><div class="md-table-cell-content">Stdlib only; on EL8 minimal images may need `python39`</div></td></tr><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">systemd</span></div></td><td><div class="md-table-cell-content">Yes</div></td><td><div class="md-table-cell-content">Service: `<span class="md-inline-path-filename">balctl-heartbeat.service</span>`</div></td></tr><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">wget</span> or <span class="font-semibold">curl</span></div></td><td><div class="md-table-cell-content">Install-time</div></td><td><div class="md-table-cell-content">Download `<span class="md-inline-path-filename">agent.zip</span>`</div></td></tr><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">unzip</span></div></td><td><div class="md-table-cell-content">Install-time</div></td><td><div class="md-table-cell-content">Extract bundle</div></td></tr><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">sudo / root</span></div></td><td><div class="md-table-cell-content">For full feature set</div></td><td><div class="md-table-cell-content">Heartbeat itself can run unprivileged; most panel jobs need root</div></td></tr></tbody></table>

</div></div></div>#### Optional packages (installed by agent jobs when needed)

<div class="ui-scroll-area" id="bkmrk-package-when-haproxy"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><table><thead class="bg-muted/80"><tr class="border-border border-b"><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm">Package</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm">When</th></tr></thead><tbody class="divide-y divide-border bg-muted/40"><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">haproxy</span></div></td><td><div class="md-table-cell-content">Install HAProxy job or `BALCTL_PROVISION_HAPROXY=1`</div></td></tr><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">socat</span></div></td><td><div class="md-table-cell-content">Admin socket drain/ready, runtime HAProxy commands</div></td></tr><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">firewalld</span></div></td><td><div class="md-table-cell-content">RHEL-family firewall jobs (auto-installed on first “Refresh rules” if missing, agent v78+)</div></td></tr><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">ufw</span></div></td><td><div class="md-table-cell-content">Debian firewall backup jobs</div></td></tr></tbody></table>

</div></div></div>### Network requirements

#### Outbound HTTPS (required)

The VM must reach:

<div class="ui-scroll-area" id="bkmrk-destination-purpose-"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><div class="ui-scroll-area"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><table><thead class="bg-muted/80"><tr class="border-border border-b"><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm">Destination</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm">Purpose</th></tr></thead><tbody class="divide-y divide-border bg-muted/40"><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">`https://serversctl.com`</span> (or your `BALCTL_API_BASE`)</div></td><td><div class="md-table-cell-content">Heartbeat, job claim/complete, backup upload/download</div></td></tr><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">`https://download.serversctl.com/agent.zip`</span></div></td><td><div class="md-table-cell-content">Self-update (default)</div></td></tr><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">`https://api.ipify.org`</span> (optional)</div></td><td><div class="md-table-cell-content">Public IPv4 discovery when `BALCTL_PROBE_PUBLIC_IP=1`</div></td></tr></tbody></table>

</div></div></div></div></div></div>All agent API traffic must use <span class="font-semibold">HTTPS</span> — the agent refuses plaintext `BALCTL_API_BASE` / `BALCTL_UPDATE_URL` (v28+).

#### Inbound (not required)

Panel-driven operations use the <span class="font-semibold">outbound job queue</span>. No inbound SSH or agent port is required if the agent runs as <span class="font-semibold">root</span> for privileged jobs.

#### IP allowlisting (enrollment)

When you create a pool member, you must supply <span class="font-semibold">at least one allowed source IPv4</span>. This is the VM’s <span class="font-semibold">outbound/egress IP</span> as seen when it calls the control plane — <span class="font-semibold">not necessarily</span> its SSH IP or the IP traffic should hit.

The control plane validates <span class="font-semibold">`CF-Connecting-IP`</span> against the enrolled allowlist on every agent request. Mismatch → <span class="font-semibold">403</span>.

#### Enrollment requirements

Before the agent can heartbeat, create the member in the dashboard (<span class="font-semibold">Add pool member</span>):

<div class="ui-scroll-area" id="bkmrk-field-requirement-ho"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><div class="ui-scroll-area"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><table><thead class="bg-muted/80"><tr class="border-border border-b"><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm">Field</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm">Requirement</th></tr></thead><tbody class="divide-y divide-border bg-muted/40"><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">Hostname</span></div></td><td><div class="md-table-cell-content">Must match the JSON `hostname` the agent sends (case-insensitive). Override with `BALCTL_HOSTNAME` if OS hostname differs.</div></td></tr><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">Allowed source IPs</span></div></td><td><div class="md-table-cell-content">One or more IPv4 addresses (comma-separated). Must include egress IP to control plane.</div></td></tr><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">Enrollment secret</span></div></td><td><div class="md-table-cell-content"><span class="font-semibold">48 hex characters</span>, no hyphens. Shown <span class="font-semibold">once</span> in the modal. <span class="font-semibold">Not</span> the member UUID on the card.</div></td></tr><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">Member template</span></div></td><td><div class="md-table-cell-content">e.g. <span class="font-semibold">HAProxy balancer</span> — determines which panel commands are available.</div></td></tr><tr class="border-border border-b"><td><div class="md-table-cell-content"><span class="font-semibold">Linux family</span></div></td><td><div class="md-table-cell-content">Debian/Ubuntu vs RHEL — affects generated install one-liner.</div></td></tr></tbody></table>

</div></div></div></div></div></div>#### Authentication model

<div class="ui-scroll-area" id="bkmrk-header%3A%C2%A0authorizatio"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content">- Header: `Authorization: Bearer <48-char-enrollment-secret>`
- Secret stored server-side as SHA-256 hash only.
- <span class="font-semibold">401</span> = wrong/unknown secret.
- <span class="font-semibold">403</span> = IP not allowlisted, or hostname mismatch, or missing `CF-Connecting-IP`.

  
</div></div></div>

# 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" id="bkmrk-path-role-%2Fusr%2Flocal"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><table><thead class="bg-muted/80"><tr class="border-border border-b"><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm">Path</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm">Role</th></tr></thead><tbody class="divide-y divide-border bg-muted/40"><tr class="border-border border-b"><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"><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"><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"><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" id="bkmrk-variable-required-de"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><table><thead class="bg-muted/80"><tr class="border-border border-b"><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm">Variable</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm">Required</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm">Default</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm">Purpose</th></tr></thead><tbody class="divide-y divide-border bg-muted/40"><tr class="border-border border-b"><td><div class="md-table-cell-content">`BALCTL_ENROLLMENT_SECRET`</div></td><td><div class="md-table-cell-content"><span class="font-semibold">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"><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"><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"><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"><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"><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"><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"><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"><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"><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.

# HAProxy Server Pools

# Overview & concepts

### What is an HAProxy pool?

An <span class="font-semibold" data-streamdown="strong">HAProxy pool</span> is a ServerCTL deployment preset for the <span class="font-semibold" data-streamdown="strong">edge tier</span>: public DNS, one or more enrolled Linux VMs running HAProxy, and optional automatic promotion when the active host fails.

ServerCTL is the <span class="font-semibold" data-streamdown="strong">control plane</span>. It does not terminate customer traffic itself. It:

- Enrols VMs via the <span class="font-semibold" data-streamdown="strong">ServersCTL agent</span>
- Publishes a managed <span class="font-semibold" data-streamdown="strong">A record</span> through Cloudflare or cPanel/WHM
- Tracks <span class="font-semibold" data-streamdown="strong">heartbeats</span> (~1s check-ins) and <span class="font-semibold" data-streamdown="strong">systemd HAProxy</span> health
- Queues <span class="font-semibold" data-streamdown="strong">remote jobs</span> (install, reload, backup, drain) that run on the next heartbeat

<span class="font-semibold" data-streamdown="strong">Status:</span> HAProxy pools are well tested and in public beta.

### Core terminology

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-term-meaning-pool-on"><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">Term</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Meaning</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="font-semibold" data-streamdown="strong">Pool</span></div></td><td><div class="md-table-cell-content">One site/deployment in the dashboard</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Member</span></div></td><td><div class="md-table-cell-content">One enrolled VM (node) with hostname, allowed egress IP, and enrollment secret</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Active member</span></div></td><td><div class="md-table-cell-content">The host whose IPv4 the managed DNS A record points at</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Standby</span></div></td><td><div class="md-table-cell-content">Enrolled member not currently receiving DNS traffic</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Failover hostname</span></div></td><td><div class="md-table-cell-content">Public FQDN clients use (e.g. `<span class="md-inline-path-filename">lb.example.com</span>`)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Member template</span></div></td><td><div class="md-table-cell-content">Role at enroll time — for HAProxy pools use <span class="font-semibold" data-streamdown="strong">HAProxy balancer</span></div></td></tr></tbody></table>

</div></div></div>### Architecture (high level)

<div class="composer-message-codeblock" id="bkmrk-clients-%E2%86%92-dns-%28cloud"><div class="ui-code-block"><div class="ui-code-block-content"><div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><div class="ui-default-code ui-code-block-default-code"><div class="ui-default-code__content"><div class="ui-default-code__line"><div class="ui-default-code__line-content">Clients → DNS (Cloudflare / cPanel) → A record → Active HAProxy VM</div></div><div class="ui-default-code__line"><div class="ui-default-code__line-content">↑</div></div><div class="ui-default-code__line"><div class="ui-default-code__line-content">ServerCTL Worker updates DNS</div></div><div class="ui-default-code__line"><div class="ui-default-code__line-content">↑</div></div><div class="ui-default-code__line"><div class="ui-default-code__line-content">Standby HAProxy VMs ← agent heartbeats + jobs</div></div></div></div></div></div></div></div></div></div><span class="font-semibold" data-streamdown="strong">Health for failover:</span> A member is unhealthy when:

1. No heartbeat within the <span class="font-semibold" data-streamdown="strong">failover delay</span> window (10–120 seconds), or
2. HAProxy is monitored, and <span class="font-semibold" data-streamdown="strong">systemd reports HAProxy inactive</span>

<p class="callout warning"><span class="font-semibold" data-streamdown="strong">Important:</span> Clients must use the <span class="font-semibold" data-streamdown="strong">failover hostname</span>, not a member’s raw IP. ServerCTL moves the A record; your apps keep the same DNS name.</p>

### What HAProxy pools include vs other presets

HAProxy pools uniquely enable:

- Remote HAProxy jobs (install, reload, backup)
- HAProxy systemd probe on member cards
- <span class="font-semibold" data-streamdown="strong">Disaster Recovery</span> tab (cross-member restore, 2+ members)
- Traffic-flow diagram on Overview
- HAProxy <span class="font-semibold" data-streamdown="strong">Status</span> tab

Generic Linux pools hide HAProxy-specific jobs unless the agent detects HAProxy on the host.

# Create your first pool & Enroll your first member

## Create your first pool

### Step 1 — Add pool

1. Go to <span class="font-semibold" data-streamdown="strong">Pools</span> → <span class="font-semibold" data-streamdown="strong">Add pool</span>
2. Choose the <span class="font-semibold" data-streamdown="strong">HAProxy template</span>
3. Name the pool (e.g. `production-edge`)
4. After create, you land in the pool with a setup banner

### Step 2 — Connect DNS (Settings)

ServerCTL needs API access to authoritative DNS to create/update the failover <span class="font-semibold" data-streamdown="strong">A record</span>.

<span class="font-semibold" data-streamdown="strong">Cloudflare</span>

- API token: <span class="font-semibold" data-streamdown="strong">Zone · DNS · Edit</span> (+ zone read)
- Cloudflare <span class="font-semibold" data-streamdown="strong">Account ID</span>
- Select the zone that will host your public hostname

<span class="font-semibold" data-streamdown="strong">cPanel / WHM</span>

- WHM hostname and port (usually 2087 or 443)
- WHM username + API token
- Zone domain (apex), e.g. `<span class="md-inline-path-filename">example.com</span>`

You can save reusable Cloudflare credentials under <span class="font-semibold" data-streamdown="strong">Settings → API providers</span> and link them to pools without re-entering tokens.

### Step 3 — Enrol the first member

On <span class="font-semibold" data-streamdown="strong">Overview</span> → <span class="font-semibold" data-streamdown="strong">Add member</span>:

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-field-notes-member-t"><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">Field</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Notes</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="font-semibold" data-streamdown="strong">Member template</span></div></td><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">HAProxy balancer</span></div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Hostname</span></div></td><td><div class="md-table-cell-content">Must match JSON `hostname` from the agent; set `BALCTL_HOSTNAME` on the VM if OS hostname differs</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Allowed source IPs</span></div></td><td><div class="md-table-cell-content">VM <span class="font-semibold" data-streamdown="strong">outbound</span> IPv4 to `<span class="md-inline-path-filename">serversctl.com</span>` (egress), not necessarily SSH address</div></td></tr></tbody></table>

</div></div></div>After creating, copy the <span class="font-semibold" data-streamdown="strong">one-shot install command</span> **immediately and run it in the HAProxy Server** — the enrollment secret is shown <span class="font-semibold" data-streamdown="strong">once</span>.

The command:

- Downloads the agent bundle
- Runs `balctl-agent.sh --enrol --key … --hostname …`
- Writes `<span class="md-inline-path-prefix">/etc/balctl/</span><span class="md-inline-path-filename">agent.env</span>`
- Installs and starts `<span class="md-inline-path-filename">balctl-heartbeat.service</span>`

Within a few seconds, the member tab should show a green heartbeat.

### Step 4 — Set the public failover hostname

<span class="font-semibold" data-streamdown="strong">Settings</span> or <span class="font-semibold" data-streamdown="strong">Managed DNS</span> tab:

- Set DNS label (e.g. `lb` → `<span class="md-inline-path-filename">lb.example.com</span>`)
- Choose orange-cloud (proxied) vs DNS-only as needed
- On Overview, <span class="font-semibold" data-streamdown="strong">Make active</span> on the member that should receive traffic

### Step 6 — Add a standby (High Availability)

Repeat enrollment on a second VM. Enable <span class="font-semibold" data-streamdown="strong">Automatic failover</span> in Settings when ready for unattended promotion.

# Pool workspace (Overview, Settings, DNS, DR, Monitoring)

## Pool workspace

The pool page has a tab bar with three groups:

1. <span class="font-semibold" data-streamdown="strong">Overview</span> (pool home)
2. <span class="font-semibold" data-streamdown="strong">Member tabs</span> (one per enrolled host)
3. <span class="font-semibold" data-streamdown="strong">Pool tools</span> (DR, Monitoring, Settings, Managed DNS)

### Overview tab

For HAProxy pools, Overview answers:

- Is traffic on the active node?
- Are standbys ready?
- Will DNS move if HAProxy or the agent fails?

<span class="font-semibold" data-streamdown="strong">[![HAProxy-H2.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/haproxy-h2.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/haproxy-h2.png)</span>

<span class="font-semibold" data-streamdown="strong">Hero panel:</span> Traffic-flow diagram — Cloudflare/DNS → active HAProxy → standbys.

<span class="font-semibold" data-streamdown="strong">Actions:</span>

- <span class="font-semibold" data-streamdown="strong">Add member</span>
- <span class="font-semibold" data-streamdown="strong">Cut DNS to standby</span> — manual DNS cutover to next ready standby (requires connected DNS)
- <span class="font-semibold" data-streamdown="strong">Settings</span> shortcut

<span class="font-semibold" data-streamdown="strong">[![HAProxy-H3.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/haproxy-h3.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/haproxy-h3.png)</span>

<span class="font-semibold" data-streamdown="strong">KPI tiles:</span> healthy members, failover-ready count, backups, cron jobs, last failover time.

Member cards show <span class="font-semibold" data-streamdown="strong">Active</span> vs <span class="font-semibold" data-streamdown="strong">Standby</span>, heartbeat state, and <span class="font-semibold" data-streamdown="strong">Make active</span> on standbys.

### Settings tab

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-section-purpose-pool"><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">Section</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"><span class="font-semibold" data-streamdown="strong">Pool name</span></div></td><td><div class="md-table-cell-content">Rename the pool</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">API providers</span></div></td><td><div class="md-table-cell-content">Cloudflare credentials, WHM links</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Balancer failover</span></div></td><td><div class="md-table-cell-content">Auto-failover toggle, recovery time (10–120s)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Remove pool</span></div></td><td><div class="md-table-cell-content">Destructive — deletes pool and related data</div></td></tr></tbody></table>

</div></div></div>Failover hostname, proxied vs DNS-only, and Dynamic DNS sync live on the <span class="font-semibold" data-streamdown="strong">Managed DNS</span> tab (not only Settings).

### Managed DNS tab

- Failover DNS label and FQDN preview
- Orange cloud vs DNS-only
- <span class="font-semibold" data-streamdown="strong">Dynamic DNS sync</span> — optional; updates A record when active member’s public IPv4 changes on heartbeat
- DNS connectivity test
- Current A record target IP

### Disaster Recovery tab

Visible when the pool has <span class="font-semibold" data-streamdown="strong">2+ members</span> (HAProxy preset only).

<span class="font-semibold" data-streamdown="strong">Cross-member restore:</span> Pick a target member, choose a snapshot from another host’s backups, restore scoped HAProxy files onto the target.

<p class="callout info"><span class="font-semibold" data-streamdown="strong">Requires Pro</span> or active trial for cross-member restore.</p>

### Monitoring tab

Pool-wide alert settings and failover notification preferences (email when auto-failover promotes a standby).

Per-member monitoring is under each member’s <span class="font-semibold" data-streamdown="strong">Monitoring</span> tab.

### Protection tab

Only appears when <span class="font-semibold" data-streamdown="strong">2+ cPanel members</span> exist — not core HAProxy-only pools. Document separately if you mix cPanel hosts into an HAProxy pool.

# Pool members & enrollment

### Member tab layout

Click a member in the tab bar to open its <span class="font-semibold" data-streamdown="strong">workspace</span>. Sub-tabs:

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-tab-purpose-control-"><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">Tab</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"><span class="font-semibold" data-streamdown="strong">Control panel</span></div></td><td><div class="md-table-cell-content">Host ops: reboot, updates, hostname, TLS domain (non-HAProxy PEM)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Security</span></div></td><td><div class="md-table-cell-content">UFW firewall, SSH enable/disable, firewall backup</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Status</span></div></td><td><div class="md-table-cell-content">Live HAProxy traffic stats from heartbeat (`show stat`)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Cron &amp; Jobs</span></div></td><td><div class="md-table-cell-content">Scheduled tasks + job timeline</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Restore Backups</span></div></td><td><div class="md-table-cell-content">List snapshots, scoped backup/restore</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Recipes</span></div></td><td><div class="md-table-cell-content">One-click enable flows (admin socket, SSH, Let’s Encrypt, agent update)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Monitoring</span></div></td><td><div class="md-table-cell-content">Member-level alert thresholds</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Settings</span></div></td><td><div class="md-table-cell-content">Display name, hostname, allowed IPs, geo, remove member</div></td></tr></tbody></table>

</div></div></div>HAProxy-specific <span class="font-semibold" data-streamdown="strong">Management</span> actions (install, reload, drain, TLS failover) are surfaced on <span class="font-semibold" data-streamdown="strong">Control panel</span> and via <span class="font-semibold" data-streamdown="strong">Recipes</span> — the dedicated HAProxy tab exists in code but is hidden until product-ready.

[![HAProxy-HM3.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/haproxy-hm3.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/haproxy-hm3.png)

### Enrollment security model

Each heartbeat must satisfy:

1. <span class="font-semibold" data-streamdown="strong">Bearer token</span> — 48-character enrollment secret (hashed in D1)
2. <span class="font-semibold" data-streamdown="strong">`CF-Connecting-IP`</span> — must match allowed source IP(s)
3. <span class="font-semibold" data-streamdown="strong">JSON `hostname`</span> — must match enrolled hostname

Mismatch → <span class="font-semibold" data-streamdown="strong">403</span> (IP) or credential errors.

### Agent environment

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-variable-purpose-bal"><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">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_API_BASE`</div></td><td><div class="md-table-cell-content">Worker URL (e.g. `https://serversctl.com`)</div></td></tr><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">From Add member modal</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">Override OS 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">Declare public IPv4 in heartbeat</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">`BALCTL_PROBE_PUBLIC_IP=1`</div></td><td><div class="md-table-cell-content">Probe public IP if not declared</div></td></tr></tbody></table>

</div></div></div>Agent runs as <span class="font-semibold" data-streamdown="strong">root</span> for HAProxy install, backup/restore, admin socket, and cert writes.

# DNS failover & traffic cutover

### Manual cutover

<span class="font-semibold" data-streamdown="strong">Make active</span> on a standby member → ServerCTL sets it as primary and updates the managed A record to its public IPv4.

<span class="font-semibold" data-streamdown="strong">Cut DNS to standby</span> on Overview → promotes next <span class="font-semibold" data-streamdown="strong">failover-ready</span> standby (same DNS update, overview-oriented workflow).

[![HAProxy-HM4.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/haproxy-hm4.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/haproxy-hm4.png)

### Automatic failover

Enable in <span class="font-semibold" data-streamdown="strong">Settings → Balancer failover</span>.

When enabled, ServerCTL periodically evaluates the active member. Promotion triggers when:

- Heartbeat age exceeds <span class="font-semibold" data-streamdown="strong">failover delay</span>, or
- HAProxy is monitored and <span class="font-semibold" data-streamdown="strong">inactive</span>

A healthy standby is promoted; DNS is updated; optional email alert fires.

### Failover delay

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-setting-range-recove"><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">Setting</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Range</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">Recovery time</div></td><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">10–120 seconds</span></div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Community (free)</div></td><td><div class="md-table-cell-content">Fixed at <span class="font-semibold" data-streamdown="strong">120s</span></div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Pro / trial</div></td><td><div class="md-table-cell-content">Faster presets (e.g. 10s, 30s)</div></td></tr></tbody></table>

</div></div></div>Agents' heartbeat independently (~1s); failover delay is <span class="font-semibold" data-streamdown="strong">not</span> the heartbeat interval.

### Failover-ready criteria

A standby is <span class="font-semibold" data-streamdown="strong">ready</span> when:

- Recent heartbeat within the failover window, <span class="font-semibold" data-streamdown="strong">and</span>
- HAProxy is not down (when monitored)

### Dynamic DNS Sync

Optional for HAProxy pools when the <span class="font-semibold" data-streamdown="strong">active</span> member’s WAN IPv4 changes (DHCP/ISP churn). Each heartbeat can push the new public IP to Cloudflare without manual DNS edits.

### Proxied vs DNS-only

- <span class="font-semibold" data-streamdown="strong">Orange cloud (proxied):</span> Traffic through Cloudflare; good for HTTP/S when origin IP hiding matters.
- <span class="font-semibold" data-streamdown="strong">DNS-only (grey cloud):</span> Clients connect directly to member IPv4 — required for raw TCP services (e.g. non-HTTP on custom ports).

# HAProxy operations

### Install &amp; lifecycle

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-action-command-id-no"><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">Action</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Command ID</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Notes</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">Install HAProxy</div></td><td><div class="md-table-cell-content">`<span class="md-inline-path-filename">haproxy.provision</span>`</div></td><td><div class="md-table-cell-content">Fresh VM</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Re-install</div></td><td><div class="md-table-cell-content">`<span class="md-inline-path-filename">haproxy.provision</span>` + `force: true`</div></td><td><div class="md-table-cell-content">Overwrite install path</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Reload</div></td><td><div class="md-table-cell-content">`<span class="md-inline-path-filename">haproxy.reload</span>`</div></td><td><div class="md-table-cell-content">After config edits</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Provision standby from backup</div></td><td><div class="md-table-cell-content">`<span class="md-inline-path-filename">standby.provision_from_backup</span>`</div></td><td><div class="md-table-cell-content">Clone config from backup onto standby</div></td></tr></tbody></table>

</div></div></div>Jobs are enqueued to the API; the agent claims and runs them on the <span class="font-semibold" data-streamdown="strong">next heartbeat</span>.

### Admin stats socket (drain / ready)

Runtime backend control requires a <span class="font-semibold" data-streamdown="strong">Unix admin socket</span> in `<span class="md-inline-path-filename">haproxy.cfg</span>`:

```
stats socket /run/haproxy/admin.sock mode 600 level admin expose-fd listeners
stats timeout 2m
```

Enable via <span class="font-semibold" data-streamdown="strong">Recipe: Enable HAProxy admin stats socket</span> or <span class="font-semibold" data-streamdown="strong">Enable admin stats socket</span> action.

**Requires <span class="font-semibold" data-streamdown="strong">socot</span> + agent as <span class="font-semibold" data-streamdown="strong">root</span>. This is <span class="font-semibold" data-streamdown="strong">not</span> a public HTTP stats page.**

### Backend server states

From Management/topology table:

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-action-command-id-ha"><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">Action</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Command ID</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">HAProxy runtime</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">Drain</div></td><td><div class="md-table-cell-content">`<span class="md-inline-path-filename">haproxy.server_drain</span>`</div></td><td><div class="md-table-cell-content">`set server … state drain`</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Ready</div></td><td><div class="md-table-cell-content">`<span class="md-inline-path-filename">haproxy.server_ready</span>`</div></td><td><div class="md-table-cell-content">`state ready`</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Maintenance</div></td><td><div class="md-table-cell-content">`<span class="md-inline-path-filename">haproxy.server_maint</span>`</div></td><td><div class="md-table-cell-content">`state maint`</div></td></tr></tbody></table>

</div></div></div>### TLS (Let’s Encrypt on HAProxy)

<span class="font-semibold" data-streamdown="strong">Recipe: Let’s Encrypt (failover / HAProxy)</span>

- Uses <span class="font-semibold" data-streamdown="strong">DNS-01</span> via Cloudflare for the <span class="font-semibold" data-streamdown="strong">pool failover FQDN</span>
- Agent writes combined PEM: `<span class="md-inline-path-prefix">/etc/haproxy/certs/</span><span class="md-inline-path-filename"><hostname>.pem</span>`
- <span class="font-semibold" data-streamdown="strong">One-time operator step:</span> add `ssl crt /etc/haproxy/certs/<hostname>.pem` in config, validate, reload
- Renew from Management or cron preset `<span class="md-inline-path-filename">tls.acme_renew_force</span>`

The pool must have Cloudflare linked and a failover label set before the recipe applies.

### Status tab

Shows live traffic from agent heartbeat enrichment — <span class="font-semibold" data-streamdown="strong">not</span> a duplicate of the Overview topology diagram. Use for session rates, backend health columns, etc.

# Backups & disaster recovery

### What gets backed up

HAProxy backup job captures:

- `<span class="md-inline-path-prefix">/etc/haproxy/</span><span class="md-inline-path-filename">haproxy.cfg</span>` and `conf.d/*.cfg`
- `<span class="md-inline-path-prefix">/etc/haproxy/certs/</span><span class="md-inline-path-filename">*</span>`
- Let’s Encrypt material under `<span class="md-inline-path-prefix">/etc/letsencrypt/</span>`
- Paths referenced by `ssl crt` in config under `<span class="md-inline-path-prefix">/etc/</span>`
- Optional <span class="font-semibold" data-streamdown="strong">UFW</span> rules (`<span class="md-inline-path-filename">backup.ufw</span>`) — separate job

Storage: <span class="font-semibold" data-streamdown="strong">Per member S3</span>.

Path pattern:

<div class="composer-message-codeblock" id="bkmrk-%2F%7Buserid%7D%2Fsites%2F%7Bsit"><div class="ui-code-block"><div class="ui-code-block-content"><div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><div class="ui-default-code ui-code-block-default-code"><div class="ui-default-code__content"><div class="ui-default-code__line"><div class="ui-default-code__line-content">/{userId}/sites/{siteId}/snapshots/{snapshotId}/</div></div></div></div></div></div></div></div></div></div>### Restore flows

<span class="font-semibold" data-streamdown="strong">Same member:</span> Restore Backups tab → pick snapshot → scoped restore → agent validates with `haproxy -c` → reload.

<span class="font-semibold" data-streamdown="strong">Cross-member (DR tab):</span> Restore another member’s snapshot to a target VM — typically after an outage or a bad config push.

<span class="font-semibold" data-streamdown="strong">Fresh VM rebuild:</span>

1. Enrol new/replacement member
2. Optional: Install HAProxy
3. Restore snapshot
4. Make active when ready

### Standby provisioning

<span class="font-semibold" data-streamdown="strong">Provision standby from backup</span> clones, HAProxy config from a backup onto a standby host — faster than manual copy for DR drills.

# Recipes & scheduled jobs

### Recipes (member → Recipes tab)

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-recipe-when-enable-h"><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">Recipe</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">When</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">Enable HAProxy admin stats socket</div></td><td><div class="md-table-cell-content">HAProxy detected</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Enable SSH access</div></td><td><div class="md-table-cell-content">Always available</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Let’s Encrypt (failover / HAProxy)</div></td><td><div class="md-table-cell-content">HAProxy + Cloudflare + failover FQDN</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Update agent</div></td><td><div class="md-table-cell-content">When agent version outdated</div></td></tr></tbody></table>

</div></div></div>Recipes show steps, completion state, and optional <span class="font-semibold" data-streamdown="strong">disable</span> actions (e.g. remove admin socket lines).

### Cron &amp; Jobs tab

Control-plane cron (UTC) enqueues jobs on the next agent heartbeat.

Common presets:

- HAProxy backup
- `<span class="md-inline-path-filename">haproxy.reload</span>`
- TLS force renew
- `<span class="md-inline-path-filename">failover.evaluate</span>` (pool-level failover check)

Separate from per-member <span class="font-semibold" data-streamdown="strong">backup schedule</span> on Restore Backups — both can exist.

### Job timeline

All agent jobs appear in <span class="font-semibold" data-streamdown="strong">Cron &amp; Jobs</span> with status: pending → running → completed/failed. Remote actions from Overview cards also enqueue here.

# Agent reference

### Heartbeat payload (HAProxy-relevant)

The agent sends JSON including:

- `ip` — declared/probed IPv4
- `hostname`
- `haproxy` block — monitored, active, topology, listeners, optional `show stat` summary

ServerCTL validates IP against enrollment and stores the latest row per node.

### CLI essentials

```
sudo balctl_heartbeat.py --version
sudo balctl_heartbeat.py --provision-haproxy # local install
sudo balctl_heartbeat.py --update # from configured agent.zip URL
sudo journalctl -u balctl-heartbeat.service -f
```

<div class="composer-message-codeblock" id="bkmrk-"><div class="ui-code-block"><div class="ui-code-block-content"><div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><div class="ui-default-code ui-code-block-default-code"><div class="ui-default-code__content"><div class="ui-default-code__line"><div class="ui-default-code__line-content">  
</div></div></div></div></div></div></div></div></div></div>### Job loop

1. `POST /api/agents/heartbeat`
2. Server returns pending jobs
3. Agent executes, posts `POST /api/agents/jobs/complete`

Full agent docs: `<span class="md-inline-path-prefix">agents/</span><span class="md-inline-path-filename">README.md</span>` in the repo.

# Troubleshooting & FAQ

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-symptom-likely-cause"><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">Symptom</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Likely cause</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Fix</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="font-semibold" data-streamdown="strong">403 on heartbeat</span></div></td><td><div class="md-table-cell-content">Wrong allowed IP or hostname</div></td><td><div class="md-table-cell-content">Update allowed IPs; set `BALCTL_HOSTNAME`</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">401 unknown credential</span></div></td><td><div class="md-table-cell-content">Used member UUID instead of enrollment secret</div></td><td><div class="md-table-cell-content">Re-enroll; use 48-char secret from modal</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">No HAProxy on card</span></div></td><td><div class="md-table-cell-content">No config / unit not detected</div></td><td><div class="md-table-cell-content">Install or ensure `<span class="md-inline-path-prefix">/etc/haproxy/</span><span class="md-inline-path-filename">haproxy.cfg</span>` exists</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Drain buttons missing</span></div></td><td><div class="md-table-cell-content">No admin socket</div></td><td><div class="md-table-cell-content">Run admin socket recipe</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Backup shows · D1 not · R2</span></div></td><td><div class="md-table-cell-content">R2 not bound when backup ran</div></td><td><div class="md-table-cell-content">Fix Worker binding; run new backup</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Auto-failover didn’t run</span></div></td><td><div class="md-table-cell-content">Only one member, auto off, or no healthy standby</div></td><td><div class="md-table-cell-content">Add standby; enable auto; check readiness</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">DNS didn’t update</span></div></td><td><div class="md-table-cell-content">DNS not connected, private IP in heartbeat, label unset</div></td><td><div class="md-table-cell-content">Connect provider; use public IPv4; set label</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Let’s Encrypt recipe greyed out</span></div></td><td><div class="md-table-cell-content">No Cloudflare or no failover FQDN</div></td><td><div class="md-table-cell-content">Complete DNS setup first</div></td></tr></tbody></table>

</div></div></div><span class="font-semibold" data-streamdown="strong">Support bundle:</span> If contacting support, include the pool name, member hostname, `journalctl` excerpt, screenshot of member health badge.

---

## Appendix — Plan gating (for operators)

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-feature-community-pr"><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">Feature</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Community</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Pro / trial</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">Failover delay</div></td><td><div class="md-table-cell-content">120s only</div></td><td><div class="md-table-cell-content">10s–120s</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Cross-member DR restore</div></td><td><div class="md-table-cell-content">Locked</div></td><td><div class="md-table-cell-content">Available</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Premium DNS/provider modals</div></td><td><div class="md-table-cell-content">Gated</div></td><td><div class="md-table-cell-content">Available</div></td></tr></tbody></table>

</div></div></div>

# Linux Server Pools

**Generic Linux Server Pools** provide monitoring and management for a wide range of Linux infrastructure. They support OpenLiteSpeed, MariaDB, Galera Cluster, and other Linux-based services running on Ubuntu, Debian, Rocky Linux, AlmaLinux, CentOS, Red Hat Enterprise Linux (RHEL), and compatible distributions.

# Part 1 - Overview & Concepts

### What ServersCTL hosting pools are

<span class="font-semibold" data-streamdown="strong">ServersCTL</span> ([serversctl.com](https://serversctl.com/)) is the control plane for <span class="font-semibold" data-streamdown="strong">redundant server infrastructure</span>: enrol Linux VMs with the ServersCTL agent, monitor heartbeats, cut over DNS between peers, run stack backups, and (on the cPanel preset) orchestrate account replication and live WHM transfers.

A <span class="font-semibold" data-streamdown="strong">server pool</span> is one deployment in your dashboard — a set of members sharing failover DNS and pool-level settings. <span class="font-semibold" data-streamdown="strong">Members run what you actually install</span> on each host. The dashboard exposes member tabs for **<span class="font-semibold" data-streamdown="strong">OpenLiteSpeed</span>**, **<span class="font-semibold" data-streamdown="strong">MariaDB/MySQL</span>**, **<span class="font-semibold" data-streamdown="strong">Galera</span>**, and **<span class="font-semibold" data-streamdown="strong">cPanel/WHM</span>**; each tab fills in when the agent detects that stack on <span class="font-semibold" data-streamdown="strong">that</span> server.

<span class="font-semibold" data-streamdown="strong">ServersCTL does not host traffic.</span> It moves DNS, queues remote jobs, and calls APIs where configured.

[![PoolOverviewLinuxGeneric.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/pooloverviewlinuxgeneric.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/pooloverviewlinuxgeneric.png)

### Pool Presets

Server pools are created using a preset template in the UI. This chapter is for the **Generic Linux Server** Preset. For HAProxy Server Pools, see the [HAProxy chapter](https://docs.serversctl.com/books/getting-started/chapter/haproxy-server-pools).

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk--1"><div class="ui-scroll-area__viewport">  
</div></div>### What runs on a member (stack compatibility)

<span class="font-semibold" data-streamdown="strong">Do not assume one VM runs every stack.</span> Common deployments:

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-deployment-typical-m"><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">Deployment</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Typical member stacks</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Notes</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="font-semibold" data-streamdown="strong">cPanel / WHM hosting</span></div></td><td><div class="md-table-cell-content">cPanel tab + MariaDB tab (cPanel-managed MySQL)</div></td><td><div class="md-table-cell-content">Apache/httpd via cPanel — <span class="font-semibold" data-streamdown="strong">not</span> OpenLiteSpeed</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">OpenLiteSpeed web farm</span></div></td><td><div class="md-table-cell-content">OpenLiteSpeed tab only</div></td><td><div class="md-table-cell-content">Standalone OLS</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">MariaDB / Galera nodes</span></div></td><td><div class="md-table-cell-content">MariaDB tab + Galera readout</div></td><td><div class="md-table-cell-content">DNS swing ≠ Galera quorum</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Mixed pool</span></div></td><td><div class="md-table-cell-content">Different tabs per member</div></td><td><div class="md-table-cell-content">e.g. two cPanel standbys + one OLS edge — each member’s tabs reflect its OS</div></td></tr></tbody></table>

</div></div></div>### Core terminology

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-term-meaning-pool-on"><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">Term</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Meaning</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="font-semibold" data-streamdown="strong">Pool</span></div></td><td><div class="md-table-cell-content">One ServersCTL Pool.</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Member</span></div></td><td><div class="md-table-cell-content">One enrolled server (hostname, egress IP, enrollment secret)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Active member</span></div></td><td><div class="md-table-cell-content">Host whose IPv4 receives the pool failover A record.</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Stack tab</span></div></td><td><div class="md-table-cell-content">Member workspace: OpenLiteSpeed, MariaDB, cPanel, etc.</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Protected account</span></div></td><td><div class="md-table-cell-content">cPanel account with a replication job.</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Agent</span></div></td><td><div class="md-table-cell-content">`<span class="md-inline-path-filename">balctl_heartbeat.py</span>` on each member — heartbeats to `<span class="md-inline-path-filename">serversctl.com</span>`</div></td></tr></tbody></table>

</div></div></div>### Architecture

<div class="composer-message-codeblock" id="bkmrk-clients-%E2%86%92-dns-%28cloud"><div class="ui-code-block"><div class="ui-code-block-content"><div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><div class="ui-default-code ui-code-block-default-code"><div class="ui-default-code__content"><div class="ui-default-code__line"><div class="ui-default-code__line-content">Clients → DNS (Cloudflare / WHM) → A record → Active member IPv4</div></div><div class="ui-default-code__line"><div class="ui-default-code__line-content">↑</div></div><div class="ui-default-code__line"><div class="ui-default-code__line-content">ServersCTL Worker (serversctl.com)</div></div><div class="ui-default-code__line"><div class="ui-default-code__line-content">↑</div></div><div class="ui-default-code__line"><div class="ui-default-code__line-content">Standby members' ← agent heartbeats (+ WHM replication on cPanel preset)</div></div></div></div></div></div></div></div></div></div><span class="font-semibold" data-streamdown="strong">Failover health:</span> missed heartbeat beyond <span class="font-semibold" data-streamdown="strong">failover delay</span> (10–120 s). No HAProxy systemd check on hosting presets.

<p class="callout info">All Linux Servers should use the Generic Server Preset when adding a pool. Only ever select the HAProxy Preset if HAProxy is installed on your server.</p>

### <span data-sd-animate="true">Create</span> <span data-sd-animate="true">a</span> <span data-sd-animate="true">Generic</span> <span data-sd-animate="true">Linux</span> <span data-sd-animate="true">pool</span>

1. - <span data-sd-animate="true">Sign</span> <span data-sd-animate="true">in</span> <span data-sd-animate="true">at</span> <span class="font-semibold" data-streamdown="strong">[<span data-sd-animate="true">serversctl.com</span>](https://serversctl.com/)</span> <span data-sd-animate="true">→</span> <span class="font-semibold" data-streamdown="strong"><span data-sd-animate="true">Pools</span></span> <span data-sd-animate="true">→</span> Create P<span class="font-semibold" data-streamdown="strong"><span data-sd-animate="true">ool.</span></span>

<span class="font-semibold" data-streamdown="strong"><span data-sd-animate="true">[![CreatePool.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/createpool.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/createpool.png)</span></span>

1. - <span class="font-semibold" data-streamdown="strong"><span data-sd-animate="true">Configuration</span> <span data-sd-animate="true">preset:</span></span> <span class="font-semibold" data-streamdown="strong"><span data-sd-animate="true">Generic</span> <span data-sd-animate="true">Linux</span> <span data-sd-animate="true">servers.</span></span>

<span class="font-semibold" data-streamdown="strong"><span data-sd-animate="true">[![CreatePool-1.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/createpool-1.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/createpool-1.png)</span></span>

1. - <span class="font-semibold" data-streamdown="strong"><span data-sd-animate="true">Name the Pool.</span></span>
    - <span class="font-semibold" data-streamdown="strong"><span data-sd-animate="true">Now, go to the pool. Pool Overview</span></span> <span data-sd-animate="true">→</span> <span class="font-semibold" data-streamdown="strong"><span data-sd-animate="true">Add</span> <span data-sd-animate="true">server. Choose </span></span><span data-sd-animate="true">— RHEL/Ubuntu - Enter </span><span data-sd-animate="true">hostname,</span> <span data-sd-animate="true">allowed</span> <span data-sd-animate="true">egress</span> <span data-sd-animate="true">IP.</span>

- <span data-sd-animate="true">[![CreateEnrollment.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/createenrollment.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/createenrollment.png)</span>
- 
- 
- 
- <span data-sd-animate="true">Click </span><span data-sd-animate="true">Create Enrollment" and copy the install command.</span>

<span data-sd-animate="true">[![CreateEnrollmentKey.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/createenrollmentkey.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/createenrollmentkey.png)</span>

1. <span data-sd-animate="true">On</span> the <span data-sd-animate="true">VM:</span>
    - <span data-sd-animate="true">Paste the install command into the console to install the agent. </span>
    - <span data-sd-animate="true">If you have existing installs of cPanel, OpenLiteSpeed, MariaDB etc. The agent will report this to the UI. </span>
    - <span data-sd-animate="true">When</span> <span class="font-semibold" data-streamdown="strong"><span data-sd-animate="true">2+</span> <span data-sd-animate="true">members</span></span> <span data-sd-animate="true">run</span> <span data-sd-animate="true">cPanel:</span> <span data-sd-animate="true">pool</span> <span class="font-semibold" data-streamdown="strong"><span data-sd-animate="true">Protection</span></span> <span data-sd-animate="true">and</span> <span class="font-semibold" data-streamdown="strong"><span data-sd-animate="true">Managed</span> <span data-sd-animate="true">DNS</span></span> <span data-sd-animate="true">tabs</span> <span data-sd-animate="true">appear</span> <span data-sd-animate="true">(see</span> Chapter 1<span data-sd-animate="true">).</span>
    - <span data-sd-animate="true">Optional:</span> <span data-sd-animate="true">Configure</span> <span data-sd-animate="true">DNS </span><span data-sd-animate="true">on</span> <span class="font-semibold" data-streamdown="strong"><span data-sd-animate="true">Managed</span> <span data-sd-animate="true">DNS</span></span> <span data-sd-animate="true">for</span> <span data-sd-animate="true">account-level</span> <span data-sd-animate="true">cutover.</span>

#### <span data-sd-animate="true">Add further members to the Pool</span>

1. <span data-sd-animate="true">From the pool overview tab click "Add Member".</span>
2. <span data-sd-animate="true">Name the member and supply the member's egress IP.</span>
3. <span data-sd-animate="true">Copy the install command into the console of the server being added to the pool.</span>
4. <span data-sd-animate="true">Repeat the process to add further members. There are no limits to the number of pools or members you may have.</span>

## How pool navigation works

### Two layers on one screen

<div class="composer-message-codeblock" id="bkmrk-the-ui-is-split-into"><div class="ui-code-block"><div class="ui-code-block-content"><div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><div class="ui-default-code ui-code-block-default-code"><div class="ui-default-code__content"><div class="ui-default-code__line">  
</div><div class="ui-default-code__line">The UI is split into two sections. The top tabs manage overall pool settings and the lower tabs manage member settings.  
<div class="ui-default-code__line-content">  
</div></div></div></div></div></div></div></div></div></div><div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-layer-what-it-contro"><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">Layer</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">What it controls</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Examples</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="font-semibold" data-streamdown="strong">Pool tabs</span></div></td><td><div class="md-table-cell-content">Settings and services that span <span class="font-semibold" data-streamdown="strong">all members</span> or <span class="font-semibold" data-streamdown="strong">protected accounts</span> across members</div></td><td><div class="md-table-cell-content">Overview fleet, Protection replication, Managed DNS catalogue, pool Monitoring presets, pool Settings</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Member tabs</span></div></td><td><div class="md-table-cell-content">One enrolled <span class="font-semibold" data-streamdown="strong">Linux server</span> — host OS, detected stacks, jobs, and backups</div></td><td><div class="md-table-cell-content">Control panel, Security, cPanel, MariaDB, Cron &amp; Jobs</div></td></tr></tbody></table>

</div></div></div>[![Linux-Server-Pool-O2.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/linux-server-pool-o2.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/linux-server-pool-o2.png)

<span class="font-semibold" data-streamdown="strong">Protection</span> and <span class="font-semibold" data-streamdown="strong">Managed DNS</span> are <span class="font-semibold" data-streamdown="strong">pool settings</span>. They float in the top tab bar <span class="font-semibold" data-streamdown="strong">above</span> the member server tabs. They coordinate <span class="font-semibold" data-streamdown="strong">account replication</span>, <span class="font-semibold" data-streamdown="strong">DNS cutover</span>, and <span class="font-semibold" data-streamdown="strong">provider keys</span> across the fleet — not operations on a single box.

### Pool tab visibility (Generic Linux template)

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-pool-tab-always%3F-whe"><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">Pool tab</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Always?</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">When it appears</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="font-semibold" data-streamdown="strong">Overview</span></div></td><td><div class="md-table-cell-content">Yes</div></td><td><div class="md-table-cell-content">Default landing</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Member tabs</span> (one per server)</div></td><td><div class="md-table-cell-content">When enrolled</div></td><td><div class="md-table-cell-content">Subtitle <span class="font-semibold" data-streamdown="strong">Member</span> on Generic pools (not Active/Standby)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Protection</span></div></td><td><div class="md-table-cell-content">No</div></td><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">2+ members</span> with cPanel detected</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Managed DNS</span></div></td><td><div class="md-table-cell-content">No</div></td><td><div class="md-table-cell-content">Same as Protection on Generic pools (2+ cPanel members)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Monitoring</span></div></td><td><div class="md-table-cell-content">Yes</div></td><td><div class="md-table-cell-content">Pool-wide alert presets</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Settings</span></div></td><td><div class="md-table-cell-content">Yes</div></td><td><div class="md-table-cell-content">Pool name, API providers, delete pool</div></td></tr></tbody></table>

</div></div></div><span class="font-semibold" data-streamdown="strong">Tab:</span> <span class="font-semibold" data-streamdown="strong">Overview</span> (first pool tab).

<span class="font-semibold" data-streamdown="strong">Purpose:</span> Fleet-wide health — are agents reporting, are backups and jobs healthy across Linux servers?

#### What you see

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-section-content-flee"><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">Section</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Content</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="font-semibold" data-streamdown="strong">Fleet Status</span> header</div></td><td><div class="md-table-cell-content">KPI widgets: member count, healthy agents, last check-in, backup count, cron jobs, running jobs, outdated agents</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Fleet geography</span> map</div></td><td><div class="md-table-cell-content">Members plotted when geo is set on each member’s <span class="font-semibold" data-streamdown="strong">Settings</span> tab</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Server tiles</span></div></td><td><div class="md-table-cell-content">One tile per enrolled member — click to open that member’s workspace</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Actions</span></div></td><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Add server</span>, <span class="font-semibold" data-streamdown="strong">Pool settings</span></div></td></tr></tbody></table>

</div></div></div>#### Operator actions

- <span class="font-semibold" data-streamdown="strong">Add server</span> — enroll another VM (see §18)
- Click a <span class="font-semibold" data-streamdown="strong">server tile</span> or <span class="font-semibold" data-streamdown="strong">member tab</span> — jump to that member’s Control panel
- <span class="font-semibold" data-streamdown="strong">Pool settings</span> — shortcut to pool <span class="font-semibold" data-streamdown="strong">Settings</span> tab

<span class="font-semibold" data-streamdown="strong">Tab:</span> <span class="font-semibold" data-streamdown="strong">Protection</span> (pool tab bar).

<span class="font-semibold" data-streamdown="strong">When visible:</span> <span class="font-semibold" data-streamdown="strong">2+ pool members</span> where the agent reports cPanel.

<span class="font-semibold" data-streamdown="strong">Purpose:</span> <span class="font-semibold" data-streamdown="strong">Account-level warm standby</span> — scheduled WHM backup → Secure Storage → restore on a standby server, with optional DNS cutover per protected account.

#### What you see

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-section-content-prot"><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">Section</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Content</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="font-semibold" data-streamdown="strong">Protection dashboard</span></div></td><td><div class="md-table-cell-content">KPIs: protected accounts, replication health, last sync</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Server cards</span></div></td><td><div class="md-table-cell-content">Each cPanel-eligible member — readiness, WHM link, geography</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Protected accounts</span></div></td><td><div class="md-table-cell-content">Per-account source → standby mapping, schedule, TTL, DNS provider</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Replication log</span></div></td><td><div class="md-table-cell-content">Sync, DNS cut, transfer, and failure events</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Geo map</span></div></td><td><div class="md-table-cell-content">Primary / standby geography when locations are set</div></td></tr></tbody></table>

</div></div></div>### Operator workflow

1. Ensure <span class="font-semibold" data-streamdown="strong">2+ cPanel members</span> and WHM API keys (<span class="font-semibold" data-streamdown="strong">Settings</span> or <span class="font-semibold" data-streamdown="strong">Managed DNS</span> → API providers).
2. <span class="font-semibold" data-streamdown="strong">Add protection</span> — pick source account, target standby member, schedule (`1h` … `1mo`), DNS TTL, DNS provider (Cloudflare or WHM).
3. <span class="font-semibold" data-streamdown="strong">Replicate now</span> / <span class="font-semibold" data-streamdown="strong">Replicate protected</span> (Pro) — on-demand sync.
4. <span class="font-semibold" data-streamdown="strong">Account Cut DNS</span> — per-account A record swing to standby (coordinates with <span class="font-semibold" data-streamdown="strong">Managed DNS</span>).
5. After DNS cut: <span class="font-semibold" data-streamdown="strong">post-failover hook</span> on standby.

### Relationship to member tabs

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-task-where-bulk-acco"><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">Task</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Where</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">Bulk account replication, schedules, protection DNS</div></td><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Pool Protection</span></div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Single-account migrate, live transfer sessions</div></td><td><div class="md-table-cell-content">Member <span class="font-semibold" data-streamdown="strong">cPanel</span> → <span class="font-semibold" data-streamdown="strong">Migrate &amp; Recovery</span></div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">WHM account CRUD, suspend, AutoSSL</div></td><td><div class="md-table-cell-content">Member <span class="font-semibold" data-streamdown="strong">cPanel</span> → <span class="font-semibold" data-streamdown="strong">Accounts</span></div></td></tr></tbody></table>

</div></div></div>Protection is <span class="font-semibold" data-streamdown="strong">pool-wide orchestration</span>; member <span class="font-semibold" data-streamdown="strong">cPanel</span> is <span class="font-semibold" data-streamdown="strong">per-server</span> WHM operations.

### Replication Transfer

Replication can take anywhere from a few minutes to several hours, depending on the size of the account.

[![ReplicationTopologySync.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/replicationtopologysync.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/replicationtopologysync.png)

The source agent will package the account and split it into multiple chunks, which are securely stored temporarily in D2 storage. Once all chunks have been uploaded, the UI instructs the receiving agent to download them and begin the restore process.

After the restore has completed successfully, all stored chunks are automatically removed from S3. As a guide, a **1.8GB backup typically takes around 5 minutes** to replicate. Please take replication time into account when configuring your schedule. If the account is large, you may need to replicate **once per day** or **every few days** to avoid overlap and ensure the process completes cleanly.

[![ReplicationTopology.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/replicationtopology.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/replicationtopology.png)

#### <span class="font-semibold" data-streamdown="strong">Tab:</span> <span class="font-semibold" data-streamdown="strong">Managed DNS</span> (pool tab bar).

<span class="font-semibold" data-streamdown="strong">When visible (Generic Linux):</span> <span class="font-semibold" data-streamdown="strong">2+ cPanel-detected members</span>

<span class="font-semibold" data-streamdown="strong">Purpose:</span> <span class="font-semibold" data-streamdown="strong">Pool-level DNS catalogue</span> for protected accounts and optional dynamic DNS — WHM vs Cloudflare zones, record health, sync, import. The API keys listed here are for DNS only. Do not use your production WHM API key here. You must use Cloudflare or a cPanel DNS Cluster API Key.

#### What you see

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-section-content-dns-"><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">Section</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Content</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="font-semibold" data-streamdown="strong">DNS Health</span></div></td><td><div class="md-table-cell-content">Zone summary, provider linkage, drift, topology banner (DNS Provider → Primary → Standby) when Protection is active</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Protected account records</span></div></td><td><div class="md-table-cell-content">Per-FQDN proxied/DNS-only, enabled, active IP, cut actions</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">API providers</span></div></td><td><div class="md-table-cell-content">Cloudflare account keys (one <span class="font-semibold" data-streamdown="strong">Global</span>), per-member WHM keys</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Dynamic DNS card</span></div></td><td><div class="md-table-cell-content">Failover hostname and sync toggle (Generic pools — primary place for DNS failover config)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Actions</span></div></td><td><div class="md-table-cell-content">Refresh DNS, Sync WHM→CF, Add record, Import from Cloudflare</div></td></tr></tbody></table>

</div></div></div>#### Operator actions

- Connect <span class="font-semibold" data-streamdown="strong">Cloudflare</span> and/or cPanel DNS API credentials
- <span class="font-semibold" data-streamdown="strong">Import</span> existing Cloudflare records into the catalogue
- <span class="font-semibold" data-streamdown="strong">Sync WHM→CF</span> after account changes on WHM
- <span class="font-semibold" data-streamdown="strong">Account Cut DNS</span> from record rows (also available on Protection cards)
- Set <span class="font-semibold" data-streamdown="strong">failover hostname</span> and enable DNS sync (when using pool-level cutover)

#### Protected DNS

The UI treats WHM servers as the **source of truth** and replicates changes to any linked DNS provider, as long as the account is marked as **Managed**. By default, the only record that will cut over automatically is the domain’s **A record**.

#### Multiple DNS Record Cut Over

You can configure the UI to cut over additional DNS records from the **Protected Account DNS** list. From here, you can specify **A**, **AAAA**, **MX**, and **SRV** records that should automatically cut to a standby server when the primary becomes unavailable.

#### Actions

The three‑dot menu under the **Action** column provides additional fine‑tuning options:

- **Disable Managed DNS** – When disabled, DNS records will not be updated or cut over during failover.
- **DNS Only** – During cutover, Cloudflare proxying will be disabled (grey cloud), ensuring a direct DNS‑level switch without CDN caching or WAF interference.
- **Sync to Cloudflare** – If you’ve added new DNS records in WHM’s DNS Manager, they will appear in the Protected DNS table and can be automatically pushed to Cloudflare.

[![ManagedDNSActions.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/manageddnsactions.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/manageddnsactions.png)

#### Pool vs member DNS

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-scope-tab-protected-"><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">Scope</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Tab</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">Protected accounts, zone catalogue, provider keys, and failover FQDN</div></td><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Pool Managed DNS</span></div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Per-member WHM key rotation</div></td><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Pool Settings</span> or <span class="font-semibold" data-streamdown="strong">Managed DNS</span> API panel</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Host TLS / Let’s Encrypt on a single VM</div></td><td><div class="md-table-cell-content">Member <span class="font-semibold" data-streamdown="strong">Control Panel</span> or <span class="font-semibold" data-streamdown="strong">Recipes</span></div></td></tr></tbody></table>

</div></div></div><span class="font-semibold" data-streamdown="strong">Tab:</span> <span class="font-semibold" data-streamdown="strong">Monitoring</span> (pool tab bar).

<span class="font-semibold" data-streamdown="strong">Purpose:</span> <span class="font-semibold" data-streamdown="strong">Pool-wide</span> monitoring presets — distinct from per-member alerts on each server’s <span class="font-semibold" data-streamdown="strong">Monitoring</span> tab.

### Generic pool without Protection (0–1 cPanel members)

<span class="font-semibold" data-streamdown="strong">Infrastructure alerts</span> section:

- Heartbeat miss thresholds, CPU/disk/service alert toggles
- Optional alert email recipients (account + team inboxes)

### Generic pool with Protection (2+ cPanel members)

<span class="font-semibold" data-streamdown="strong">Protection DNS failover</span> section (in addition to or instead of infrastructure, depending on layout):

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-preset-meaning-failo"><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">Preset</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Meaning</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="font-semibold" data-streamdown="strong">Failover banner</span></div></td><td><div class="md-table-cell-content">How long pool header shows <span class="font-semibold" data-streamdown="strong">Failover active</span> after DNS cut (Community: 2 h max)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Failover email</span></div></td><td><div class="md-table-cell-content">Notify when DNS moves to standby</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Failover alert recipients</span></div></td><td><div class="md-table-cell-content">Team inboxes for protection cutover</div></td></tr></tbody></table>

</div></div></div>#### Operator note

Configure <span class="font-semibold" data-streamdown="strong">per-member</span> heartbeat and resource alerts on each server’s member <span class="font-semibold" data-streamdown="strong">Monitoring</span> tab. Pool <span class="font-semibold" data-streamdown="strong">Monitoring</span> is for <span class="font-semibold" data-streamdown="strong">fleet-level</span> and <span class="font-semibold" data-streamdown="strong">protection DNS</span> behavior.

##### <span class="font-semibold" data-streamdown="strong">Tab:</span> <span class="font-semibold" data-streamdown="strong">Settings</span> (pool tab bar).

<span class="font-semibold" data-streamdown="strong">Purpose:</span> Pool identity, shared API credentials, danger zone.

#### What you see (Generic Linux)

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-card-content-pool-na"><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">Card</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Content</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="font-semibold" data-streamdown="strong">Pool name</span></div></td><td><div class="md-table-cell-content">Rename the pool</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">API providers</span></div></td><td><div class="md-table-cell-content">Cloudflare account keys (mark one <span class="font-semibold" data-streamdown="strong">Global</span>). Each cPanel server needs its <span class="font-semibold" data-streamdown="strong">own</span> WHM key (pool-level + per-member rows)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Danger zone</span></div></td><td><div class="md-table-cell-content">Delete pool</div></td></tr></tbody></table>

</div></div></div>##### What is NOT on the Generic pool Settings

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-feature-where-instea"><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">Feature</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Where instead</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="font-semibold" data-streamdown="strong">Balancer failover</span> (auto-failover delay, make-active hostname)</div></td><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Managed DNS</span> (when tab visible) or optional — Generic pools work without DNS</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Protection jobs</span></div></td><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Protection</span> tab</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">DNS record catalog</span></div></td><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Managed DNS</span> tab</div></td></tr></tbody></table>

</div></div></div>HAProxy pools include <span class="font-semibold" data-streamdown="strong">Balancer failover</span> on Settings.

# Part 2 - Member workspace (per-server tabs)

### Member tab bar (Generic Linux Server Pool)

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-tab-always-in-nav%3F-a"><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">Tab</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Always in nav?</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Active when</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="font-semibold" data-streamdown="strong">Control panel</span></div></td><td><div class="md-table-cell-content">Yes</div></td><td><div class="md-table-cell-content">Always</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Security</span></div></td><td><div class="md-table-cell-content">Yes</div></td><td><div class="md-table-cell-content">Always</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">OpenLiteSpeed</span></div></td><td><div class="md-table-cell-content">Yes (Generic)</div></td><td><div class="md-table-cell-content">Content when OpenLiteSpeed detected; else frosted <span class="font-semibold" data-streamdown="strong">not detected</span> overlay</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">MariaDB</span></div></td><td><div class="md-table-cell-content">Yes (Generic)</div></td><td><div class="md-table-cell-content">Content when MariaDB/MySQL detected or cPanel-managed MySQL</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">cPanel</span></div></td><td><div class="md-table-cell-content">Yes (Generic)</div></td><td><div class="md-table-cell-content">Content when cPanel detected else frosted overlay</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Status</span></div></td><td><div class="md-table-cell-content">Yes</div></td><td><div class="md-table-cell-content">Always — host/agent health summary</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Cron &amp; Jobs</span></div></td><td><div class="md-table-cell-content">Yes</div></td><td><div class="md-table-cell-content">Always</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Restore Backups</span></div></td><td><div class="md-table-cell-content">Yes</div></td><td><div class="md-table-cell-content">Always</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Recipes</span></div></td><td><div class="md-table-cell-content">Yes</div></td><td><div class="md-table-cell-content">Always</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Monitoring</span></div></td><td><div class="md-table-cell-content">Yes</div></td><td><div class="md-table-cell-content">Always</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Settings</span></div></td><td><div class="md-table-cell-content">Yes</div></td><td><div class="md-table-cell-content">Always</div></td></tr></tbody></table>

</div></div></div>## Member Control Panel

<span class="font-semibold" data-streamdown="strong">Purpose:</span> <span class="font-semibold" data-streamdown="strong">Host-level</span> operations on this Server — OS family, uptime, services, quick actions, TLS.

### What you see

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-section-content-heal"><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">Section</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Content</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="font-semibold" data-streamdown="strong">Health strip</span></div></td><td><div class="md-table-cell-content">Agent version, heartbeat age, firewall summary</div></td></tr><tr><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Console</span></div></td><td><div class="md-table-cell-content">Open a secure SSH session to the member</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">KPI row</span></div></td><td><div class="md-table-cell-content">CPU, memory, disk, load</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Quick actions</span></div></td><td><div class="md-table-cell-content">Reboot, shutdown, install updates, backup (stack-aware), Let’s Encrypt (when applicable)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Services</span></div></td><td><div class="md-table-cell-content">Running units relevant to detected stacks</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">SSL / TLS</span></div></td><td><div class="md-table-cell-content">Certificate expiry, sync domain from DNS</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Recent activity</span></div></td><td><div class="md-table-cell-content">Latest completed jobs</div></td></tr></tbody></table>

</div></div></div>## Member Security

**<span class="font-semibold" data-streamdown="strong">Tab:</span> <span class="font-semibold" data-streamdown="strong">Security</span>.**

<span class="font-semibold" data-streamdown="strong">Purpose:</span> <span class="font-semibold" data-streamdown="strong">Host firewall and SSH</span> on this member.

### What you see

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-section-content-fire"><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">Section</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Content</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="font-semibold" data-streamdown="strong">Firewall KPIs</span></div></td><td><div class="md-table-cell-content">Enabled/disabled, rule count, last sync</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Action tiles</span></div></td><td><div class="md-table-cell-content">Enable/disable firewall, add rule, manage rules, backup UFW config</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">SSH access</span></div></td><td><div class="md-table-cell-content">Key-based access helpers</div></td></tr></tbody></table>

</div></div></div>## Member OpenLiteSpeed

**<span class="font-semibold" data-streamdown="strong">Tab:</span> <span class="font-semibold" data-streamdown="strong">OpenLiteSpeed</span>.  
Sub Tabs: Overview, Recovery Wizard**

<span class="font-semibold" data-streamdown="strong">For:</span> VMs where <span class="font-semibold" data-streamdown="strong">OpenLiteSpeed is the web server</span> — standalone OLS hosts.

<span class="font-semibold" data-streamdown="strong">Not for:</span> Default cPanel/Apache hosting — expect a <span class="font-semibold" data-streamdown="strong">not-detected</span> overlay on cPanel servers.

### Overview

Web admin (`:7080` link), KPIs, virtual hosts table, reload/restart/config test/backup/upgrade/LSPHP, logs.

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-"><div class="ui-scroll-area__viewport">[![OpenLitespeed-Overview.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/openlitespeed-overview.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/openlitespeed-overview.png)</div></div>### Recovery Wizard

Also known as **Cross‑Member Restore**, this feature allows you to automatically take a full backup of an OpenLiteSpeed account, including SSL certificates and the database\*. The backup is stored within your **Pool API Storage**, and the **Recovery Wizard** allows you to restore that backup to a different OpenLiteSpeed server.

If you have a DNS API Key scoped for the domain, DNS records can be updated automatically during the recovery process. Simply select the appropriate API Key when using the Recovery Wizard.

\* **Database restoration requires that no additional MySQL/MariaDB password is set when accessing** `mysql` **from the command line.** If an additional password has been configured, the automated restore process cannot proceed, and the database will need to be restored manually.

### Recipes &amp; backups

<span class="font-semibold" data-streamdown="strong">Install OpenLiteSpeed</span>, <span class="font-semibold" data-streamdown="strong">Harden OpenLiteSpeed</span> on <span class="font-semibold" data-streamdown="strong">Recipes</span>. Restore on <span class="font-semibold" data-streamdown="strong">Restore Backups</span> (`backup_openlitespeed`, Pro).

## Member MariaDB

**<span class="font-semibold" data-streamdown="strong">Tab:</span> <span class="font-semibold" data-streamdown="strong">MariaDB</span>.  
Subtabs: Overview, Databases**

<span class="font-semibold" data-streamdown="strong">For:</span> Dedicated DB servers <span class="font-semibold" data-streamdown="strong">or</span> cPanel-managed MySQL on WHM hosts.

### Standalone database server

#### Overview

Health, KPIs, schema cards, restart, config test, flush privileges, logical backup, harden, logs.

[![MariaDB-MySQL-Overview.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/mariadb-mysql-overview.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/mariadb-mysql-overview.png)

#### Databases

The **Databases** tab lists all databases discovered on the OpenLiteSpeed server. Databases are detected by scanning for common configuration files such as `wp-config.php` and parsing their contents.

Databases can be backed up individually or via cron. For a full account backup, use the **Recovery Wizard**.

[![MariaDB-MySQL-Databases.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/mariadb-mysql-databases.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/mariadb-mysql-databases.png)

### cPanel-managed MySQL

If cPanel is on the same host: MariaDB tab shows a <span class="font-semibold" data-streamdown="strong">cPanel host</span> notice — use member <span class="font-semibold" data-streamdown="strong">cPanel</span> (§11) for account-level DB ops.

[![MariaDB-MySQL-cPanel.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/mariadb-mysql-cpanel.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/mariadb-mysql-cpanel.png)

### Galera (`wsrep`) (In Alpha)

When Galera is enabled, the agent will report the wsrep state. <span class="font-semibold" data-streamdown="strong">ServersCTL does not</span> run quorum, SST, or writer election. <span class="font-semibold" data-streamdown="strong">DNS is active ≠ Galera primary.</span>

### Recipes &amp; backups

<span class="font-semibold" data-streamdown="strong">Install MariaDB/MySQL</span>, <span class="font-semibold" data-streamdown="strong">Harden the database</span>. Restore Backups. Advanced recipes require Pro.

[![Member-Recepies.png](https://docs.serversctl.com/uploads/images/gallery/2026-06/scaled-1680-/member-recepies.png)](https://docs.serversctl.com/uploads/images/gallery/2026-06/member-recepies.png)

## Member cPanel

**<span class="font-semibold" data-streamdown="strong">Tab:</span> <span class="font-semibold" data-streamdown="strong">cPanel</span>.**

<span class="font-semibold" data-streamdown="strong">For:</span> WHM servers — the most common ServersCTL hosting workload.

<span class="font-semibold" data-streamdown="strong">Inner tabs:</span> <span class="font-semibold" data-streamdown="strong">Overview</span> · <span class="font-semibold" data-streamdown="strong">Operations</span> · <span class="font-semibold" data-streamdown="strong">Accounts</span> · <span class="font-semibold" data-streamdown="strong">Migrate &amp; Recovery</span>

### Overview (inner)

DNS banner, protection topology (when account is protected), service badges, WHM audit summary.

### Operations (inner)

Restart web/mail/cPanel, config check, WHM backup, harden, WHM API status, listeners, disk, metrics.

### Accounts (inner) *(Pro)*

CRUD, suspend, terminate, backups, AutoSSL, one-time login. Free: read-only, 5 accounts cap.

### Live Migrate &amp; Recovery (inner) (Pro)

Live transfer, sessions, push copy. <span class="font-semibold" data-streamdown="strong">Bulk</span> replication and schedules: pool <span class="font-semibold" data-streamdown="strong">Protection</span> (§3), not this inner tab alone.

### WHM Binding

Full WHM API when member matches pool `host`. Run <span class="font-semibold" data-streamdown="strong">WHM link check</span> recipe after DNS connect.

## Member Status

**<span class="font-semibold" data-streamdown="strong">Tab:</span> <span class="font-semibold" data-streamdown="strong">Status</span>.**

<span class="font-semibold" data-streamdown="strong">Purpose:</span> Read-only <span class="font-semibold" data-streamdown="strong">health and readiness</span> snapshot for this member.

### Generic Linux member

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-section-content-os-%2F"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover"><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">Section</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Content</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="font-semibold" data-streamdown="strong">OS / agent</span></div></td><td><div class="md-table-cell-content">OS family, agent version (outdated warning), generic probe template</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Live stats</span></div></td><td><div class="md-table-cell-content">CPU, memory, disk from last heartbeat</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Updates</span></div></td><td><div class="md-table-cell-content">Pending package updates</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Firewall</span></div></td><td><div class="md-table-cell-content">Summary from heartbeat</div></td></tr></tbody></table>

</div></div></div></div></div></div>## Member Cron &amp; Jobs

**<span class="font-semibold" data-streamdown="strong">Tab:</span> <span class="font-semibold" data-streamdown="strong">Cron &amp; Jobs</span>.**

<span class="font-semibold" data-streamdown="strong">Purpose:</span> <span class="font-semibold" data-streamdown="strong">Scheduled tasks</span> on this member and visibility into recent job activity.

### What you see

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-section-content-kpi-"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover"><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">Section</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Content</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="font-semibold" data-streamdown="strong">KPI row</span></div></td><td><div class="md-table-cell-content">Cron count, backup schedules, last run</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Schedules</span></div></td><td><div class="md-table-cell-content">Enable/disable, edit schedule (UTC), add from presets</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Recent jobs</span></div></td><td><div class="md-table-cell-content">Timeline of agent and worker jobs</div></td></tr></tbody></table>

</div></div></div></div></div></div>### Common presets

Stack backups (`<span class="md-inline-path-filename">backup.cpanel</span>`, `<span class="md-inline-path-filename">backup.database</span>`, etc.), `<span class="md-inline-path-filename">failover.evaluate</span>` where applicable.

## Member Restore Backups

<p class="callout info">We are developing off-site backups. We currently only backup configuration unless stated in the UI. Contact us if you have questions.</p>

<span class="font-semibold" data-streamdown="strong">Tab:</span> <span class="font-semibold" data-streamdown="strong">Restore Backups</span>.

<span class="font-semibold" data-streamdown="strong">Purpose:</span> <span class="font-semibold" data-streamdown="strong">Snapshot catalog</span> for this member — run backup, restore, delete.

### Modes

Tab adapts to detected stack: `cpanel`, `openlitespeed`, `database`, or `mixed`.

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-section-content-kpi--1"><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">Section</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Content</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="font-semibold" data-streamdown="strong">KPI row</span></div></td><td><div class="md-table-cell-content">Snapshot count, total size, last backup</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Calendar</span></div></td><td><div class="md-table-cell-content">Backup history</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Snapshot cards</span></div></td><td><div class="md-table-cell-content">Restore or delete individual snapshots</div></td></tr></tbody></table>

</div></div></div><span class="font-semibold" data-streamdown="strong">Pro</span> required for restore actions on stack backups.

## Member Recipes

<p class="callout info">In development - Only ever use recipes on clean servers. </p>

**<span class="font-semibold" data-streamdown="strong">Tab:</span> <span class="font-semibold" data-streamdown="strong">Recipes</span>.**

<span class="font-semibold" data-streamdown="strong">Purpose:</span> <span class="font-semibold" data-streamdown="strong">Guided install and harden flows</span> — one-click enqueue of multi-step agent jobs.

### Examples by stack

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-recipe-stack-install"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><table style="width: 39.6429%; height: 238.172px;"><thead class="bg-muted/80" data-streamdown="table-header"><tr class="border-border border-b" data-streamdown="table-row" style="height: 29.7969px;"><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell" style="width: 62.6506%; height: 29.7969px;">Recipe</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell" style="width: 37.3494%; height: 29.7969px;">Stack</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" style="height: 29.7969px;"><td style="width: 62.6506%; height: 29.7969px;"><div class="md-table-cell-content">Install cPanel / harden</div></td><td style="width: 37.3494%; height: 29.7969px;"><div class="md-table-cell-content">cPanel</div></td></tr><tr class="border-border border-b" data-streamdown="table-row" style="height: 29.5938px;"><td style="width: 62.6506%; height: 29.5938px;"><div class="md-table-cell-content">Install OpenLiteSpeed / harden</div></td><td style="width: 37.3494%; height: 29.5938px;"><div class="md-table-cell-content">OLS</div></td></tr><tr class="border-border border-b" data-streamdown="table-row" style="height: 29.7969px;"><td style="width: 62.6506%; height: 29.7969px;"><div class="md-table-cell-content">Install MariaDB / harden</div></td><td style="width: 37.3494%; height: 29.7969px;"><div class="md-table-cell-content">MariaDB</div></td></tr><tr class="border-border border-b" data-streamdown="table-row" style="height: 29.7969px;"><td style="width: 62.6506%; height: 29.7969px;"><div class="md-table-cell-content">Galera cluster (read-only)</div></td><td style="width: 37.3494%; height: 29.7969px;"><div class="md-table-cell-content">MariaDB + wsrep</div></td></tr><tr class="border-border border-b" data-streamdown="table-row" style="height: 29.7969px;"><td style="width: 62.6506%; height: 29.7969px;"><div class="md-table-cell-content">SSH hardening, agent update</div></td><td style="width: 37.3494%; height: 29.7969px;"><div class="md-table-cell-content">Host</div></td></tr><tr class="border-border border-b" data-streamdown="table-row" style="height: 29.7969px;"><td style="width: 62.6506%; height: 29.7969px;"><div class="md-table-cell-content">WHM link check</div></td><td style="width: 37.3494%; height: 29.7969px;"><div class="md-table-cell-content">cPanel + DNS</div></td></tr><tr class="border-border border-b" data-streamdown="table-row" style="height: 29.7969px;"><td style="width: 62.6506%; height: 29.7969px;"><div class="md-table-cell-content">Let’s Encrypt (host or HAProxy)</div></td><td style="width: 37.3494%; height: 29.7969px;"><div class="md-table-cell-content">Host / edge</div></td></tr></tbody></table>

</div></div></div></div></div></div>## Member Monitoring

**<span class="font-semibold" data-streamdown="strong">Tab:</span> <span class="font-semibold" data-streamdown="strong">Monitoring</span>**

<span class="font-semibold" data-streamdown="strong">Purpose:</span> <span class="font-semibold" data-streamdown="strong">Per-member</span> alert settings.

### What you configure

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-setting-meaning-hear"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content"><div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover"><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">Setting</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Meaning</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">Heartbeat miss alerts</div></td><td><div class="md-table-cell-content">Email when agent stops checking in</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">CPU / disk / service thresholds</div></td><td><div class="md-table-cell-content">Resource alerts for this server</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Alert recipients</div></td><td><div class="md-table-cell-content">Account email + optional team inboxes</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content">Recovery notifications</div></td><td><div class="md-table-cell-content">Notify when member recovers</div></td></tr></tbody></table>

</div></div></div></div></div></div>## Member Settings

**<span class="font-semibold" data-streamdown="strong">Tab:</span> <span class="font-semibold" data-streamdown="strong">Settings</span>** (member tab bar).

<span class="font-semibold" data-streamdown="strong">Purpose:</span> <span class="font-semibold" data-streamdown="strong">Identity and location</span> for this enrolled server.

### What you see

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk-section-content-disp"><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">Section</th><th class="whitespace-nowrap px-4 py-2 text-left font-semibold text-sm" data-streamdown="table-header-cell">Content</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="font-semibold" data-streamdown="strong">Display name / hostname</span></div></td><td><div class="md-table-cell-content">Must match agent JSON; `BALCTL_HOSTNAME` to override</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Allowed source IPs</span></div></td><td><div class="md-table-cell-content">Member egress IPv4 allowed to call `<span class="md-inline-path-filename">serversctl.com</span>`</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Server location</span></div></td><td><div class="md-table-cell-content">Geo for pool Overview map and Protection geo map</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Monitor settings</span></div></td><td><div class="md-table-cell-content">Member-level alert toggles (overlaps with <span class="font-semibold" data-streamdown="strong">Monitoring</span> tab)</div></td></tr><tr class="border-border border-b" data-streamdown="table-row"><td><div class="md-table-cell-content"><span class="font-semibold" data-streamdown="strong">Remove member</span></div></td><td><div class="md-table-cell-content">Detach server from pool</div></td></tr></tbody></table>

</div></div></div>WHM API keys for cPanel: prefer <span class="font-semibold" data-streamdown="strong">pool Settings</span> / <span class="font-semibold" data-streamdown="strong">Managed DNS</span> API providers; per-member WHM edit is available there.

<div class="ui-scroll-area" data-direction="horizontal" data-scroll-padding="4" data-visibility="hover" id="bkmrk--5"><div class="ui-scroll-area__viewport"><div class="ui-scroll-area__content">  
</div></div></div>