Skip to content

Blog ·

Automating CloudPanel billing and provisioning

A paid order, an overdue invoice, a cancellation. CloudPanel has a command for two of those three. The third is a vhost edit, and that choice has consequences.

By Provibr

An automated hosting product has exactly three moments where money and machines touch each other:

  1. The order is paid. The site has to exist, correctly configured, within seconds.
  2. The invoice is overdue. The site has to go dark, and come back the moment it is paid.
  3. The subscription ends. The site has to be removed, for real, including its data.

CloudPanel has a clean command for moment one and a clean command for moment three. For moment two it has nothing at all. That is not a small footnote on an otherwise ordinary integration; it is the defining fact of automating billing on this panel, and it deserves the middle of this page rather than a line at the bottom.

The three moments, and what CloudPanel offers

MomentCloudPanelExists?
Paid orderclpctl site:add:php (plus clpctl db:add for a database)Yes
OverdueNo command; the vhost is the leverNot in the panel
Paid after allNo command; the same lever, reversedNot in the panel
Upgrade / downgradeNo command; there is no package conceptNo
Cancellationclpctl site:delete --domainName=… --forceYes

Everything runs through clpctl, the panel's command-line tool. There is no REST API: CloudPanel is a server-rendered panel plus a CLI, and automation goes through the CLI or it does not go.

The two commands that do exist are good ones. site:add:php takes the domain, the PHP version, a vhost template and the Linux site user with its password, and comes back having written the vhost, the pool and the home directory. site:delete --force is a real delete, not a soft one. Between them sits the gap.

The missing middle, and how it gets filled

Suspension is the action a billing system performs more than any other, because it is the one that runs unattended, on a schedule, against accounts nobody is looking at. It is also the only reversible lever you have between "keep hosting them for free" and "delete their business".

CloudPanel has no word for it. There is no site:suspend, no site:disable, no maintenance flag in the panel, and no per-site status you can flip. So if you sell CloudPanel hosting, suspension is something you build. The realistic options, with their honest costs:

Stop the site's PHP-FPM pool. Fast, reversible, and it genuinely stops the site serving. It also produces a 502 rather than an explanation, so your customer sees a broken website rather than a suspended account. And it is a systemd operation on the host, not a clpctl one, so it is yours to keep working across panel upgrades.

Edit the vhost. The site's NGINX configuration is a file on the host, and one line in it decides whether the site answers. Reversible, instant, and it costs the customer nothing but access. The catch is that you are writing into a file CloudPanel owns, so it matters a great deal how you write into it.

Delete with a backup first. Unambiguous, and permanent in a way that will eventually cost you a customer who paid on day thirty-one. Restoring is not a switch, with everything that implies about DNS, certificates and the mail you have to send afterwards.

There is no fourth option that is clean. What matters is that you pick one deliberately and make sure the person who answers the phone knows which one it is. The failure mode here is not technical. It is a support agent promising a customer their site will come straight back, on a system where coming back means restoring a backup.

What a careful vhost edit looks like

The second option is the one worth doing properly, and "properly" is a short list. This is how Provibr does it, and the reasoning transfers whether you use Provibr or write it yourself.

Mark what you inserted. The suspension is a return 403; wrapped in a begin and an end comment. Removing it means deleting exactly the lines between those markers, so resuming leaves the file byte-for-byte as it was, and a return 403; the customer put there for their own reasons survives untouched. A search-and-replace on return 403 would not have that property.

Every public server block, not the first one. A vhost usually has a block for port 80 and one for 443. Inject into one and you get a suspended site over HTTP and a perfectly healthy one over HTTPS, which is worse than not suspending at all, because it looks like it worked.

Test the config before reloading it. nginx -t runs first. A suspension that takes down every other site on the box is not a suspension, and this is a shared web server by design.

Make the state readable from the host. Because the marker is a comment in a file, the suspended sites are a grep away and the agent reads that back rather than trusting its own database. If someone edits the file by hand, the next poll notices.

What none of this changes: CloudPanel itself still has no concept of a suspended site. The panel will show it as an ordinary site, because from the panel's point of view it is one. Anyone with CloudPanel access, including the customer you just suspended, can open that file and take the line out. The state is readable and it is enforced at the web server, but it is not owned by the panel, and no arrangement on this panel can make it so.

There is no upgrade path either, because there are no packages

CloudPanel has no plans, no quotas and no per-site limits. That removes an entire moment from the list above: there is no change package call, because there is no package.

It also means the difference between your plans has to be real somewhere else. If your €5 plan and your €15 plan land on the same CloudPanel with the same everything, then what your customer bought was a price, and the first one who works that out will tell the others. Enforcement lives above the panel: separate hosts per tier, quotas at the filesystem level, or limits your own platform applies and counts.

Reconciliation needs a list, and there is not one

clpctl can list panel users. It cannot list sites; those live in the panel's own SQLite database on the host.

For billing that is a real hole, because a billing system is supposed to be able to ask two questions at any time: does everything I am charging for still exist? and is there anything here I am not charging for? Both are answered by comparing your records against a list of what is really on the machine. Without a list you find out about a hand-deleted site when the customer emails, and about a hand-created one never.

Whatever you build over CloudPanel has to source that list from somewhere other than clpctl, and it will need host access to do it. In practice there is exactly one place: the panel's own database, /home/clp/htdocs/app/data/db.sq3, where a plain select over the site table gives you the domain, the Linux user, the type, the vhost template and the PHP version in one go. Provibr reads it there.

That works, and it is worth being clear about what you are agreeing to when you do it. That schema is CloudPanel's internal business, not a published interface, so it can change in a release without anyone having broken a promise. Reading it is the right call, because a billing system without reconciliation is worse, but it belongs on the short list of things to re-check after a panel upgrade, next to the clpctl output formats.

The credential is not an API token

Worth being blunt about, because it is the biggest practical difference between automating CloudPanel and automating a panel with a REST API.

clpctl runs as root. It writes NGINX configuration, creates Linux users and manages services, so root is not an oversight. It is the job. Anything that drives it therefore holds administrative access to a production web server carrying other people's sites, not a scoped token limited to one API surface.

Two consequences for anyone wiring billing into this. Blast radius is different: a leaked panel API token lets someone create and delete accounts, while a leaked shell credential on the web server is a different conversation with a different set of obligations. And least privilege is your own work. There is no permissions checkbox to lean on, so scoping is something you design rather than configure.

Where the invoice actually lives

Worth being explicit, because it is the most common misunderstanding: Provibr does not invoice your customers. It does not send them a bill, does not chase them, and does not know what you charge them. That relationship is yours.

What Provibr bills is the capacity you reserve: how many hosting accounts you may run at once, per month, at a tiered unit price. What you charge your own customers, and how, stays entirely inside your own billing system.

There are two ways to connect the two halves:

A WHMCS module. If you already run WHMCS, install the Provibr provisioning module and point one WHMCS product at one Provibr web plan. Your customer keeps one account, one invoice and one login. The module is a plain zip you install yourself, and it runs inside your WHMCS; there is nothing to enable on our side.

The REST API. If you run FOSSBilling, Blesta, something in-house, or a spreadsheet and a cron job, drive it directly. One key, one base URL, and exactly the permissions the member behind the key already has.

The one rule for driving it from your own code

Everything that touches a machine is asynchronous, and it has to be. Your infrastructure is reachable only through the agent inside your own network, and that agent dials outbound. So an endpoint that changes an account cannot answer with the result. It answers that the order was accepted and hands you a command to follow, which you read until its status is final: done, failed, timed out or cancelled.

Reading never works this way. An inventory, an address or an invoice answers immediately, because that answer comes from a database and not from your host.

The practical consequence for a billing hook: do not mark the order complete on the accepted answer. The answer you get first is "we sent it", not "it worked".

Suspension is also your own cost control

One connection that is easy to miss, and it is the reason the missing middle above costs money as well as goodwill. In Provibr, a suspended server stops counting towards the capacity you pay for. For a hosting account there is no "stopped": there is active, and there is suspended.

So the dunning step you already wanted for commercial reasons also frees the slot you are paying for, provided suspension is a real action rather than a note in your own database. That is the practical argument for building it rather than leaving the middle empty: an unpaid account you have not yet decided to delete is capacity you keep paying for.

Everything above about the panel is a property of CloudPanel rather than a description of an integration, which is deliberate: the gaps are real and they are not ours to wish away. What an integration can do is choose carefully which of the three options it takes, and say out loud what that choice still does not fix.

If you also sell VPS, game servers or cPanel accounts, the same three moments apply to all of them, and they can live in one inventory. Provibr runs its control panels, hypervisors and game panels side by side today. They share one set of permissions and one activity trail across the lot.

Start with one host and one agent

Create an account, install the agent with a single command and connect your first Proxmox host. The trial runs for 14 days and asks for no payment details.