On this page

cPanel / WHM

A cPanel/WHM server can join through Connect like any other server. The agent on it then carries out cPanel operations locally on your behalf, so you can put cPanel sites behind the edge from the panel: create or reuse the account, add the domain to it, create the zone and its proxied records, and lock the site's .htaccess to the edge.

The control plane never holds a WHM API token or a cPanel password. Every cPanel operation is a typed command executed by the agent on the WHM server, which validates every argument again and runs cPanel's own tools with argument arrays (never a shell string).

Joining a WHM server

  1. Create a Connect application (or use an existing one) and install the agent on the WHM server as described in Connect → Installing the agent, with one difference: run it as root. cPanel operations (whmapi1, the account's files) need root, so on a WHM server use User=root in the systemd unit (the gpagent user, setcap and the capability lines are not needed).

  2. Detection (GP_AGENT_CPANEL=auto, the default): the agent treats the machine as a WHM server when /usr/local/cpanel/version contains a numeric version and /usr/local/cpanel/bin/whmapi1 is an executable file. It then logs cPanel/WHM <version> detected; cPanel commands enabled and reports the capability whm and the version when it joins and every time it connects. GP_AGENT_CPANEL=off disables detection; the server is then an ordinary Connect server.

  3. Choose the application's cpanel permission before approving:

    ValueAllows
    noneNothing through this server (it can still be approved).
    readList the server's cPanel accounts and its sites.
    manageEverything in read, plus create accounts, add domains and sites, and write or remove the .htaccess protection.

    A server that reported whm cannot be approved until the permission is chosen (the panel says "Choose the application's cPanel permission, then approve the server from the application page"); the same holds for automatic approval.

  4. Approve the server. Its page (Connect → server) shows the cPanel version, the accounts (read live from the server, so it must be online) and the sites.

API: GET /api/v1/connect/servers/{id}, GET /api/v1/connect/servers/{id}/cpanel/accounts (each account: username, domain, plan, contact_email, suspended, ip, ipv6), GET /api/v1/connect/servers/{id}/cpanel/sites.

Adding a cPanel site

On the server page, Add cPanel site (requires manage). API: POST /api/v1/connect/servers/{id}/cpanel/sites.

FieldMeaning
usernameThe cPanel account: 1-16 lowercase letters and digits.
create_accounttrue to create a new account; false to use an existing one.
planPackage for a new account; empty = the server's default package.
contact_emailContact address for a new account (required when creating).
domainThe site's hostname.
domain_typemain (the account's primary domain, the default), addon, parked or subdomain. A new account's domain is always main; a subdomain needs a parent domain (at least three labels).
protection_modeip_allowlist or origin_token (see below).
include_wildcardAlso create a proxied * record for the site.

The site is then set up in six steps, run in order. Each step records its status (pending, done, skipped, failed, waiting) and a short detail, visible on the site. The flow is resumable: every step is idempotent, and Resume (POST /api/v1/connect/cpanel/sites/{id}/resume) continues where it stopped.

StepWhat it does
accountNew account: creates it on the server; its password is shown once (see below). If an account with this username already exists for the same domain it is reused (its password is not available); if the username belongs to another account the step fails. Existing account: checks that it exists and, for main, that the domain is its main domain.
cpanel_domainAdds the domain to the account as addon, parked or subdomain; skipped for main.
zoneFinds your zone for the domain or one of its parents (for blog.example.com, the zone example.com with records named blog); if none exists, creates a zone for the domain in your account (status pending_ns, "point the domain's nameservers at the platform"). A zone belonging to another account fails the step.
recordsCreates or updates proxied records for the site's name, www below it and, with include_wildcard, * below it: an A record, and an AAAA record when the account has an IPv6 address. The origin is the address Apache serves the account on; if that IPv4 address is private (cPanel behind 1:1 NAT) the server's public connection address is used; if the account reports no usable address, the server's connection address. TTL 300.
origin_protectionComputes the protection block (the edge addresses, or the domain's origin key).
htaccessWrites the managed block into the site's .htaccess — only once the zone is delegated (status active); until then the step is waiting.

One request runs steps for at most about 52 seconds; if steps remain, the response has "incomplete": true and Resume continues. Each command to the server times out after 30 seconds (50 seconds for creating an account). The response carries site (with steps, status — in_progress, active or error — and last_error), incomplete, and password when an account was created in that request.

Record conflicts are never overwritten: a CNAME at one of the site's names, or an A/AAAA record there that the application does not manage, fails the records step with "remove it and resume". Records the application created earlier are updated, and removed for an address family the account no longer has.

Protection modes

ip_allowlist

Apache answers only requests coming from the edge addresses; everything else — including direct visits to the server's IP — gets 403. Written block (addresses are the active edges):

apache
# BEGIN GreenProxy-managed
# Maintained automatically; edits inside this block are replaced.
<IfModule mod_authz_core.c>
Require ip 198.51.100.10 198.51.100.11
</IfModule>
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
Allow from 198.51.100.10
Allow from 198.51.100.11
</IfModule>
# END GreenProxy-managed

The block is re-written automatically when the set of active edges changes. It requires at least one active edge.

origin_token

Apache refuses requests that do not carry the domain's secret X-Origin-Token header, which the edge adds to every request. Works behind any network path. Written block:

apache
# BEGIN GreenProxy-managed
# Maintained automatically; edits inside this block are replaced.
<IfModule mod_authz_core.c>
<If "req('X-Origin-Token') != '<origin key>'">
Require all denied
</If>
</IfModule>
<IfModule !mod_authz_core.c>
RewriteEngine On
RewriteCond %{HTTP:X-Origin-Token} !^<origin key>$
RewriteRule ^ - [F,L]
</IfModule>
# END GreenProxy-managed

On Apache 2.4 the <If> section applies to every subdirectory too, even one with its own .htaccess and RewriteEngine (WordPress in /blog/, Laravel's public/). The rewrite form is only the Apache 2.2 fallback (cPanel's EasyApache 4 ships Apache 2.4). The domain's origin key is created if it did not exist; when you rotate it, the new key is pushed to the site's .htaccess right away.

How the block is managed

  • It lives in the .htaccess of the domain's document root, between the # BEGIN GreenProxy-managed and # END GreenProxy-managed lines. A new block is inserted at the top of the file (after a UTF-8 byte-order mark if there is one); an existing block is replaced in place. Everything outside the markers is preserved byte for byte, and the file's line endings (LF or CRLF) are kept.
  • If the file holds an unbalanced, nested or repeated block, nothing is written and the step fails with "the managed .htaccess block is malformed; fix or remove it by hand".
  • A document root or .htaccess that is not a plain file inside the account is refused.
  • The control plane keeps a digest of the block last written. Whenever the content should change — edge addresses, a rotated origin key, a new block format — it is pushed again: when the edge set changes, when the server's stream opens, after an origin key rotation, and on a check every minute. A site whose server is offline or no longer permitted is marked "protection is out of date; it is updated when the server reconnects" or similar.

Waiting for delegation, and the override

While the zone is not delegated to the platform, public DNS still sends visitors straight to the server. A block that only admits the edge would refuse all of them, so the htaccess step waits ("waiting for the domain's nameservers to point at the platform") and completes automatically once verification marks the zone active.

Apply protection before delegation writes the block anyway — for example for a new site that has no visitors yet. API: POST /api/v1/connect/cpanel/sites/{id}/reapply with {"before_delegation": true}. The same endpoint without the flag re-runs the protection steps normally.

Password delivery

When the flow creates an account, the agent generates a random 24-character password (lowercase and uppercase letters, digits and -_.@*!). It is passed to cPanel in the body of a WHM API request to 127.0.0.1:2087, authenticated with a short-lived root API token created just before and revoked right after — never on a command line, where other local users could read it. The password is returned once, in the response of the request that created the account (password), and the panel shows it then. The control plane stores only the fact that it was delivered (password_delivered), never the password or a hash of it. If you miss it, reset the password in WHM.

Removing a site

Remove (DELETE /api/v1/connect/cpanel/sites/{id}) removes the managed block from the site's .htaccess and forgets the site. The cPanel account, its domains, the zone and its records stay.

  • If the server cannot be reached or refuses (offline, no longer approved, cPanel permission lowered), the site is kept and the request fails, unless you add ?force=true: then the site is forgotten anyway and the panel warns that the .htaccess block may still be on the server — remove it by hand.
  • A site of a revoked server is forgotten without contacting the server; its block stays.

What never leaves the server

  • No WHM API token: the agent creates a short-lived one locally only for the moment it needs one, and revokes it.
  • No cPanel password, except a new account's password in the single response delivered to you.
  • No file content: the control plane sends typed commands and receives results — the account list (username, main domain, plan, contact email, suspended flag, IP addresses), whether something changed, the document root path, and short generic error messages.

Limits and known gaps

  • The agent must run as root on a WHM server.
  • Only Apache .htaccess protection is managed; Apache 2.2 servers use the rewrite fallback, which subdirectories with their own RewriteEngine do not inherit.
  • Removing a site does not delete the account, the domain on the account, the zone or the records.
  • A missed new-account password cannot be shown again.
  • Account listing and every site operation need the server online.