---
title: Plesk
description: RackHelm creates a Plesk customer for each portal user and a subscription — a webspace — for each order item they buy, then suspends, unsuspends and terminates those subscriptions as the billing requires. This article is for the operator setting…
url: "https://rackhelm.com/documentation/plesk-integration/"
updated: "2026-09-24"
category: "Integrations: Control Panels"
---

# Plesk

RackHelm creates a Plesk customer for each portal user and a subscription — a webspace — for each order item they buy, then suspends, unsuspends and terminates those subscriptions as the billing requires. This article is for the operator setting that up: how to get an API key without ever storing a password, every field on the RackHelm form, how a product is bound to a Plesk service plan, and the Plesk-specific traps that will otherwise cost you an afternoon.

## Before you start

- The `edit_integrations` permission. See [Roles and Permissions](/docs/roles-and-permissions/).
- A Plesk server reachable from the RackHelm server. Plesk's admin interface and REST API normally listen on port **8443**.
- The login and password of the Plesk **administrator**, or of the reseller whose service plans your products will sell. You do not need a key in advance — RackHelm mints one for you and never stores the password.
- At least one hosting plan under **Plesk » Service Plans » Hosting Plans**, owned by that user.

> **Note:** The hint under **Base URL** says it plainly: "Plesk listens on port 8443. A private or LAN address (a Plesk that cannot be made public) is refused unless it is listed in `SAFEURL_PRIVATE_ALLOWLIST` in the portal's `.env` file." That allow-list is new in 1.0.1.1 and takes addresses or CIDR ranges, for example `SAFEURL_PRIVATE_ALLOWLIST="192.168.1.50,10.10.0.0/16"`. Everything else private stays blocked. See [Adding a Control Panel Connection](/docs/adding-a-panel-connection/).

> **Note:** RackHelm stores *one* Plesk connection, not a list. The form saves into a single row keyed on the provider name, so one RackHelm install drives one Plesk server. The **Panel** dropdown on a product will only ever offer that one entry.

## Add the connection

1. Open **Admin → Integrations** and press **Configure** on the **Plesk** card (it reads **Manage** once a connection exists).
2. Set **Base URL** to the Plesk origin including the port, for example `https://plesk.example.com:8443`.
3. If Plesk's certificate is self-signed, set **Verify SSL** to **No (pin certificate)**.
4. Under **Generate an API key**, enter the **Plesk login** and **Password** and press **Generate API key**. The key appears in the **API Key** field above.
5. Press **Save**. That single action stores the key and, if you pinned one, the certificate.

![The Plesk panel form showing Base URL with an 8443 placeholder, the API Key field, HTTP Timeout and Verify SSL](/wp-content/uploads/rackhelm-docs/integrations-core-panel-edit-plesk.png)
Plesk's credential field is labelled **API Key** and is sent as the `X-API-Key` header.

### Fields

| Field | Rule | Notes |
| --- | --- | --- |
| **Name** | Optional, max 120 characters | Defaults to "Plesk". |
| **Active** | Checkbox | Required for Plesk to appear as an **Integration Type** on products. |
| **Base URL** | Required, http/https, publicly resolvable or allow-listed | Placeholder `https://plesk.example.com:8443`. |
| **API Key** | Optional on edit, max 190 characters | Blank keeps the stored key. Stored encrypted. The hint tells you which Plesk user the saved key belongs to, or says "No key is saved yet." |
| **HTTP Timeout (sec)** | Integer, minimum 1 (form minimum 5) | Default 30. Creating and deleting a subscription override it to 180 seconds. |
| **Verify SSL** | **Yes** or **No (pin certificate)** | Read by the driver, the connection test and the product-form lookup. With **No**, Plesk's public key is pinned and any other key is refused. |
| **cPGuard API Key** | Optional | Relocated into the encrypted credential block on save, never left in the plain settings blob. |
| **Verify cPGuard SSL Certificate** | Checkbox, on by default | Untick for a self-signed certificate on port 9098. |

> **Note:** There is deliberately no password field on this form. The Plesk administrator password is used for exactly one request — the key request — and is not part of what gets saved. If an older build of RackHelm stored a password here, it is discarded the next time you press **Save**.

### The pinned certificate

With **Verify SSL** set to **No (pin certificate)**, a line appears below reading "No certificate is pinned yet. Click **Test API** (or **Generate API key**) to read the panel's certificate, then **Save** to pin it. Until then the portal refuses to connect with verification off." After a successful test it shows the certificate in the shape `CN=plesk.example.com, self-signed, expires 2027-03-14, SHA-256 A1:B2:…` with a **Forget pinned certificate** link beside it.

> **Warning:** Testing does not pin; **Save** pins. If you test, see "certificate pinned, click Save to keep it" and navigate away, nothing is stored. Later, when Plesk's certificate is replaced, calls fail with "The panel's certificate key no longer matches the pinned one. Open the panel settings, click Test API to see the new certificate, then Save to trust it." That refusal happens *before* the API key is sent, which is the point of it.

## Generate an API key

Plesk issues API keys to a logged-in user. The block at the bottom of the form does that for you: RackHelm posts `/api/v2/auth/keys` with the login and password you type, checks the new key works, and drops it into the **API Key** field for you to **Save**. The password is used for that one request and is never stored, never logged, and never posted to **Save**.

![The Generate an API key block with Plesk login, Password (not stored) and the Generate API key button](/wp-content/uploads/rackhelm-docs/integrations-core-plesk-generate-key.png)
One step: mint, verify, and fill the API Key field. The password box clears itself afterwards.

On success the status line reads something like "Plesk 18.0.80 · API key generated for admin · click Save to store it. The password was not kept.", and the hint under **API Key** changes to "New key generated for Plesk user admin. It is not saved yet: click Save."

> **Warning:** Plesk binds an API key to the IP address it saw the key request arrive from. A key you created on your laptop, or with `plesk bin secret_key` on the Plesk server itself, will be refused here with 401 however carefully you paste it. That is why the button exists: the key must be minted *from the RackHelm server*. The portal's own refusal says so — "Plesk rejected the API key. Keys only work from the IP address that created them, so generate a new one from this screen (Generate API key) rather than pasting in one made elsewhere."

Two login cases are worth knowing:

- **`root`** — Plesk lets the system root account authenticate as the administrator, but root is not a Plesk *user*, so it cannot own a key. RackHelm leaves the owner out in that case and Plesk assigns the key to the administrator; the form then shows the login as `admin`.
- **Any other non-Plesk login** — the password is accepted and the key request still fails. You get "Plesk accepted the password but cannot issue a key to '*name*' because that is not a Plesk user account. Log in as 'admin' (the Plesk administrator) or as a reseller. Keys always belong to a Plesk user."

The key is described in Plesk as your portal's name, host and today's date, so you can tell it apart in `plesk bin secret_key --list`.

> **Warning:** If minting succeeds but the verification step is refused, the message reads "Plesk created a key but then refused it from this server… The key ending "…*abc123*" is unused; remove it in Plesk with `plesk bin secret_key --delete -key <key>`." This means the two requests reached Plesk from different addresses — a NAT, a VPN or a proxy in the path. Fix the path first; each failed attempt leaves an orphaned key behind.

### When the problem is not your password

Plesk's **Tools & Settings » Security » Restrict Administrative Access** policy answers with HTTP 403 *before* it looks at any credential, which is indistinguishable from a wrong password by status alone. RackHelm recognises that specific refusal and tells you what to allow, naming the address Plesk saw:

```bash
Plesk's administrative access policy is blocking this server, which Plesk sees as 10.20.151.17.
In Plesk open Tools & Settings » Security » Restrict Administrative Access and allow 10.20.151.17
(or set the policy to allow everything except the listed networks), then try again.
The login and password were not the problem.
```

## Testing the connection

**Test API** runs `GET /api/v2/server` through the same client provisioning uses, with the TLS settings you have saved, and reports **OK · Plesk 18.0.80** on success. A pass means hostname, port, certificate, firewall and key are all correct.

Leaving the key box blank re-tests with the stored key. Two deliberate refusals:

- **"Re-enter the API token when changing the Base URL."** — you changed the base URL and left the key blank. The stored key is never rendered back to you, so resolving it onto a freshly typed host would be a way to have it delivered elsewhere.
- **"No saved API token; please enter it."** — nothing is stored yet. Use **Generate API key** first; that button does not need a saved key.

Any failure other than a refused credential or a recognised access policy reads "The panel rejected the connection test. See the application log for the upstream response." The wording is constant on purpose — varying it by Plesk's reply would turn the button into a probe for whatever host was typed into **Base URL**.

## Bind a product to Plesk

Edit the product under **Catalogue → Catalogue & Groups**, open the **Provisioning** tab and, in the **Server Integration** card, set **Integration Type** to **Plesk**. Choose the **Panel** and the **Service Plan** dropdown fills itself from the server.

![The Plesk block on a product's Provisioning tab with Panel and Service Plan dropdowns and the Reload plans from panel link](/wp-content/uploads/rackhelm-docs/integrations-core-plesk-product-config.png)
The service plan is read from Plesk, not typed.

| Field | What it is for |
| --- | --- |
| **Panel** | Which saved Plesk connection to use. Credentials come from this row and nowhere else — a product cannot supply its own base URL or key. |
| **Service Plan** | Required. "Plesk » Service Plans » Hosting Plans. Each subscription this product creates is put on this plan; only plans owned by the API key's user are listed." |
| **Suspend after (days past due)** | Stored, default 1. Not read by anything — see the warning below. |
| **Terminate after (days after suspend)** | Stored, default 7. Same. |
| **Provisioning Notes (internal)** | Staff-only free text. Never shown to customers. |

Press **Reload plans from panel** after adding a plan in Plesk; the list is cached for 60 seconds otherwise. A saved plan the server no longer lists stays selected and is relabelled, for example `Unlimited (saved value, not found on panel)`, rather than being silently dropped.

> **Note:** Plesk's REST API has no service-plan endpoint, so the list comes from one XML API call to `/enterprise/control/agent.php`, authenticated with the same key. If that surface is disabled or the key's user is not allowed to use it, the dropdown falls back to a plain text box with "Could not load the service plans from the panel: … You can type the plan name instead." and the placeholder "Service plan name exactly as shown in Plesk". A server with no plans at all reports "Plesk returned no service plans for this API key's user. Create one under Service Plans » Hosting Plans."

> **Note:** Unlike in 1.0.0.0, **Service Plan** is now validated as required. Saving a Plesk product without one fails with "The integration.plesk.package id field is required."

> **Warning:** **Suspend after (days past due)** and **Terminate after (days after suspend)** are saved on the product and then ignored. The timing lives on the schedules **Suspend Overdue Services** and **Terminate Overdue Services** under **Schedules**, each with its own `days` option, and **both ship disabled**. Until you enable them nothing is suspended or terminated automatically, whatever those product fields say. See [Suspending, Unsuspending and Terminating a Service](/docs/suspending-and-terminating-services/).

> **Warning:** The **Enable Provisioning** switch at the top of the **Server Integration** card is not written when a product is saved. Switching it off does not stop provisioning; the integration is rebuilt as active every time. To sell a product without automatic provisioning, set **Integration Type** to **None / Manual Processing**.

## The domain each subscription is created for

A Plesk webspace is named after a domain, and RackHelm takes it from the order item — the `DomainName` the customer typed at checkout, or a `domain` already stored on the item. Attach the seeded **Domain Name** custom field (key `DomainName`) to the product under **Options & Add-ons → Configurable Options**, or give the product a fixed saved value. See [Custom Fields](/docs/custom-fields/).

Two failures come from this step:

```bash
Missing DomainName (OrderItem.variable_values or product/group saved variable).
'shop' is not a valid domain name for a Plesk subscription.
```

The second means the value is not a hostname — no dot, an illegal character, or shorter than four characters. Plesk is stricter here than cPanel, so a bare label that would have produced a cPanel account is refused outright.

## What provisioning does

Nothing provisions itself: the **Provision Orders** schedule finds paid items awaiting provisioning and queues each one, and like most schedules it ships disabled — enable it under **Schedules**. See [How Automatic Provisioning Works](/docs/automatic-provisioning/). Once an item is queued the driver:

1. Checks the product's service plan actually exists on the server. If it does not: "Plesk has no service plan named 'Silver' for the API key's user — check the product's Service Plan setting (available: Unlimited, Starter)." If the *list* cannot be fetched for a reason other than a refused key, the check is skipped and the subscription call answers authoritatively instead.
2. Refuses to touch a domain that already exists on the server: "The domain 'example.com' already exists on the Plesk server under another subscription." A same-named webspace RackHelm did not create is somebody else's, and adopting it would hand a stranger's site to a new customer.
3. Finds or creates the Plesk customer for this portal user. The login is derived from the local part of their email address — letters and digits only, at most 16 characters, starting with a letter — and falls back to that plus their user id, then plus a random suffix, if the login is taken. Plesk's `external_id` carries the portal user id, which is how a re-link after a lost local record can identify our own customer rather than adopting a same-login stranger.
4. Invents an FTP login from the domain (at most 14 characters, never starting `test` or `admin`) and a 16-character password.
5. Creates the subscription with `POST /domains`, `hosting_type: virtual`, the customer as owner and the plan by name. Neither `base_domain` nor `parent_domain` is sent, which is what makes Plesk create a new webspace rather than an add-on domain.
6. Stores the numeric Plesk domain id as the item's external id, plus `panel_id`, `client_id`, `domain`, `domain_id`, `guid`, `plan`, `ftp_login` and the encrypted FTP password under the item's `plesk` metadata key. The link between portal user and Plesk customer is kept separately, per panel.

Every step is safe to re-run. A webspace this driver already created is adopted rather than duplicated, and an existing Plesk customer carrying our user id is reused.

> **Warning:** The generated Plesk customer password and FTP password are stored only as ciphertext and nothing reads them back. They are not emailed — the "Your Service is Ready!" notification carries a dashboard link and no credentials — and no screen reveals them. Customers work through the portal's **Sites** area, which mints its own one-time Plesk sessions. If someone needs the password itself, reset it in Plesk.

> **Warning:** Provisioning is attempted **once**. A failed item is marked failed, an admin notification "Provisioning failed for order item #…" is raised, and nothing retries it — the background scan only picks up items that are pending or unstarted. Clear it by hand from **Pending Provisioning** with **Retry Provisioning** or **Mark Provisioned**. See [The Pending Provisioning Queue](/docs/pending-provisioning-queue/).

## Renewal, suspension and termination

| Action | What RackHelm does |
| --- | --- |
| Renew | Nothing on the server. The driver answers "Hosting renewal is billing-only. No remote action needed." |
| Suspend | `PUT /domains/{id}/status` with `suspended`. |
| Unsuspend | The same call with `active`. |
| Terminate | `DELETE /domains/{id}`, tolerating a webspace that has already gone, then the customer clean-up below. |

All three read the numeric domain id from the order item. Without it they fail with "Plesk domain id not found on OrderItem."

> **Warning:** Terminating the customer's *last* subscription also deletes their Plesk customer account and the portal's link to it. Plesk removes a customer's subscriptions along with the customer, so this only runs when Plesk itself confirms the customer has no webspaces left. If that listing is unavailable — older Plesk builds do not serve it — the clean-up is skipped and the result says so: "Plesk subscription terminated (Plesk client cleanup skipped: …)". Those orphaned Plesk customers accumulate quietly; tidy them up by hand.

## What the customer sees

A provisioned Plesk item appears under **Sites** in the client portal, and since 1.0.1.1 that area is real on Plesk, not just Enhance. The portal translates its own screens onto three Plesk surfaces — REST for subscriptions, customers and databases; the XML API for the DNS zone, mail, FTP users, webspace properties and certificates; and the CLI gateway for everything neither exposes. The customer gets **FTP**, **Domains** and DNS records, **Email**, **SQL** databases and users (with phpMyAdmin reached through a one-time Plesk session minted for the subscription's own owner), the PHP version, and WordPress installs through WP Toolkit.

> **Note:** **Backups are not offered on Plesk.** Plesk's backup list is per storage, not per subscription, so a customer cannot be shown only their own. The Backups card is hidden rather than shown broken, and anyone reaching it by URL is told "Creating a backup is handled in Plesk under Backup Manager on this platform, where you can also download a copy. Open your control panel to run one, or contact support and we will take care of it." Restores are deliberately staff-only: "Restoring a Plesk backup is done by our team so nothing is overwritten by accident. Please open a ticket with the date you would like restored." cPanel is the only other panel that does offer backups in the portal.

Signing straight into WordPress is the other gap: "Signing straight into WordPress is not available from here on Plesk hosting. Open your control panel, go to WP Toolkit and use the Log In button on the installation, which signs you in without a password." Installing, removing and updating WordPress all work from the portal.

See [My Sites: the site dashboard](/docs/my-sites-dashboard/) for the customer's view.

## Troubleshooting

| Symptom | Cause and fix |
| --- | --- |
| "Plesk panel has Verify SSL switched off but no pinned certificate" | The upgrade case. Open the connection, **Test API**, then **Save**. |
| "The panel's certificate key no longer matches the pinned one" | Plesk's certificate was replaced. **Test API** to see the new one, then **Save** to trust it. |
| "The panel presented a certificate the portal cannot verify" | Verification is on and Plesk's certificate is self-signed. Set **Verify SSL** to **No**, test, then save to pin it. |
| Test API says the key was rejected, and the key was pasted in | Plesk binds keys to the IP that created them. Use **Generate API key** on this screen instead. |
| 403 that looks like a wrong password | Plesk's administrative access policy. The message names the address to allow under **Tools & Settings » Security » Restrict Administrative Access**. |
| "Plesk accepted the password but cannot issue a key to '…'" | That login is not a Plesk user. Use `admin`, or a reseller login. |
| "Plesk created a key but then refused it from this server" | The mint and verify requests reached Plesk from different addresses. Remove the orphaned key with `plesk bin secret_key --delete -key <key>` and fix the NAT or VPN in the path. |
| "Plesk needs an API key. Open Admin » Integrations » Plesk and generate one with the Plesk admin login and password." | No key is stored. Generate one and save. |
| Service Plan dropdown is empty or has become a text box | The XML API could not be reached, or the key's user owns no plans. The message on the form says which. |
| Product will not save | **Service Plan** is empty: "The integration.plesk.package id field is required." |
| Order fails with "Missing DomainName …" | The product has no **Domain Name** configurable option and no saved value. |
| "'…' is not a valid domain name for a Plesk subscription." | The customer typed a bare label, not a hostname. Correct the item's domain and retry. |
| "The domain '…' already exists on the Plesk server under another subscription." | Deliberate. Remove or rename the existing webspace in Plesk, or provision this item by hand. |
| "Plesk has no service plan named '…' for the API key's user" | The plan was renamed or belongs to a different Plesk user. Reload the list on the product and pick again. |
| Nothing is ever suspended for non-payment | The **Suspend Overdue Services** schedule is disabled by default. The product's **Suspend after** value is not used. |
| Plesk missing from the Integration Type dropdown | The connection is saved but not **Active**. |
| Base URL rejected on save | The host resolves to a private, reserved or unresolvable address. Use a public hostname, or list the address in `SAFEURL_PRIVATE_ALLOWLIST`. |
| Item stuck showing "We're setting up your service…" | The job was killed mid-run, leaving the item in *processing*. Resolve it from **Pending Provisioning**. Subscription creation is allowed 180 seconds and the job 270, so the worker timeout must be at least that; see [Queue Workers, Horizon and the Scheduler](/docs/queue-workers-and-scheduler/). |
