CorePanel CLI
The corepanel command-line interface is the administrative tool for operating a CorePanel
server from the shell. It talks directly to corepanel-core over a local Unix socket, so it
is ideal for initial provisioning, automation, and troubleshooting — no browser required.
Overview
Section titled “Overview”corepanel system init # provision a fresh servercorepanel status # are the services up?corepanel account … # hosting accounts: create, update, import, back up, restorecorepanel domain / subdomain … # domains, aliases and subdomainscorepanel mail … # mailboxes, forwarders, antispamcorepanel cron … # per-account cron jobscorepanel wp … # WordPress installationscorepanel app … # application runtimecorepanel quota … # disk quotascorepanel ssl … # commercial certificatescorepanel firewall … # host firewall (firewalld)corepanel license / edition … # licensing and the edition in effectcorepanel notifications … # what the server has been trying to tell youEvery group answers --help, and so does every command inside it.
Naming an account
Section titled “Naming an account”Every command that acts on a hosting account takes it as <account>, and accepts any of
three spellings:
| Spelling | Example |
|---|---|
| Linux username | corepanel domain list acmecorp |
| Any domain the account owns — primary, alias or addon | corepanel domain list example.com |
| Any subdomain of one of those | corepanel domain list shop.example.com |
Numeric account id (the legacy dom-{id} form works too) | corepanel domain list 7 |
Reach for the username. It is what appears in your billing system, in /etc/passwd and in
a support ticket, so it is the name you already know — whereas the numeric id is an
internal row number you would have to look up first. The id keeps working everywhere it
ever did, so existing scripts need no change.
A reference that names no account fails with a non-zero exit status, and that includes
ids: corepanel domain list 999 reports that no account matches rather than printing an
empty list, which would read like a real answer for an account that was never there.
There is no ambiguity for CorePanel to resolve: a domain always contains a dot and a Linux username never can, and CorePanel never creates a username made only of digits — so a bare number is always an id.
corepanel ssl
Section titled “corepanel ssl”Manages certificates bought from a commercial CA. Every site already gets a free certificate, issued and renewed automatically — this command group is for the case where the certificate has to come from somewhere else: an OV/EV certificate, a corporate CA, or an organisation’s policy.
corepanel ssl list [--json]corepanel ssl show <host> [--json]corepanel ssl upload <host> --cert FILE --key FILE [--chain FILE]corepanel ssl delete <host> [--yes]Three rules explain the whole group:
- An installed certificate is never replaced by a renewal. It lives in its own store, which nothing automatic writes to, and is served ahead of the automatic certificate.
- Expiry degrades, it does not break. A bought certificate does not renew itself, so the panel warns from 30 days out. If it lapses anyway, the site falls back to its automatic certificate and keeps serving valid TLS — under a different issuer, which is why the warning matters.
- Installing requires Pro or Business; listing, inspecting and removing do not. A server whose licence lapsed is never stuck with a certificate it cannot manage.
corepanel ssl list
Section titled “corepanel ssl list”Lists every commercial certificate on the server with its issuer, expiry date and time remaining. The exit status is 1 when at least one certificate is inside its 30-day warning window or already expired, so it can be watched from monitoring without parsing the output. A server with no commercial certificates is not a problem: the list is empty and the status stays 0.
corepanel ssl show
Section titled “corepanel ssl show”Shows one host’s certificate: subject, issuer, the names it covers, its validity window, the chain length and the SHA-256 fingerprint. The private key is never shown.
corepanel ssl upload
Section titled “corepanel ssl upload”Installs a certificate. The material is read from files, not arguments: a private key on a command line ends up in the shell history and is visible to every user on the box through the process list.
--chain is optional — some CAs append the intermediate to the certificate itself, others
mail it separately. The upload is refused when:
| Refusal | Why it matters |
|---|---|
| The chain does not reach a trusted CA | The failure that works in the browser you test with (it cached the issuer elsewhere) and breaks on Android and curl |
| The key does not match the certificate | Would take TLS down on the next handshake |
| The certificate does not cover the host | Name mismatch in every browser |
| Expired, or not yet valid | Cannot secure anything |
| Self-signed | A trust error for every visitor |
Nothing is written unless every check passes, so a rejected upload leaves the site exactly as it was. Installing over an existing certificate is how a renewal is applied.
corepanel ssl delete
Section titled “corepanel ssl delete”Removes a host’s certificate and its private key, returning the host to the automatic
certificate from the next connection on. This is also the way back if an installed
certificate turns out to be wrong. Because the key is deleted, the command asks for
confirmation; a non-interactive run must pass --yes.
# Monitoring: alert before a certificate runs outcorepanel ssl list >/dev/null || echo "a certificate needs attention"
# Install a certificate whose CA sent the bundle separatelycorepanel ssl upload example.com \ --cert /root/example.com.crt \ --key /root/example.com.key \ --chain /root/example.com.ca-bundle
# Apply a renewal: same command, new filescorepanel ssl upload example.com --cert new.crt --key new.key --chain new.ca-bundle
# Scripting: days left on every certificatecorepanel ssl list --json | jq -r '.[] | "\(.host) \(.daysRemaining)"'
# Go back to the automatic certificatecorepanel ssl delete example.com --yesbash corepanel [command] [subcommand] [flags]
| Command | Description ||---------|-------------|| `corepanel system init` | Interactive wizard to initialize a fresh server || `corepanel status` | Show the status of all CorePanel services || `corepanel quota` | Enable disk quotas, inspect state and usage, and set per-account limits || `corepanel account list` | List hosting accounts with their ids, usernames and domains || `corepanel account create` | Create a new hosting account and its primary domain || `corepanel account update` | Change an account's limits, owner details, hosting package or PHP version || `corepanel account suspend` / `unsuspend` | Suspend a hosting account across every plane, and lift it again || `corepanel account transfer` | Move a hosting account to another reseller || `corepanel account panel` | Show or change whether an account may sign in to the client panel || `corepanel account import` | Import a cPanel account (or preview with `--dry-run`) || `corepanel account backup` | Create a full local backup of a hosting account || `corepanel account restore` | Restore a hosting account from a cpbackup archive || `corepanel account backup-schedule` | Manage recurring backup schedules and retention || `corepanel domain` | List, add and remove an account's secondary (alias/addon) domains || `corepanel subdomain` | List, add, tune and remove an account's subdomains || `corepanel app` | Run and publish an account's persistent applications || `corepanel cron` | Manage an account's cron jobs || `corepanel wp` | Install, update, harden and roll back WordPress sites || `corepanel seller` | Create, edit, suspend and delete the resellers this server hosts for || `corepanel admin` | Create, list and delete the people who sign in to the panel || `corepanel mail sync` | Resynchronize the Postfix lookup database || `corepanel mail list` | List the mailboxes of a domain || `corepanel mail delete` | Delete a mailbox and its Maildir || `corepanel mail quota` | Set a mailbox storage quota || `corepanel mail suspend` / `activate` | Suspend or reactivate a mailbox || `corepanel mail forwarder` | Manage mail forwarders (aliases) || `corepanel mail catchall` | Manage a domain catch-all || `corepanel mail redirect` | Redirect all mail for a domain to another domain || `corepanel mail vacation` | Manage a mailbox vacation autoresponder || `corepanel mail filter` | Manage mailbox Sieve filter rules || `corepanel mail spam` | Manage spam thresholds and allow/deny lists || `corepanel mail deliverability` | Check whether mail leaving this server is accepted || `corepanel edition websites` | Show how many websites the edition allows and how many are in use || `corepanel license` | Buy, activate, inspect, refresh or release this server’s license || `corepanel telemetry` | Turn this server’s daily check-in on or off || `corepanel ssl` | Install and manage certificates bought from a commercial CA || `corepanel version` | Print the CLI build version |
Run `corepanel [command] --help` at any time to see the available subcommands and flags.
## `corepanel system init`
Interactive wizard that initializes a fresh CorePanel server. It detects the server identity,sets the panel domain (the FQDN used to access CorePanel), validates DNS, sets the administratoridentity, and generates (or reuses) the administrator password.
```bashcorepanel system initThe wizard walks through six steps: detecting the public IP, prompting for the panel domain, checking DNS resolution against the server IP (advisory, non-blocking), prompting for the admin email, preparing the admin password, and applying the configuration. The panel domain is a DNS name pointing at the server — not the machine’s OS hostname, which is only offered as the default when it happens to be a fully qualified domain name.
The panel domain is optional
Section titled “The panel domain is optional”You do not need a domain to finish the setup. Leave the prompt blank — or run unattended on a
server whose hostname is not an FQDN, which is what cloud images and provisioning systems give you
(almalinux-2gb-nyc1-01) — and CorePanel is served on https://<server-ip> with a self-signed
certificate. The panel then asks for the domain on first login, together with the nameservers.
Set it whenever you are ready, from the panel or by re-running this command with --domain. Once
the name resolves to this server, a trusted certificate is issued automatically.
A domain passed explicitly with --domain is still validated: a value that is not an FQDN is an
error, not a silent fallback to the IP.
Nameservers and company details are not asked here. The panel’s initial configuration wizard collects them on first login and keeps opening until they are set — see Initial Server Setup.
| Flag | Description |
|---|---|
--domain <fqdn> | Panel domain — the FQDN used to access CorePanel (e.g. panel.example.com). Optional; without it the panel is served on the server’s IP |
--admin-name <name> | Administrator display name (default Administrator) |
--admin-email <email> | Administrator email for notifications and Let’s Encrypt |
--admin-password <password> | Administrator password (generated if omitted) |
--no-interactive | Skip all prompts; use flags and defaults for scripted provisioning |
--hostname <fqdn> is retained as a deprecated alias for --domain.
Non-interactive provisioning
Section titled “Non-interactive provisioning”For automated installs, pass the values as flags and disable prompts:
corepanel system init \ --domain panel.example.com \ --admin-email admin@example.com \ --no-interactiveWhen there is no domain yet — a cloud image, a marketplace image, a CI run — omit --domain
entirely and the install still completes:
corepanel system init \ --admin-email admin@example.com \ --no-interactiveWhen --admin-password is omitted, a strong 16-character password is generated and stored at
/root/.corepanel_password (mode 0600). If that file already contains a password, it is
reused instead of generating a new one.
corepanel status
Section titled “corepanel status”Displays the status of all CorePanel services — corepanel-api, corepanel-auth,
corepanel-core, and corepanel-sys — in a table. For each service it shows the systemd
active state, the detailed sub-state (running, exited, failed, …), and the availability of its
sockets.
corepanel status+----------------+--------+---------+---------------------------------+| Service | Active | Status | Sockets |+----------------+--------+---------+---------------------------------+| corepanel-api | active | running | ● 127.0.0.1:16087 || corepanel-auth | active | running | ● corepanel-ftp-auth.sock || | | | ● corepanel-mail-auth.sock || | | | ● ftp-getcert.sock || corepanel-core | active | running | ● corepanel-core.sock || corepanel-sys | active | running | ● corepanel-sys.sock |+----------------+--------+---------+---------------------------------+Socket indicators: ● means the socket (or TCP port) is available, ○ means it is
unavailable. Use this command as a first check when the panel or a subsystem is not responding.
corepanel quota
Section titled “corepanel quota”Manages disk quotas: the server’s quota state, what each account is using, and the per-account limits.
Usage is measured by the kernel’s per-uid quota accounting, so these commands always report the state of that accounting alongside the figures. With accounting off there is nothing to measure, and a usage of zero would read as an empty account rather than as “not measured”. Limits are stored either way and take effect when quotas are enabled.
corepanel quota enable
Section titled “corepanel quota enable”Turns disk quotas on for this server: enables the kernel’s per-account accounting and enforcement, applies every account’s stored limit, and measures what each one is using.
Until this is run, CorePanel stores the quota an account was sold but nothing measures or enforces it. Quota accounting is a property of how the filesystem was mounted, which is why enabling it is a deliberate act and never something a package upgrade does on its own.
What the command can finish depends on the filesystem:
| Filesystem | What happens | Reboot |
|---|---|---|
| ext2/3/4 | the option is added to /etc/fstab and the filesystem is remounted | no |
XFS, separate /home | unmounted and mounted again, when nothing holds it open | only if busy |
XFS on / | configured on the kernel command line with grubby | yes |
The output distinguishes configured from active. On a root XFS — the usual server layout — nothing is measured or limited until the machine reboots, and the command says so instead of reporting success.
corepanel quota enable [--json]Disk quotas are CONFIGURED but NOT yet active — this server must be rebooted. disk quotas are configured on the kernel command line and start at the next reboot Configured in: the kernel command line (grubby) grubby --update-kernel=ALL --args=rootflags=uquota
Usage figures stay unavailable and no limit is enforced until this server reboots.corepanel quota status
Section titled “corepanel quota status”Shows the quota state of the filesystem backing the account home directories.
The three layers are reported separately because they fail separately:
- Accounting — is the kernel counting bytes per account at all.
- Enforcement — is it refusing writes past a limit.
- Reboot required — quotas are configured but the filesystem has not been mounted with them yet. On a root filesystem this is the normal state between enabling quotas and restarting the server.
corepanel quota status [--json]Disk quotas: ACTIVE (accounting + enforcement) Filesystem : /dev/sda4 on / (xfs) Separate /home : no Accounting : on Enforcement : on App data counted: yesApp data counted reports whether /var/opt/userapps shares the quota filesystem. When
/home is a separate partition it does not, and application data is therefore excluded
from every usage figure — the tidier-looking layout is the one that measures less.
corepanel quota list
Section titled “corepanel quota list”Lists per-account disk usage, largest first.
Disk and database sizes are shown in separate columns and are deliberately not added
together: only the disk figure is accounted by the kernel and enforced against, because the
MySQL data directory belongs to the mysql user and no per-uid quota can see it.
corepanel quota list [--search <domain>] [--json]When quotas are inactive the command prints a warning before the table: the figures are then whatever was last measured, not current usage.
corepanel quota inspect
Section titled “corepanel quota inspect”Shows where an account’s disk usage is, not just how much it is.
corepanel quota inspect <account> [--deep] [--json]A disk quota counts every file the account’s Linux user owns on the filesystem — not the contents of its home directory. Usually those are the same thing. When they are not, the account sees a figure it cannot explain and cannot reduce, because the files are somewhere it cannot even list.
Some of that is by design, and the report says so:
| Location | What is there |
|---|---|
/opt/userapps/<user> | A published application’s program and releases. Root owns the code — deliberately out of reach of the account’s own FTP, so it cannot replace the binary systemd is executing — so this normally contributes nothing to the quota; it is checked because anything account-owned that lands here would |
/var/opt/userapps/<user> | That application’s data, which the account owns and survives every deploy |
/var/spool/cron/<user> | The account’s crontab |
/var/spool/mail/<user> | The system mail spool |
/tmp, /var/tmp | Not by design: files left there still count against the quota |
The full picture of what a quota counts is in Quotas and Usage.
The rest is the reason this command exists: files that ended up owned by an account by accident. The account cannot delete them, so a quota applied on top of them puts it permanently over its limit with nothing it can do.
Anything the scan cannot place is reported as unexplained. --deep sweeps the entire
filesystem to locate it and groups the results by directory. That takes minutes on a busy
server, which is why it is not the default.
Disk usage of example.com (user example, uid 1007)
Counted by the kernel : 1.2 GiB Inside the home : 840.0 MiB (/home/example) Outside the home : 384.0 MiB
+----------------------------+-----------+-------+-----------+---------------------------+| LOCATION | SIZE | ENTRIES | BY DESIGN | WHAT IT IS |+----------------------------+-----------+-------+-----------+---------------------------+| /home/example | 840.0 MiB | 9214 | yes | home directory || /opt/userapps/example | 320.0 MiB | 412 | yes | published application ... || /var/opt/userapps/example | 64.0 MiB | 88 | yes | application data ... |+----------------------------+-----------+-------+-----------+---------------------------+corepanel quota set
Section titled “corepanel quota set”Sets an account’s disk quota in MB. A quota of 0 removes the limit.
corepanel quota set <account> <MB># 20 GB for account 12, and no limit at all for dom-7corepanel quota set 12 20480corepanel quota set dom-7 0The kernel gets a hard limit slightly above the quota — 5 %, at least 50 MB, never more than double it. That margin is the grace window, and it is not generosity: at exactly 100 % the failures are indirect and ugly. PHP cannot write its session files, Dovecot cannot deliver and mail bounces, cron jobs fail half-way. The account goes over its stated quota, and is warned, before writes are refused.
If the account is already above the new quota, the command says so — its grace period starts immediately. On a server where quotas are not active the limit is still stored, and applies as soon as they are enabled.
corepanel quota apply
Section titled “corepanel quota apply”Pushes every account’s stored quota to the kernel and reports which accounts are already over theirs.
corepanel quota apply [--dry-run]This is what a server needs when its accounts predate disk quotas: the limits are in CorePanel’s database, but the kernel has never been told about them. It is also the repair path for a limit whose push failed when the account was created.
Run --dry-run first. Before enforcement is switched on, the number that matters is how many
accounts are already over their limit — those are exactly the ones whose writes will start
failing.
corepanel quota refresh
Section titled “corepanel quota refresh”Forces an immediate refresh of the stored usage instead of waiting for the hourly job.
corepanel quota refreshWhen quotas are inactive nothing is measured and the stored figures are left untouched. They are never zeroed — “cannot measure” must not be recorded as “uses nothing”.
corepanel account list
Section titled “corepanel account list”Lists hosting accounts with their id, username and primary domain. Any of the three
works as the <account> argument of every other command (see
Naming an account), so this is a way to browse the server rather than
a step you have to run before doing anything else.
corepanel account list| Flag | Description |
|---|---|
--search <term> | Filter accounts (case-insensitive substring over the names an account is reachable by: username, primary domain, addon domains and subdomains) |
--json | Output the account list as JSON for scripting |
The default table lists ID, Domain, User, Seller, Email (used/limit), DBs (used/limit),
Quota MB (used/limit), and Package; a limit of ∞ means unlimited. The command pages
through core transparently, so every matching account is returned in one call.
Examples
Section titled “Examples”# List every hosting accountcorepanel account list
# Find an account when you only remember part of the namecorepanel account list --search example
# Scripting: extract the account id for a domaincorepanel account list --search example.com --json | jq '.[0].accountId'corepanel account create
Section titled “corepanel account create”Creates a new hosting account together with its primary domain. The domain is passed as a positional argument; core provisions the Linux user, web vhost, DNS, and mail records.
corepanel account create example.comPassword handling
Section titled “Password handling”The account password can be supplied in three ways, listed from most to least secure:
--password-stdin— read the password from standard input. Best for automation, since the secret never appears in the shell history or the process list.- Interactive prompt — when no password flag is given and the session is attached to a terminal, the CLI prompts for the password with hidden input.
--password <value>— pass it directly. Convenient but visible in the shell history and inps.
The password must meet core’s strength requirements: at least 12 characters including lower-case, upper-case, a digit and a special character.
| Flag | Description |
|---|---|
--password <value> | Account password (prefer --password-stdin or the prompt) |
--password-stdin | Read the account password from standard input |
--username <name> | Linux username for the account. Defaults to a name derived from the domain. Lowercase letters and digits only, not starting with a digit, at most 16 characters; a name already taken by an account, an administrator or a system user is refused |
--owner-name <name> | Owner full name |
--owner-email <email> | Owner email address |
--description <text> | Free-form account description |
--quota <mb> | Disk quota in MB (0 = unlimited) |
--max-emails <n> | Maximum number of mailboxes (0 = unlimited) |
--max-databases <n> | Maximum number of MySQL databases (0 = unlimited) |
--max-ftp <n> | Maximum number of FTP accounts (0 = unlimited) |
--max-apps <n> | Applications the account may run. 0 = none, not unlimited, and there is no unlimited value — an account created without this flag cannot run applications |
--plan <name> | Hosting plan name to associate with the account |
--php <version> | PHP version to provision (e.g. 8.3). Defaults to the highest version installed on the server. |
# Interactive password prompt, default (highest) PHP versioncorepanel account create example.com
# Scripted: read the password from stdin, pin PHP 8.3 and a 5 GB quotaprintf '%s' "$ACCOUNT_PASSWORD" | \ corepanel account create example.com --password-stdin --php 8.3 --quota 5120On success the command prints the account ID, Linux username, domain, and home directory.
corepanel account update
Section titled “corepanel account update”Changes an existing account’s limits, owner details, hosting package or PHP version
(core.UpdateAccount). The account is passed as a positional argument — its username,
any domain it owns, or the numeric id
from corepanel account list; the dom-<id> form is accepted too.
corepanel account update <account> [flags]Only the flags you pass are changed. Every other limit is left exactly as it is. Since
0 means unlimited, omitting a flag is not the same as passing it with 0: a limit you
do not mention is untouched, while --max-emails 0 grants unlimited mailboxes.
| Flag | Description |
|---|---|
--owner-name <name> | Owner full name |
--owner-email <email> | Owner email address |
--description <text> | Free-form account description |
--quota <mb> | Disk quota in MB (0 = unlimited) |
--max-bandwidth <mb> | Monthly bandwidth allowance in MB (0 = unlimited) |
--max-emails <n> | Maximum number of mailboxes (0 = unlimited) |
--max-databases <n> | Maximum number of MySQL databases (0 = unlimited) |
--max-ftp <n> | Maximum number of FTP accounts (0 = unlimited) |
--max-aliases <n> | Maximum number of domain aliases (0 = unlimited) |
--max-subdomains <n> | Maximum number of subdomains (0 = unlimited) |
--max-addon <n> | Maximum number of addon domains (0 = unlimited) |
--max-apps <n> | Applications the account may run. 0 = none, not unlimited — the one limit that reads the other way round. Lowering it below the applications already running is refused |
--package <id> | Apply a hosting package; its limits become the account’s. Any limit flag given alongside it still wins. 0 detaches the account without changing a limit. |
--php <version> | Move the account to another installed PHP version (e.g. 8.3). Not a limit — see below |
--force | Allow a disk quota below current usage, leaving the account over quota |
--keep-mail | Keep receiving mail while the account is suspended — on by default; --keep-mail=false bounces instead (sending is cut either way) |
# Upgrade: more mailboxes and more diskcorepanel account update 7 --max-emails 100 --quota 20480
# Move the account onto a package, then override one of its limitscorepanel account update 7 --package 3 --max-subdomains 50
# Plan downgrade below what the account currently storescorepanel account update 7 --quota 1024 --force
# Move a customer off an end-of-life PHP releasecorepanel account update 7 --php 8.4An account created from a package stays linked to it after a manual edit and is reported as customized — see Editing an Account.
corepanel account suspend / corepanel account unsuspend
Section titled “corepanel account suspend / corepanel account unsuspend”Suspends a hosting account across every plane it touches, and lifts it again
(core.SuspendAccount / core.UnsuspendAccount).
corepanel account suspend <account> [--reason <text>]corepanel account unsuspend <account><account> is the username, any domain the account owns, or the numeric id. A name that
matches more than one account is refused rather than guessed.
| Plane | While suspended |
|---|---|
| Websites | Every domain of the account serves a 403 suspension page |
| Applications | Stopped; ones already stopped by their owner stay stopped |
| Cron | Jobs stop running, definitions untouched |
| FTP | Login refused |
| Mail — sending | Rejected at MAIL FROM with 550 |
| Mail — receiving | Rejected with 550 unless the account keeps its mail |
| Mail — reading | Works normally, webmail included |
| Data | Untouched — nothing is deleted |
Flags:
| Flag | Meaning |
|---|---|
--reason <text> | Recorded for the operator and shown in the panel. Never published: the suspension page says nothing about why |
suspend requires CorePanel Business. unsuspend is not gated on any edition, so
a licence that lapses while an account is suspended can never leave a customer’s sites
dark with no way back.
# Suspend by domain, with a note for your own recordcorepanel account suspend example.com --reason "invoice 4471 unpaid"
# Lift it again by account idcorepanel account unsuspend 7corepanel account list shows a Status column, and --json carries suspended,
suspendedAt and suspendReason. See
Suspending an Account for why a suspended customer can still
read their mail.
corepanel account transfer
Section titled “corepanel account transfer”Moves a hosting account to another reseller (core.TransferAccount).
corepanel account transfer <account> --to <reseller><account> is the username, any domain the account owns, or the numeric id; <reseller>
is a seller id or name. Ownership is the only thing that changes — nothing on the server
knows which reseller owns an account, so no service is restarted and no site goes down.
The account keeps its own limits, its own password and its own suspension state.
| Flag | Meaning |
|---|---|
--to <reseller> | The reseller that will own the account, by id or name (required) |
Two things move with it, and the command reports both:
- the account’s hosting package is cleared, because a package belongs to a reseller and
the account is leaving that catalog. Limits are untouched — put the account on one of the
new owner’s packages with
corepanel account update <account> --package <id>; - past notifications stay with the reseller that owned the account when they happened.
Refused when the destination reseller is suspended, and when the account is currently down
because its own reseller is suspended — lift that account first with
corepanel account unsuspend, which is you deciding it comes back online. An account
suspended in its own right moves normally and stays suspended.
# Hand a customer to another resellercorepanel account transfer alice --to "Acme Hosting"
# Take a reseller's customers over before removing the organisationcorepanel account transfer example.com --to 1corepanel seller delete "Acme Hosting"See Editing an Account for the whole picture.
corepanel account panel
Section titled “corepanel account panel”Shows or changes whether a hosting account may sign in to the
client panel
(core.GetAccountPanelAccess / core.SetAccountPanelAccess).
corepanel account panel status <account>corepanel account panel enable <account>corepanel account panel disable <account><account> is the username, any domain the account owns, or the numeric id.
There is no panel user to create and no panel password to set. The account signs in with the username and password it already has — the same ones that open FTP and SSH. These commands only decide whether that credential also opens the panel. To change the password itself, use the account’s password controls; there is one place to do it, not two.
status prints four separate things, because “my customer cannot log in” is usually not
the flag:
| Line | What it means |
|---|---|
panel access | This account’s flag — what enable and disable set |
licence | Whether the client panel exists on this server at all (CorePanel Business) |
suspended | A suspended account is refused, with its own message |
password | An imported or weak credential must be changed before anything else |
can sign in | The four above, resolved in the order the server enforces them |
$ corepanel account panel status example.com account: acmecorp (example.com) (id 7) panel access: enabled licence: the client panel is available on this server suspended: no password: must be changed at next sign-in (imported from another panel, never checked against this server's policy) last sign-in: never can sign in: yes, but only to change its passworddisable revokes self-service, not hosting. Websites, mail, FTP and SSH are untouched,
and the same password keeps working everywhere it worked before. Sessions that are already
open are refused on their next request: the server re-reads this flag on every call rather
than trusting the session token.
Both commands are idempotent, and neither is gated by the licence. Revoking access has
to keep working on a server whose licence has lapsed, and gating enable would leave you
able to turn access off and unable to turn it back on. The licence is enforced at the login
itself.
corepanel account import
Section titled “corepanel account import”Imports a cPanel account into CorePanel from a cpmove-<user>.tar.gz / backup archive —
or from an already-extracted account directory. With --dry-run the archive is parsed
server-side and the plan is printed (core.PreviewImport, read-only); without it, the
import runs (core.ImportAccount).
# Preview (read-only)corepanel account import <archive> --dry-run
# Run the importcorepanel account import <archive> [--package <id>] [--preserve-uid]| Flag | Description |
|---|---|
--dry-run | Parse and validate the archive, print the plan, and touch nothing |
--seller <id> | Target seller id (default: primary seller) |
--package <id> | Target hosting package id; its limits drive the account |
--preserve-uid | Preserve the source UID/GID via useradd -u/-g |
--on-conflict abort|skip | Policy when the linux user or primary domain already exists (default abort) |
--report <path> | Write the JSON import report to this path |
The <archive> path is resolved on the machine running corepanel-core (the archive must
already be staged on the server) and is sent as an absolute path.
Dry-run reports:
- the detected source panel and version;
- the account identity (Linux user, UID/GID, plan, contact) — and whether the source has it suspended, which is marked beside the name because such an account is imported suspended;
- resource counts — domains, mailboxes, forwarders, databases, DB users, DNS records, FTP accounts, cron jobs, certificates;
- credential compatibility — which passwords carry across verbatim versus which need a
reset (cPanel
$6$/$5$/$1$, bcrypt and$y$yescrypt hashes are preserved); - conflicts against existing CorePanel accounts and domains;
- every parse warning (anything skipped or unsupported — nothing is dropped silently).
A mailbox or FTP login the source had locked arrives closed (inactive), keeping the password stored behind the lock marker. It is not counted among the credentials needing a reset: there is nothing to reset, and reopening it is a decision rather than a chore.
An account suspended on the source arrives suspended, keeping the source’s own suspension date: its sites serve the suspension page and its logins stay closed until an administrator lifts it. A site that does not answer after importing one of these is the correct outcome, not a failed import.
Real import creates the system account preserving the source username, UID/GID
(with --preserve-uid) and password hash where the scheme is compatible, then provisions
the CorePanel account, the primary domain (vhost + managed DNS zone), the FTP accounts and
the primary domain’s mailboxes with their credentials carried over verbatim. A credential
whose scheme cannot be preserved gets a random password and is flagged for a reset (never
surfaced in plaintext). It also restores the account’s heavy data: the home directory
(site files and Maildir messages), the MySQL databases (schema + data) and their users
(source password hashes preserved), the crontab (imported as panel-managed cron jobs), and
the primary domain’s TLS certificate (installed verbatim when not self-signed).
Addon/subdomain provisioning, source DNS-record import and mail forwarders remain later
phases and are listed as deferred in the report. The job outcome is persisted and can be
polled via core.GetImportJob or listed via core.ListImportJobs.
Examples:
# Preview a staged cpmove archive without importingcorepanel account import /var/lib/corepanel/import/cpmove-example.tar.gz --dry-run
# Preview an already-extracted account directorycorepanel account import /var/lib/corepanel/import/job-42/cpmove-example --dry-run
# Run the import into package 3, preserving the original UID/GIDcorepanel account import /var/lib/corepanel/import/cpmove-example.tar.gz \ --package 3 --preserve-uid --report /root/import-example.jsoncorepanel account backup
Section titled “corepanel account backup”Creates a full local backup of a hosting account. Every MySQL database is dumped with
mysqldump --single-transaction, each database user’s credential is preserved, and the
home directory (files + Maildir) is archived into a self-describing
cpbackup-<user>-<timestamp>.tar archive under /var/lib/corepanel/backups/<user>/.
Local backups and their schedules are available in every edition; copying an archive to a remote destination (S3-compatible storage or SFTP) is a Pro/Business feature.
corepanel account backup <user> [--wait] [--dest <name|id>]The backup runs asynchronously server-side and prints a job id. Pass --wait to block
until it finishes and print the resulting archive path and status.
# Start a backup and return immediately with the job idcorepanel account backup pxdemo
# Start a backup and block until it completescorepanel account backup pxdemo --wait
# Back up and push the archive off this servercorepanel account backup pxdemo --dest offsite --wait
# Show the status and report of a backup jobcorepanel account backup-status 7The local copy is kept even when a destination is configured, so a destination that is
unreachable degrades the job to partial with the reason in its report — it never costs
you the backup.
corepanel account restore
Section titled “corepanel account restore”Restores a hosting account from a cpbackup-<user>-<timestamp>.tar archive produced by
corepanel account backup. Recreates the system account (preserving UID/GID and the
password hash where compatible), the CorePanel account row and primary domain, every
secondary domain (aliases and addons) and subdomain — each with its vhost and managed
DNS zone — the home directory (files + Maildir), MySQL databases + users with preserved
credentials, FTP accounts, and every mailbox login (primary and addon domains) with its
password hash carried over verbatim.
corepanel account restore <archive> [--on-conflict abort|skip] [--dry-run] [--wait]corepanel account restore --dest <name|id> --remote-key <key> [--on-conflict abort|skip] [--dry-run] [--wait]Use --dry-run to preview what would be restored without touching the system, and
--wait to block until the restore finishes and print the per-resource report.
With --dest and --remote-key the archive is pulled from a
remote destination instead of read from local disk: it is
downloaded into a private staging directory, restored, and the download removed — the copy
at the destination is left alone. This path is never gated by edition, because pulling a
backup back is recovery.
# Preview a restorecorepanel account restore /var/lib/corepanel/backups/cph2/cpbackup-cph2-20260721T090349Z.tar --dry-run --wait
# Restore, blocking until donecorepanel account restore /var/lib/corepanel/backups/cph2/cpbackup-cph2-20260721T090349Z.tar --waitcorepanel account backup-schedule
Section titled “corepanel account backup-schedule”Manages recurring backup schedules and their retention. A schedule targets one account
(--user) or every account (--all), fires on a cron expression or a
daily/weekly/monthly preset, and prunes only its own old archives. CorePanel runs an
in-process scheduler that polls every minute, so no OS crontab entry is created. Retention
never deletes a manual backup and always keeps at least the most recent scheduled archive
per account. run starts the run in the background and returns immediately; track progress
with the schedule’s last status or corepanel account backup-status.
corepanel account backup-schedule listcorepanel account backup-schedule create (--user <user> | --all) [--cron "m h dom mon dow" | --frequency daily|weekly|monthly] [--keep N] [--days N] [--name <label>] [--disabled]corepanel account backup-schedule enable <id>corepanel account backup-schedule disable <id>corepanel account backup-schedule run <id>corepanel account backup-schedule delete <id>create flags:
--user <linux-user>— Back up a single account (mutually exclusive with--all).--all— Back up every account on the server.--cron "m h dom mon dow"— Explicit 5-field cron expression (wins over--frequency).--frequency <daily|weekly|monthly>— Preset (daily 02:00, weekly Sun 02:00, monthly 1st 02:00).--keep N— Keep the N most recent scheduled archives per account (0= unlimited).--days N— Delete scheduled archives older than N days (0= disabled).--dest <name|id>— Push this schedule’s archives to a remote destination (Pro/Business). Retention then prunes the remote copies as well as the local ones.--name <label>— Optional human label.--disabled— Create the schedule disabled. Before 1.6.14 this had no effect and the schedule ran at its first due time.
# Nightly backup of one account, keep the last 7 archivescorepanel account backup-schedule create --user pxdemo --frequency daily --keep 7 --name "pxdemo nightly"
# Weekly server-wide backup with a custom cron, delete archives older than 30 dayscorepanel account backup-schedule create --all --cron "0 3 * * 0" --days 30
# List schedules, run one now, then disable itcorepanel account backup-schedule listcorepanel account backup-schedule run 3corepanel account backup-schedule disable 3corepanel backup-destination
Section titled “corepanel backup-destination”Manages where finished backup archives are copied. Without a destination every archive
stays on the same disk as the server it protects — the one failure a backup exists to
survive. Two drivers are available: s3 for any S3-compatible service (AWS, MinIO,
Cloudflare R2, Backblaze B2, Wasabi, and Google Cloud Storage through its S3 endpoint) and
sftp for any SSH server.
Configuring and using a destination requires CorePanel Pro or Business. Listing, disabling and deleting one — and restoring an archive back from one — work on every edition, so a lapsed licence never leaves you with configuration you cannot manage or a backup you cannot recover.
corepanel backup-destination listcorepanel backup-destination add <name> --type s3|sftp [driver flags]corepanel backup-destination test <name|id>corepanel backup-destination enable <name|id>corepanel backup-destination disable <name|id>corepanel backup-destination rm <name|id>corepanel backup-destination archives <name|id>S3 flags
Section titled “S3 flags”| Flag | Description |
|---|---|
--endpoint <host> | Service host without a scheme (s3.eu-west-1.amazonaws.com) |
--region <region> | Region |
--bucket <bucket> | Bucket name |
--prefix <folder> | Folder inside the bucket, so several servers can share it |
--path-style | Address the bucket as <endpoint>/<bucket> — MinIO and most self-hosted gateways need this; AWS does not |
--insecure | Plain HTTP; only sensible on a private network |
--access-key, --secret-key | Credentials |
SFTP flags
Section titled “SFTP flags”| Flag | Description |
|---|---|
--host <host>, --port <n> | Server address (port defaults to 22) |
--user <name> | Username |
--path </abs/dir> | Absolute remote directory archives are written into; it must already exist |
--key-file <path> | Private key, read from disk locally so it never reaches a shell history |
--passphrase <text> | Passphrase for an encrypted private key |
--password <password> | Password authentication |
--host-key <line> | Pin the server key in authorized_keys form. Left empty, the key seen on the first successful connection is adopted and every later connection is verified against it |
# S3-compatible destination, then prove it really accepts uploadscorepanel backup-destination add offsite --type s3 \ --endpoint s3.eu-west-1.amazonaws.com --region eu-west-1 --bucket cp-backups \ --access-key AKIA... --secret-key ...corepanel backup-destination test offsite
# A MinIO on the LANcorepanel backup-destination add minio --type s3 \ --endpoint 10.0.0.5:9000 --bucket backups --path-style --insecure \ --access-key minioadmin --secret-key ...
# An SFTP box, authenticating with a keycorepanel backup-destination add nas --type sftp \ --host nas.example.net --user backups --path /srv/backups \ --key-file ~/.ssh/id_ed25519test is worth running every time you add or change a destination: it uploads a small
probe object, lists it and removes it again. A plain connection or login check would
report success for a destination that still fails on the first real push.
Moving an account to another server
Section titled “Moving an account to another server”A destination is also the shortest path to rebuilding an account somewhere else. On the new server, add the same destination, find the archive and restore it:
corepanel backup-destination add offsite --type s3 ...corepanel backup-destination archives offsitecorepanel account restore --dest offsite --remote-key cpbackup-pxdemo-20260802T031500Z-j42.tar --waitThe archive is self-contained, and the DNS zone is regenerated by the new server, so the records point at it rather than at the old one.
corepanel seller
Section titled “corepanel seller”Manages the resellers this server hosts for. Aliases: sellers, reseller, resellers.
A reseller owns accounts, and which accounts it owns is what its administrators may see and touch: an administrator whose seller is a reseller reaches that reseller’s accounts and nothing else on the server.
The organisation that owns the server is a seller too, flagged as the primary vendor in the listing. It is not a reseller — its administrators see everything and its limits are never consulted — so it can be neither edited nor deleted here.
Every subcommand takes a reseller by id or by name, matched without regard to case.
corepanel seller listcorepanel seller show "Acme Hosting"The Accounts and Disk columns read used / ceiling, so a glance answers the question you
actually have — whether the next account will go through. A ceiling of 0 prints as
unlimited.
For a reseller that came across from a cPanel migration, corepanel seller show also prints
the privilege set the source panel granted them, in cPanel’s own names:
Imported from cPanel, where this reseller was granted 7 privileges and no others: acct-summary, basic-system-info, basic-whm-functions, cpanel-api, list-pkgs, manage-dns-records, ssl-infoCorePanel does not grant privileges one at a time: this reseller's administratorscan do anything within its own accounts, and nothing outside them.That set is a record of where the reseller came from and is never enforced here — see Your resellers come across with their limits. It is printed only where the source’s grant was a restriction: a reseller cPanel granted everything had nothing withheld, so there is nothing to say about it.
corepanel seller create <name>
Section titled “corepanel seller create <name>”Creates a reseller. The name is what the panel shows and has to be free; it is compared without regard to case, because that is also how an import resolves a source panel’s reseller to a seller here.
Requires CorePanel Business. The entitlement gates creation — a new reseller, and an administrator under one. A reseller that already exists keeps working on any edition: you can still rename it, change its ceilings, lift a suspension and read everything about it, because a licence lapsing must never leave you unable to undo something.
| Flag | Description |
|---|---|
--email <address> | Address the reseller is notified at (required) |
--max-accounts <n> | How many accounts the reseller may create in total (0 = unlimited) |
--max-quota <mb> | Total disk the reseller may sell, in MB (0 = unlimited) |
--overselling | Let the reseller exceed its total disk (the account count stays enforced) |
corepanel seller create "Acme Hosting" --email ops@acme.test \ --max-accounts 20 --max-quota 102400The two ceilings are totals across every account the reseller owns, and they are enforced — see what the ceilings do below.
corepanel seller update <seller>
Section titled “corepanel seller update <seller>”Changes a reseller’s name, contact address or limits. Aliases: limits, set.
Only the flags you pass are written, and everything else is left exactly as it is — so
tightening one ceiling cannot revert a rename that happened in between. Flags: --name,
--email, --max-accounts, --max-quota, --overselling.
# Tighten one ceiling and touch nothing elsecorepanel seller limits "Acme Hosting" --max-accounts 10Lowering a ceiling below what the reseller already holds is allowed and takes nothing away: the accounts that exist keep running, and it is the next one that is refused.
The primary vendor is refused.
What the two ceilings do
Section titled “What the two ceilings do”--max-accounts and --max-quota are totals across every account the reseller owns, and
0 means unlimited. They are refusals, not decorations:
- Creating an account for the reseller — from the panel, the CLI, WHMCS or an import.
- Transferring an account into it, which raises its totals by exactly as much.
- Raising a customer’s disk quota, or applying a hosting package that raises it.
The refusal names the wall, what it is and what has been used of it, so the reseller knows whether to delete an account or to call you.
Disk counts what was assigned, not what is used. Ten accounts of 10 GB are 100 GB whether or not anything has been uploaded — that is what selling 100 GB means — and a server whose filesystem has no active quotas measures nothing at all, which is exactly where a limit computed from measured bytes would quietly become no limit.
A reseller with a disk ceiling cannot be given an account with no quota. An unlimited account adds nothing to the total, so one of them would exempt the reseller from its own ceiling for good. Where there is no ceiling, or where the reseller may oversell, unlimited means what it always meant. Accounts that already have no quota — every reseller arrives from a cPanel import with some — are counted as accounts and shown separately under the disk figure, so a total that looks too small explains itself.
--overselling lifts the disk ceiling only. The account count is not an overselling
switch and stays enforced, which is the same split cPanel makes: there, overselling is a
property of diskspace and bandwidth and never of the account limit.
A ceiling belongs to the reseller, not to whoever is calling. Creating an account for a reseller that is full is refused for you as well — that is what stops a billing system provisioning past a limit through your own credential. Raise the ceiling first.
corepanel seller suspend <seller>
Section titled “corepanel seller suspend <seller>”Stops a reseller trading. Requires CorePanel Business.
Two things happen, and the second is the one to be sure about before you press it:
- The reseller stops trading. Its administrators are refused at the login form, and on every request of a session that is already open — a session minted an hour ago does not outlive the decision by the rest of its life.
- Every account it owns is suspended. The same suspension a single account gets: sites serve a suspension page, applications stop, cron jobs stop running, FTP is refused and inbound mail is rejected. Nothing is deleted.
An account that was already suspended in its own right — one of the reseller’s customers who stopped paying weeks ago — is left exactly as it is, keeps its own reason, and is not put back online when you lift the reseller’s suspension.
| Flag | Description |
|---|---|
--reason <text> | Why, in your words. Recorded on the reseller |
--yes | Do not ask for confirmation |
--reason is for you. It is shown on corepanel seller show and never reaches the
reseller’s customers: their accounts carry a fixed sentence saying only that the reseller
who owns them is suspended. A note about somebody’s unpaid invoice is not something to mail
to forty unrelated people.
While it is suspended the reseller is also given no new accounts — not from the CLI, not from an import, and not from WHMCS, none of which go through one of its sessions.
The command asks for confirmation, and refuses to run at all without a terminal unless you
pass --yes. The organisation that owns this server cannot be suspended.
corepanel seller suspend "Acme Hosting" --reason "unpaid January invoice"The output names every account, one group at a time — suspended, already down, and any that could not be taken down. Partial success is a real outcome: the reseller stops trading even when one of its accounts would not go offline, and you have to be told which one.
corepanel seller unsuspend <seller>
Section titled “corepanel seller unsuspend <seller>”Lets a reseller trade again. Alias: resume.
Its administrators can sign in, and the accounts this suspension took down are restored. Only those.
No confirmation, and no edition gate — a licence that lapsed while a reseller was suspended must never be the reason its customers cannot come back.
corepanel seller unsuspend "Acme Hosting"corepanel seller delete <seller>
Section titled “corepanel seller delete <seller>”Deletes a reseller. Alias: rm.
Refused while it still owns accounts, administrators, packages or live API tokens, and the
refusal names what is in the way. There is no cascade: cancelling a reseller contract is not
a reason to delete customer hosting accounts — move them first with
corepanel account transfer, one at a time, and then delete
the empty organisation.
corepanel admin
Section titled “corepanel admin”Manages the people who can sign in to the panel. Aliases: admins, administrator,
administrators.
An administrator is not a reseller. An administrator is a person with a login; a
reseller is an organisation that owns hosting accounts, and it is managed with
corepanel seller above. Every administrator belongs to exactly one
seller, and that is what decides what they see:
- an administrator of the organisation that owns the server sees everything;
- an administrator of a reseller sees only that reseller’s accounts.
A super administrator additionally may do the server-wide things — licensing, the firewall, PHP versions, the logs. Only a super administrator may create another administrator, and a super administrator may only ever belong to the organisation that owns the server.
corepanel admin list
Section titled “corepanel admin list”Lists administrator accounts and prints their key fields — ID, username, email, seller, and
super-admin flag — in a table. Alias: ls.
| Flag | Description |
|---|---|
--seller <id> | Filter by a specific seller (default: 1) |
--all | Include administrators from every seller (overrides --seller) |
corepanel admin listcorepanel admin list --allcorepanel admin list --seller 42corepanel list admins is the older spelling of this command and keeps working.
corepanel admin create <username>
Section titled “corepanel admin create <username>”Creates an administrator.
The username is the login. It must be free among administrators and among hosting accounts: one login form answers both, so a name they share would be two different people told apart only by their passwords happening to differ.
| Flag | Description |
|---|---|
--email <address> | Contact address, unique across administrators (required) |
--name <name> | Display name shown in the panel (defaults to the username) |
--password <password> | Initial password (generated and printed when omitted) |
--seller <id|name> | Reseller this administrator belongs to |
--super | Grant server-wide administration |
# A second operator for the server's ownercorepanel admin create jane --email jane@example.com --name "Jane Roe"
# An administrator for a reseller, with a password you chosecorepanel admin create acmeops --email ops@acme.test \ --seller "Acme Hosting" --password 'S0me-Strong!Pass'Without --password a strong one is generated and printed once. Either way the
administrator is asked to replace it the first time they sign in: whoever created the
account knows the password, and a credential its holder has not chosen is still the
issuer’s. This password is never written to /root/.corepanel_password — that file has
exactly one subject, the server owner’s own administrator.
Creating an administrator under a reseller requires CorePanel Business. A second administrator for the server’s own organisation is not a reseller feature and works on every edition.
Giving an imported reseller its first login prints a note
Section titled “Giving an imported reseller its first login prints a note”It is the one thing a cPanel migration makes wider. On cPanel a reseller holds a named set of privileges — they may create accounts but not terminate them, or touch DNS but not packages. CorePanel has no such setting: a reseller is all-or-nothing within their own accounts. So a reseller who could not terminate an account on the source can terminate one here.
Nothing happens at the migration itself, because the reseller that arrives has no login and can do nothing. It happens the day you run this command:
Note: acmehost was imported from cPanel, where it was granted 7 privileges and no others:acct-summary, basic-system-info, basic-whm-functions, cpanel-api, list-pkgs,manage-dns-records, ssl-info. CorePanel does not grant privileges one at a time — thisadministrator can do anything within acmehost's own accounts, including whatever the sourcewithheld, and nothing outside them. Run `corepanel seller show acmehost` to see the full setagain.The administrator was created: this is a note, not a failure, because widening those privileges may well be what you intend. If it is not, delete the administrator — the reseller goes back to having no login, and its accounts keep working the whole time. The note appears only for a reseller imported from a cPanel that restricted them.
corepanel admin delete <admin>
Section titled “corepanel admin delete <admin>”Deletes an administrator, by id or by username. Alias: rm.
It removes the login and nothing else: hosting accounts belong to the seller, not to the person who created them, so nothing a deleted administrator touched is affected. A live session of theirs stops working on its next request.
Refused for the last super administrator — a server nobody can administer is not a state to leave a machine in — and for the administrator behind your own session.
corepanel admin delete acmeopscorepanel mail sync
Section titled “corepanel mail sync”Triggers a complete resynchronization of all mail domains, mailboxes, and aliases from
corepanel-core into corepanel-auth’s Postfix lookup database. Use it to recover from data
inconsistencies, initialize the Postfix database after installation, or force a refresh after
bulk imports or manual database changes.
corepanel mail syncAdd -v / --verbose to see a breakdown of how many domains, mailboxes, and aliases were
synchronized:
corepanel mail sync --verboseSynchronizing Postfix lookup database...Synced: - Domains: 12 - Mailboxes: 148 - Aliases: 37The rewrite is a single transaction, and it is all-or-nothing on both sides: if any part of the server’s mail data cannot be read, nothing is sent and the lookup database keeps the contents it already had. Postfix reads that database live, so a partial rewrite would mean refusing mail for whatever was left out — worse than the inconsistency the command was run to fix. A failure prints the reason and changes nothing.
corepanel mail (mailbox management)
Section titled “corepanel mail (mailbox management)”Manage mailboxes for a domain. Each command persists the change in corepanel-core and
synchronizes it to the Postfix/Dovecot lookup database.
# List the mailboxes of a domain (address, status, quota)corepanel mail list example.com
# Delete a mailbox: removes the Maildir and revokes the credentialscorepanel mail delete intern@example.com
# Set a mailbox storage quota in MB (0 = unlimited)corepanel mail quota sales@example.com 1024
# Suspend a mailbox (blocks authentication and delivery), then reactivate itcorepanel mail suspend sales@example.comcorepanel mail activate sales@example.comsuspend closes both halves of a mailbox — delivery and sign-in — and activate opens
both. A mailbox listed as sign-in blocked is the third state: it still receives mail
and only its login is shut, which is how an imported mailbox arrives when the source panel
had locked its password. activate is what reopens it.
A quota is applied by Dovecot at delivery: a message for a full mailbox is refused with
552 5.2.2 Quota exceeded and Postfix bounces it back to the sender. The new limit reaches
Dovecot at the mailbox’s next authentication, so nothing needs restarting. Remember that the
same messages count against the owning account’s disk quota, which stops delivery for every
mailbox at once — see Quotas and Usage.
corepanel mail forwarder
Section titled “corepanel mail forwarder”Manage mail forwarders (aliases). A forwarder maps a source address to one or more destinations; a forwarder that shadows an existing mailbox or forms a mail loop with another alias is rejected, and so is one pointing at an address on a domain this server hosts that cannot receive — no mailbox, no other forwarder, no catch-all, or a suspended mailbox. Mail through such a forwarder would be bounced straight back at its sender.
That check guards new saves; a destination deleted or suspended afterwards still leaves a
forwarder pointing at nothing, so list flags those where you will see them:
support@example.com -> old-staff@example.com !! cannot receive: old-staff@example.com# List forwarders for a domaincorepanel mail forwarder list example.com
# Create or replace a forwarder to one or more destinationscorepanel mail forwarder set sales@example.com a@example.com b@partner.tld
# Keep a copy in the mailbox and send one oncorepanel mail forwarder set sales@example.com sales@example.com boss@partner.tld
# Delete a forwardercorepanel mail forwarder delete sales@example.comNaming the source address among its own destinations is what keeps the mail in its mailbox while forwarding a copy elsewhere. It requires a mailbox at that address whose delivery is open; without one the address resolves to nothing and the forwarder is refused. Deleting the mailbox drops the local copy, and removes the forwarder if that was its only destination.
corepanel mail catchall
Section titled “corepanel mail catchall”Manage the domain catch-all: any address that does not match a mailbox or forwarder is routed to the catch-all destinations.
# Show the current catch-allcorepanel mail catchall get example.com
# Route all otherwise-unmatched mail to a destinationcorepanel mail catchall set example.com bucket@example.com
# Remove the catch-allcorepanel mail catchall delete example.comcorepanel mail redirect
Section titled “corepanel mail redirect”Redirect all mail for a domain to the same local part at another domain (Postfix
@source → @target). Useful when a domain is renamed or consolidated: sales@old.com
becomes sales@new.com. A domain has a single @domain route, so setting a redirect
replaces any catch-all configured on the domain.
# Show the current whole-domain redirectcorepanel mail redirect get old-example.com
# Redirect every address to the same local part at another domaincorepanel mail redirect set old-example.com example.com
# Remove the redirectcorepanel mail redirect delete old-example.comcorepanel mail vacation
Section titled “corepanel mail vacation”Manage a mailbox vacation autoresponder (an out-of-office reply). CorePanel renders the
reply as a Sieve script that Dovecot runs at delivery, so replies are sent server-side even
when no mail client is connected. Replies are rate-limited per sender by --days.
# Show the current autorespondercorepanel mail vacation get sales@example.com
# Enable an autoresponder (optionally bounded to a date window)corepanel mail vacation set sales@example.com \ --subject "Out of office" --message "Back on Monday." --days 2 \ --start 2026-07-20 --end 2026-07-27
# Save it without turning it on yetcorepanel mail vacation set sales@example.com --subject "OOO" --message "…" --inactive
# Remove the autorespondercorepanel mail vacation clear sales@example.comcorepanel mail filter
Section titled “corepanel mail filter”Manage mailbox Sieve filter rules. Each rule matches on message headers and applies actions; CorePanel compiles the rules to a Sieve script executed at delivery.
- fields:
from,to,cc,subject - operators:
contains,is,matches(the last supports*/?wildcards) - actions:
fileinto(folder),redirect(address),discard,keep,stop
# Print the current rules as JSONcorepanel mail filter get sales@example.com
# Replace the rules from a JSON arraycorepanel mail filter set sales@example.com --file rules.json
# Clear all rulescorepanel mail filter clear sales@example.comExample rules.json:
[ { "name": "Newsletters to a folder", "matchAll": true, "conditions": [{ "field": "subject", "op": "contains", "value": "newsletter" }], "actions": [{ "type": "fileinto", "arg": "Lists" }, { "type": "stop" }] }]corepanel mail spam
Section titled “corepanel mail spam”Manage spam thresholds and allow/deny sender lists at a scope, enforced by Rspamd.
- scope:
global(no target),domain <domain>, ormailbox <address> - thresholds:
reject,add_header,greylist(Rspamd action scores);0inherits the Rspamd default, and the effective values must satisfygreylist ≤ add_header ≤ reject - lists:
allow(never mark matching mail as spam) anddeny(always reject it); a pattern is a sender email address or a bare domain
More specific scopes and explicit allow/deny lists take precedence over broader ones.
# Show a mailbox's thresholds and listscorepanel mail spam show mailbox sales@example.com
# Set per-domain thresholds (0 = inherit default; all-zero clears the policy)corepanel mail spam threshold domain example.com --reject 12 --add-header 5
# Allow / deny senderscorepanel mail spam allow mailbox sales@example.com friend@partner.tldcorepanel mail spam deny global spammer.example
# Remove a list entry by its id (from `show`)corepanel mail spam remove domain example.com 7A message that reaches the add-header threshold is filed in the recipient’s Junk folder; one above the reject threshold never reaches a mailbox. Spam training is automatic from there: moving a message to Junk teaches the filter it is spam, and moving it back out teaches it as legitimate.
corepanel mail deliverability
Section titled “corepanel mail deliverability”Reports whether mail leaving this server is accepted — the server-wide counterpart to the per-domain SPF/DKIM/DMARC records. It checks the name the server announces (HELO) against the panel domain, the reverse DNS of every address it sends from, their blocklist reputation, which address family Postfix prefers, and record coverage across the server’s domains. See Email Deliverability for what each check means.
# Run the checks (results are cached for an hour)corepanel mail deliverability
# Re-run everything, blocklist queries includedcorepanel mail deliverability --refresh
# Set the HELO name to this server's panel domaincorepanel mail deliverability repair-helo
# Choose which address family outbound mail tries firstcorepanel mail deliverability prefer ipv4corepanel mail deliverability prefer anyEmail deliverability — checked 2026-08-10T14:38:00ZHELO: vps-1a0b77bc.vps.ovh.net (panel domain: server.example.com)
[FAIL] helo the HELO name is "vps-1a0b77bc.vps.ovh.net" but this server's domain is "server.example.com"[FAIL] reputation 2001:db8:305:2100::1:3d3a on Spamhaus Zen: CSS — snowshoe/low-reputation sending detected https://check.spamhaus.org/query/ip/2001:db8:305:2100::1:3d3a[warn] ptr the reverse DNS of 203.0.113.25 does not match the HELO name[ ok ] outbound_preference outbound mail prefers IPv4[ ok ] dkim the signer is running and all 4 domains publish a DKIM keyEach check prints one of [ ok ], [warn], [FAIL] or [ ? ]. [ ? ] is not a
pass: it means the check could not be made at all — most often a blocklist refusing
queries that arrive through a public resolver such as 8.8.8.8, or one that is over its
daily budget. Point the server at its own resolver and run it again with --refresh.
prefer ipv4 is what CorePanel installs with. Postfix’s own default, any, chooses
between IPv4 and IPv6 at random for every destination that publishes both — so on a
server whose IPv6 address has no reputation, roughly half of all mail is a coin flip.
corepanel edition websites
Section titled “corepanel edition websites”Reports how many websites this server may host and how many are already in use.
corepanel edition websitescorepanel edition websites --jsonEdition: personalWebsites: 12 / 20Available: 8A website is a primary or an addon domain — the two kinds that own a document root and serve a site of their own. Domain aliases (parked domains) and subdomains are not counted: they republish a document root that is already counted, and stay unlimited in every edition. The figure is server-wide, across every account.
The allowance is enforced by the backend on every creation path — account create,
domain add --type addon, a cPanel import and a
backup restore — so hitting the limit refuses the operation before
anything is provisioned. The free Personal edition hosts up to 20 websites; Pro and
Business are unlimited (reported as "limit": 0, "unlimited": true).
The exit status is 1 once the allowance is exhausted, so a provisioning script can check for room before it starts:
# Bail out early when the server is fullcorepanel edition websites >/dev/null || { echo "no room left"; exit 1; }
# How many are left?corepanel edition websites --json | jq '.remaining'See the server-wide website limit for the full picture.
corepanel license
Section titled “corepanel license”Manages this server’s license — the credential that unlocks the Pro and Business
features. edition reports what the server is allowed to do; license is where that
allowance comes from.
corepanel license status [--json]corepanel license activate [key] [--key-stdin] [--json]corepanel license trial --edition pro|business --email ADDRESS [--consent] [--json]corepanel license refresh [--json]corepanel license deactivate [--yes]corepanel license upgrade [--edition pro|business] [--cycle monthly|yearly] [--wait] [--json]corepanel license claim [--json]corepanel license cancel-purchaseTwo rules explain the whole command group:
- The license binds to the public IPv4 the license server observes on the connection,
never to one this machine reports. There is nothing to configure and nothing to spoof;
a change of address is handled by
refresh, which rebinds and retries by itself. - Licensing never stops sites from being served. If the license server cannot be reached, the last verified license keeps working for 14 days (a warning starts at 7); only a verdict — expired or revoked — degrades the server immediately. Degrading means falling back to Personal features; nothing is taken offline.
corepanel license status
Section titled “corepanel license status”Reports the licensing state from the license cached locally, so it works offline and is safe to call from a monitoring script.
Edition: proKey: CPL-EQ0T6-…-2ES83Status: activeBound to: 203.0.113.24Valid to: 2027-08-01 00:00 UTCVerified: 2026-08-01 04:12 UTCThe report distinguishes the edition in effect from the edition the license pays
for; they differ exactly when something needs attention, and then the licensed edition
is printed as not in effect.
The exit status is 1 once the licensed edition is not in effect and 0 otherwise. A server with no license at all is not a degradation — Personal is a supported edition and the status stays 0.
Alert (alert in JSON) | Meaning |
|---|---|
| (empty) | Healthy, or unlicensed Personal |
validation_failing | Check-ins have been failing for over 7 days; the edition still holds until the grace deadline |
grace_expired | Check-ins never recovered; the server fell back to Personal |
expired | The license ran out (payment stopped) |
revoked | The license was revoked (refund or chargeback) |
unverifiable | The stored license no longer verifies against the trusted signing key |
The key is always shown masked (CPL-EQ0T6-…-2ES83): the panel never returns it in full.
corepanel license activate
Section titled “corepanel license activate”Binds a license key to this server and unlocks its edition. The key looks like
CPL-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX; it is case-insensitive and the dashes are optional.
A key already bound to another server is rebound to this one, spending one of the
license’s rebinds (3 per 30 days). Releasing the old server first with
corepanel license deactivate does not spend one.
The key is a bearer credential: passing it as an argument leaves it in the shell history
and in the process list, so --key-stdin is the right form on shared machines. With no
argument and no --key-stdin, an interactive run prompts for it and a scripted one
fails rather than hanging.
corepanel license activate --key-stdin < license.keycorepanel license refresh
Section titled “corepanel license refresh”Forces a check-in instead of waiting for the daily job. Use it after paying an invoice, after changing the server’s IP address, or to confirm connectivity to the license server. A failed check-in does not degrade the server — the grace window has to run out first.
corepanel license deactivate
Section titled “corepanel license deactivate”Releases the license so it can be activated on another server: the license server drops the binding and this server forgets the key. The server returns to Personal features immediately.
Because the key is forgotten locally, the command asks for confirmation — keep the key at
hand before running it. A non-interactive run must pass --yes; without it, it refuses
rather than proceeding silently.
corepanel license trial
Section titled “corepanel license trial”Starts a 14-day free trial of Pro or Business on this server. No card and no key: the license server mints the trial, binds it to the public IPv4 it observes and this server stores it, exactly as if it had been bought. It is the same thing the panel offers on the first sign-in, for a server being set up over SSH.
corepanel license trial --edition business --email ops@example.com--email is required — it is where the warnings go before the trial ends (7, 3 and 1 days
before, and once after). --consent is a separate opt-in for product news and changes
nothing about the trial.
- One trial per server and per email address. A second request is refused and the answer is the checkout, not a retry.
- Switching edition mid-trial is free and keeps the original end date.
- When it ends the server returns to Personal. Sites, mail, databases and certificates keep running; what stops is the paid features and creating sites beyond the allowance of 20.
On a Business trial, plan for one consequence: hosting accounts sign in to the client area, which Personal does not include. When the licence ends they keep access for 5 days, with a notice telling them to contact their provider, and then those logins stop. Their sites and mail are unaffected.
corepanel license upgrade
Section titled “corepanel license upgrade”Opens a checkout for this server and prints the URL to complete it. Nothing is charged by the command: it mints a single-use token, ties it to this server and hands back the link.
--edition takes pro or business; the catalogue lives in corepanel-core, so an
edition it does not sell is refused here rather than opening a checkout that fails at the
far end.
The link works from any browser — it does not have to be one on this server. The license is fetched afterwards by this server, which is what binds it to this server’s public address rather than to anything entered at checkout. The first server to redeem a link takes the license, so a link is not something to share.
--wait keeps the terminal open until the payment is confirmed, polling every 5 seconds
for up to 10 minutes. Interrupting it (or never using it) costs nothing: the panel
finishes the purchase in the background, and an unredeemed one is retried at every
check-in.
A server that already holds a working license is refused — changing plan is a change to the subscription, not a second purchase.
corepanel license claim
Section titled “corepanel license claim”Installs the license for a purchase that was already paid for. The panel does this by itself, so this is for when nobody was watching: a browser closed at the wrong moment, or a restart mid-checkout. It is safe to repeat — a repeated claim from the same server returns the same license — and while the payment has not reached the license server yet it says so and changes nothing.
corepanel license cancel-purchase
Section titled “corepanel license cancel-purchase”Forgets a checkout that was never completed. It redeems it first: a purchase that turns out to have been paid for is installed rather than discarded. It never cancels a payment.
# Buy Pro, yearly, and wait until the payment landscorepanel license upgrade --edition pro --cycle yearly --wait
# Buy Business, monthlycorepanel license upgrade --edition business
# Paid in the browser, closed the tab: install the license nowcorepanel license claim
# Monitoring: alert when the paid edition is not in effectcorepanel license status >/dev/null || echo "licensing needs attention"
# Moving to a new server: release here first so the rebind is freecorepanel license deactivate --yes
# Scripting: how long until the grace window closes?corepanel license status --json | jq -r '.graceExpiresAt // "n/a"'corepanel telemetry
Section titled “corepanel telemetry”Controls whether this server takes part in the daily check-in. It has nothing to do with licensing despite sharing an endpoint: it carries no credential, unlocks nothing, and runs on every edition including Personal. See Usage statistics for the complete field list.
corepanel telemetry status [--json]corepanel telemetry enable [--json]corepanel telemetry disable [--json]It is on by default. What it sends, once a day: a random installation id, the CorePanel version and edition, the OS and architecture, how long ago it was installed, counts of accounts/domains/mailboxes/databases/applications/WordPress sites, and the names of the features that are switched on. What it never sends: hostnames, domain names, email addresses, account names, or anything typed into the panel. The receiving end keeps the public IP the check-in arrives from, resolves it to a country and may look up its reverse DNS; CorePanel staff can see both.
status exits 0 either way — not reporting is a supported configuration, not a fault.
Turning it off stops future check-ins. It does not remove what was already reported: your server cannot reach into our records to delete rows.
For a fleet, COREPANEL_NO_TELEMETRY=1 in the environment of corepanel-core turns it off
without a database write, so a golden image decides it once for every machine cloned from
it.
# Are we reporting, and under which id?corepanel telemetry status
# Opt outcorepanel telemetry disable
# Scripting: the id this server is counted undercorepanel telemetry status --json | jq -r .installIdcorepanel notifications
Section titled “corepanel notifications”Reads what CorePanel has been trying to tell you — and proves that the email side of it works before you need it. See Notifications for what produces these events and how they are delivered.
corepanel notifications list [--unread] [--severity <s>] [--source <m>] [--audience admin|seller|owner|all] [--account <user>] [--limit <n>] [--json]corepanel notifications ack [--all | --id <id>...] [--audience <a>] [--account <user>]corepanel notifications test [--audience admin|seller|owner] [--account <user>] [--seller-id <id>]Two things are specific to the CLI:
- It is root-local, so it may read every audience. The panel cannot — a session only ever sees its own notifications. This is what answers “did my customer actually get that mail?” without opening the database.
--accountimplies--audience owner. Asking for one account’s notifications as the administrator would otherwise return your copies of events about that account, which is a different list.
corepanel notifications list
Section titled “corepanel notifications list”Lists notifications newest-first with the delivery state of each one. The DELIVERY
column is the useful part when something did not arrive:
| State | Meaning |
|---|---|
pending | Not decided yet; the next delivery pass (once a minute) will |
sent | Handed to the local mail server — which is not the same as delivered |
skipped | Deliberately not mailed, with the reason beside it |
The skip reasons are no_address, disabled, below_severity,
local_recipient_affected (the recipient’s mailbox lives inside the very account
the alert is about, so the message could not have arrived) and send_failed.
A ×N beside the event means repeats were folded into one row while it was unread.
corepanel notifications ack
Section titled “corepanel notifications ack”Marks notifications as read, by id or all at once. Read state is per recipient: acknowledging your copy leaves the customer’s unread, because they have not seen it. A read notification stops counting towards the bell badge and is kept for 90 days rather than 180.
corepanel notifications test
Section titled “corepanel notifications test”Writes a test notification for one recipient and mails it immediately, bypassing the severity policy and the hourly rate limit. It fails loudly — rather than quietly doing nothing — when the recipient has no address on file or has notification email turned off, because those are the two things you would otherwise spend an afternoon discovering.
“Sent” means the local mail server accepted it. A fresh VPS sending as
corepanel@<hostname> with no SPF, no DKIM and no matching PTR is exactly the
profile large providers drop silently, so check the receiving mailbox including
its spam folder — and see
Making the email actually arrive.
# What has the server been trying to tell me?corepanel notifications list --unread
# Only the things that are actually on firecorepanel notifications list --severity critical
# Did this customer get their "WordPress is ready" mail?corepanel notifications list --account john
# Everything, every audience — support viewcorepanel notifications list --audience all --limit 200
# Prove email works before something breakscorepanel notifications testcorepanel notifications test --account john
# Clear the badgecorepanel notifications ack --all
# Monitoring: anything unread and critical?corepanel notifications list --unread --severity critical --json | jq '.total'corepanel version
Section titled “corepanel version”Prints the CLI build version embedded at compile time.
corepanel versionFTP accounts
Section titled “FTP accounts”Manage the FTP accounts of a hosting account. Every account is provisioned with a primary FTP account named after its system user, reaching the whole home; these commands manage the additional ones beside it. Accounts are identified by their username, any domain they own, or their numeric account id (see Naming an account).
corepanel ftp list <account>corepanel ftp add <account> <username> --domain <domain> [flags]corepanel ftp passwd <account> <ftp-account-id>corepanel ftp remove <account> <ftp-account-id>An additional account is named user@domain, and the domain has to be one the account
owns. That is not cosmetic: FTP authenticates by username alone, so the names are unique
across the whole server — requiring a domain you already hold is what stops one customer
claiming a name another account may need later.
These accounts are virtual: no Linux user is created. They log in with the hosting account’s own uid and gid, which means they share its disk quota and see exactly what that user can see — there is no way for one to reach outside the account.
--directory confines the login to a subdirectory of the account home, which is the
usual reason to make one: a designer who should reach uploads and nothing else. The
directory is created if it does not exist (0700, owned by the account); a path that
would leave the home is refused, symlinks included.
Flags for add:
| Flag | Description |
|---|---|
--domain <domain> | A domain of this account, forming user@domain (required) |
--directory <path> | Directory relative to the account home, created if missing (default: the whole home) |
--password <pw> | Password — visible in shell history; prefer the options below |
--password-stdin | Read the password from standard input |
passwd takes --password / --password-stdin with the same meaning and changes one
account’s password, the primary included. Omitting both on a terminal prompts for the
password without echoing it.
corepanel ftp add 12 designer --domain example.com --directory domains/example.com/uploadscorepanel ftp add 12 deploy --domain example.com --password-stdin < /root/ftp.pw
corepanel ftp list 12corepanel ftp passwd 12 4corepanel ftp remove 12 4Deleting an account removes the credential and leaves its files alone — the directory it pointed at stays exactly as it was. The primary account cannot be deleted: it is the account’s own credential, and removing it would leave the owner unable to reach their files.
How many additional accounts an account may have comes from its package
(--max-ftp on corepanel account create and
update, where 0 means unlimited), with a hard ceiling of
100 underneath it. The primary account is mandatory and counts against neither.
Cron jobs
Section titled “Cron jobs”Manage per-account cron jobs. CorePanel stores the jobs and renders the account’s
crontab (/var/spool/cron/<user>), which cronie executes as the account user — so
scheduled jobs keep running independently of the panel and behave exactly like cPanel.
A bare php in a command resolves to the account’s own PHP version, so standard
WHMCS/WordPress cron lines work unchanged. Accounts are identified by their username, any domain they own, or their numeric account id (see Naming an account).
corepanel cron list <account>corepanel cron add <account> --schedule <spec> --command <cmd> [flags]corepanel cron update <account> <job-id> --schedule <spec> --command <cmd> [flags]corepanel cron remove <account> <job-id>corepanel cron enable|disable <account> <job-id>corepanel cron run <account> <job-id>The --schedule value is a five-field cron spec ("*/5 * * * *") or one of the
macros @hourly, @daily, @midnight, @weekly, @monthly, @yearly,
@annually. It is validated and the next run computed before the job is stored.
@reboot and @every <duration> are not supported — use a five-field spec
(*/30 * * * * for “every 30 minutes”).
Flags for add / update:
| Flag | Description |
|---|---|
--schedule <spec> | Five-field cron spec or @macro (required) |
--command <cmd> | Command to run (required) |
--comment <text> | Human-readable label |
--mailto <email> | Email job output to this address (empty disables mail) |
--timezone <tz> | IANA timezone for the schedule (e.g. America/Santiago) |
--disabled | Create/leave the job disabled (had no effect before 1.6.14: the job was created enabled) |
--single-instance | Skip a run if the previous one is still active (flock) |
--timeout <sec> | Kill the job after N seconds |
--cpu <sec> | CPU-time cap |
--mem-kb <kb> | PHP memory limit in KB (applied to PHP jobs via memory_limit) |
--nproc <n> | Max processes |
--nice <0-19> | Niceness (higher = lower priority) |
# WordPress and WHMCS cron, cPanel-compatible:corepanel cron add 12 --schedule "*/5 * * * *" --command "php /home/user/public_html/wp-cron.php"corepanel cron add 12 --schedule "@daily" --command "php ~/whmcs/crons/cron.php" --mailto ops@example.com --single-instance
corepanel cron list 12corepanel cron run 12 3 # run once, now, and show the outputcorepanel cron disable 12 3Jobs run through a wrapper that applies the resource limits, single-instance lock, and a timeout, and captures output. Disabling a job keeps it in the list but comments it out of the crontab.
The command environment
Section titled “The command environment”There is no allowlist: the command is any shell line the account could type itself. It
runs as the account user (never root), so it can only reach what that user can reach —
homes are 0700 and files 0600.
Every job is executed as /bin/sh -c '<your command>' with:
SHELL=/bin/shPATH=/opt/corepanel/bin:/usr/local/bin:/usr/bin:/binWhat that means in practice:
- It is
sh, notbash. No[[ ]], arrays or process substitution. If you need bash features, wrap them:bash -c '...'. phpworks on its own and resolves to that account’s PHP version, even though the host has no global PHP. The shim also points PHP’s temporary files, uploads and sessions at the account’s private~/tmp, and applies the job’s--mem-kbas PHP’s ownmemory_limit. Do not hardcode/usr/bin/php— it does not exist on a CorePanel host, and an absolute Remi path such as/opt/remi/php84/root/usr/bin/phppins the job to one PHP version and loses the temp-dir isolation and memory limit.wp(WP-CLI) is on the PATH and runs with the account’s PHP.- Pipes, redirections,
&&and variables are all fine — it is a normal shell line. - Base tools available to jobs include
curl,wget,git,tar,unzip,rsync,jq,findand the MariaDB client (mysqldump). %needs no escaping. CorePanel escapes it for cron, sodate +%Yreaches the shell verbatim.~expands to the account home, since cron setsHOME— but only at the start of a word. Inside a flag (--path=~/public_html) it stays literal; write"$HOME/..."there instead.
More valid commands
Section titled “More valid commands”# Application cron entry pointsphp ~/public_html/cron.phpphp ~/whmcs/crons/cron.phpphp ~/app/artisan schedule:run # Laravel scheduler (run every minute)
# WordPress via WP-CLI (after disabling wp-cron.php in wp-config.php)wp --path="$HOME/public_html" cron event run --due-nowwp --path="$HOME/public_html" plugin update --allwp --path="$HOME/public_html" db export ~/backups/wp-$(date +%F).sql
# Trigger something over HTTP instead of on diskcurl -fsS https://example.com/tasks/run > /dev/nullcurl -fsS -m 30 https://example.com/health || echo "health check failed"
# Database dump with rotation (credentials in ~/.my.cnf, mode 0600)mysqldump --defaults-extra-file="$HOME/.my.cnf" mydb | gzip > ~/backups/db-$(date +%F).sql.gzfind ~/backups -name 'db-*.sql.gz' -mtime +14 -delete
# Housekeepingfind ~/tmp -type f -mtime +7 -deletefind ~/logs -name '*.log' -size +100M -delete
# Your own scripts (must be executable, or invoke the interpreter explicitly)~/bin/nightly-report.sh >> ~/logs/report.log 2>&1sh ~/bin/sync-assets.shbash -c 'for d in ~/sites/*/; do echo "checking $d"; done'
# Archive a directorytar -czf ~/backups/uploads-$(date +%F).tar.gz -C ~/public_html uploadsPassing a password on the command line (mysqldump -p'secret') works but exposes it in
the process list to other processes on the host; a ~/.my.cnf with mode 0600 is the
safer form shown above.
What does not work
Section titled “What does not work”| Command | Why |
|---|---|
/usr/bin/php script.php | No global PHP on the host — exits 127. Use php. |
sudo ..., anything as root | Jobs run as the account user; cronie drops privileges first. |
| Multi-line commands | Newlines and control characters are rejected — a job is one line. |
--schedule "@reboot" | Not a supported macro (see above). |
| Reading another account’s files | Homes are 0700; jobs are confined to their own account. |
An account may define up to 100 jobs.
Domains
Section titled “Domains”Manage an account’s domains beyond the primary. Two types are supported:
- alias (parked): serves the account’s primary docroot under its own DNS zone (pointing at the server IP), and its mail is routed to the primary — it has no independent mailboxes. Aliases count against the account’s parked-domain limit.
- addon: a fully independent domain inside the same account — its own docroot at
~/domains/<domain>, its own DNS zone, and its own mail identity (DKIM + awebmail.<domain>vhost). Addon domains can have their own mailboxes and count against the account’s dedicated addon-domain limit.
Both limits use 0 = unlimited. Accounts are identified by their username, any domain they own, or their numeric account id (see Naming an account)
(list them with corepanel account list); domain names are
globally unique.
corepanel domain list <account>corepanel domain add <account> <domain> [--type alias|addon] [--alias-for <domain>] [--web-mode <mode>] [--canonical <host>]corepanel domain delete <domain>corepanel domain canonical show <domain>corepanel domain canonical set <domain> <none|www|nonwww|primary>corepanel domain optimizations show <domain>corepanel domain optimizations set <domain> [--webp on|off|default] [--minify-js on|off|default] [--minify-css on|off|default] [--early-hints on|off|default] [--page-cache off|shadow|on] [--state-cookies <names>]listprints every domain the account owns — primary and secondary — with its type, alias target, document root, web mode and canonical host (core.ListAccountDomains). The document root is printed because it is not derivable from the name: an addon owns~/domains/<domain>, an alias shares the primary’s~/public_html, and an account imported from another panel keeps the layout it arrived with.addvalidates the FQDN, global uniqueness, ownership and the matching per-type limit, provisions the vhost + DNS zone (and, for an addon, its own docroot, DKIM andwebmail.<domain>) viasys.AddDomain, and moves the usage counter (core.AddDomain). An alias republishes the account’s primary domain by default;--alias-forparks it on one of the account’s addon domains instead, so it serves that site’s document root and its mail is redirected there. The target must belong to the same account and be a primary or addon domain — an alias of an alias is refused.deleteremoves a secondary domain (core.DeleteDomain). For an addon this also tears down its mailboxes, DNS zone, DKIM, docroot, subdomains and any alias parked on top of it — those serve the addon’s document root, so they cannot outlive it. The account’s primary domain cannot be removed this way — it is only removed by deleting the account.canonical showprints which hostname of the domain serves the site and what the others do (core.GetDomainCanonicalHost).canonical setpicks the real hostname (core.SetDomainCanonicalHost) and re-emits the site config. Every domain answers under both<domain>andwww.<domain>, and an alias adds two more hostnames serving a copy of the primary’s site — identical content on several hostnames is duplicate content, which splits link equity and leaves search engines guessing which URL is authoritative. The hostnames that are not canonical answer a 301 that preserves the path and query string, so deep links and indexed URLs keep working.optimizations showprints a domain’s web optimization overrides (core.GetDomainOptimizations).optimizations setsets them (core.SetDomainOptimizations) and re-emits the site config. WebP-on-the-fly and Early Hints (HTTP 103) are baseline in every edition and on by default — WebP transparently serves WebP to browsers that accept it (safe fallback to the original image); Early Hints learns a page’s preload sub-resources and replays them as a 103 on later requests (HTTP/2 and HTTP/3 only, inert for static/SPA sites). CSS/JS minification and the dynamic page cache are Speed Optimizer (Pro/Business) and off by default — minification can break sites (source maps, already-minified assets, execution order), and a page cache should be measured before it serves anything, so both are opt-in. Use these commands to override each per domain.
Flags for add:
| Flag | Description |
|---|---|
--type <type> | Domain type: alias (parked, default) or addon (independent) |
--alias-for <domain> | For an alias, the site it republishes: an addon domain of the same account. Empty parks it on the primary (the default) |
--web-mode <mode> | Web serving mode plain|cms|spa (empty ⇒ plain) |
--canonical <host> | Canonical hostname none|www|nonwww|primary (empty ⇒ www) |
Values for canonical set (and --canonical):
| Value | Effect |
|---|---|
none | Every hostname serves the site directly, no redirect. Domains created before this feature keep this after an upgrade — nothing changes for a live site until you choose a canonical host. |
www | www.<domain> serves; <domain> answers 301 to it. Default for new domains. |
nonwww | <domain> serves; www.<domain> answers 301 to it. |
primary | Alias (parked) domains only. Every hostname of the alias answers 301 to the primary’s canonical hostname, in a single hop, instead of serving a copy of its site. Changing the primary’s canonical host re-points every alias set to primary automatically. |
Flags for optimizations set:
| Flag | Description |
|---|---|
--webp <state> | WebP-on-the-fly: on (force), off (disable), or default (follow the server default, on). Omitting the flag resets to default. |
--minify-js <state> | CSS/JS minify — JavaScript (Speed Optimizer, Pro/Business): on (force), off (disable), or default (follow the server default, off). Omitting the flag resets to default. |
--minify-css <state> | CSS/JS minify — CSS (Speed Optimizer, Pro/Business): on (force), off (disable), or default (follow the server default, off). Omitting the flag resets to default. |
--early-hints <state> | Early Hints (HTTP 103): on (force), off (opt this domain out), or default (follow the server default, on). Omitting the flag resets to default. |
--page-cache <mode> | Dynamic page cache (Speed Optimizer, Pro/Business): off, shadow (evaluate without serving) or on. Not tri-state — there is no server default to follow, so omitting the flag turns the cache off. Refused on alias (parked) domains. |
--state-cookies <names> | Extra cookies that mean “this visitor has state”, comma-separated; a request carrying one bypasses the page cache and its response is never stored. A trailing * matches a prefix; at most 32. Added to the cookies CorePanel already knows (WordPress session, comments, WooCommerce, Aelia currency), which always apply. Not whole-state: omitting the flag keeps the site’s current list; pass "" to clear it. |
The page cache stores whole pages so a repeat visitor is answered without running PHP at all. Two things make it safe to switch on:
- Only what WordPress itself declares shareable is stored. CorePanel’s companion plugin decides, page by page, whether a response can be handed to the next visitor, and purges the right URLs when a post is published or edited. A site without the plugin is not cached at all rather than guessed at.
shadowmeasures before anything is served. In shadow the cache decides and stores exactly as it would in production and serves none of it, so you can see the hit ratio a site would get before a single visitor is affected. Leaving shadow is self-healing: those entries are dropped on sight, with nothing to purge by hand.
corepanel domain list 12corepanel domain add 12 parked.example.comcorepanel domain add 12 shop.example.net --type addon --web-mode cmscorepanel domain add 12 parked.example.com --canonical primarycorepanel domain add 12 alias-of-shop.example.com --alias-for shop.example.netcorepanel domain delete parked.example.comcorepanel domain canonical show example.comcorepanel domain canonical set example.com wwwcorepanel domain canonical set example.com nonwwwcorepanel domain canonical set parked.example.com primarycorepanel domain optimizations show shop.example.netcorepanel domain optimizations set shop.example.net --webp offcorepanel domain optimizations set shop.example.net --webp defaultcorepanel domain optimizations set shop.example.net --minify-js on --minify-css oncorepanel domain optimizations set shop.example.net --early-hints offcorepanel domain optimizations set shop.example.net --page-cache shadowcorepanel domain optimizations set shop.example.net --page-cache oncorepanel domain optimizations set shop.example.net --state-cookies "my_currency,my_shop_*"corepanel domain optimizations set shop.example.net --state-cookies ""Reverse proxy routes
Section titled “Reverse proxy routes”Publish a local service under one of the server’s domains. A route forwards the requests
matching a path to a host:port upstream; every unmatched path keeps being served from the
docroot. Routes are evaluated first match wins, and CorePanel keeps them ordered
most-specific-first so a catch-all never hides a more specific route. The full guide —
what the feature is for, when to use it and how the panel presents it — is in
Reverse Proxy Routes.
These commands are administrator-only, and that is a security boundary rather than a UI
choice: the caller picks the upstream, so pointed at an internal service the route would
publish it on the public internet under the domain’s name. Targets on the local loopback are
filtered accordingly — the web server’s own ports (80/443) are refused outright because they
would loop, and well-known service ports (corepanel-api, MySQL, PostgreSQL, Redis, Memcached,
SSH, SMTP) require --allow-reserved-port.
corepanel domain proxy list <domain> [--check]corepanel domain proxy add <domain> <path> <upstream> [--strip-prefix] [--tls] [--allow-reserved-port]corepanel domain proxy remove <domain> <path>listprints the domain’s whole route table in evaluation order (core.GetDomainRoutes). The MANAGED column marks which entries these commands own:yesfor a proxy route you added,autofor the docroot fallback CorePanel maintains,nofor a hand-written route it leaves untouched. With--checkevery proxy target is dialed (core.CheckProxyUpstreams) and a STATUS column reportsup (Nms)orDOWN: <reason>— the fastest way to tell “my service died” from “my site is broken”, which from the outside are the same 502.addadds or replaces a route (core.SetDomainProxyRoute).<path>is the matcher — a prefix (/api/*), an exact path (/health) or a catch-all (/*) — and it is the route’s identity: re-runningaddwith the same path edits that route in place instead of adding a second, unreachable one.<upstream>ishost:port, never a URL — or the unix socket of an application CorePanel published, written asunix:/run/corepanel/apps/<account>/<app>.sock. Only that exact layout is accepted, so no other socket on the server can be named; CorePanel writes those routes itself. The upstream is probed right after the route is applied, and a warning is printed if nothing is listening (the route is applied either way).removedrops the route matching<path>(core.DeleteDomainProxyRoute). Removing the last one returns the site to serving its docroot directly; removing a path that has no route is not an error.
Flags for add:
| Flag | Description |
|---|---|
--strip-prefix | Remove the matcher prefix from the proxied request, so /api/* reaches the upstream as /*. Ignored for a catch-all matcher. |
--tls | Dial the upstream over HTTPS instead of plain HTTP. |
--allow-reserved-port | Confirm a target on a well-known local service port. |
corepanel domain proxy list app.example.comcorepanel domain proxy list app.example.com --checkcorepanel domain proxy add app.example.com '/api/*' 127.0.0.1:8080 --strip-prefixcorepanel domain proxy add app.example.com '/*' 127.0.0.1:8080corepanel domain proxy remove app.example.com '/api/*'Subdomains
Section titled “Subdomains”Manage an account’s subdomains (e.g. blog.example.com). A subdomain gets its own document
root (~/domains/<fqdn>, beside the primary’s public_html rather than inside it) and web
vhost, plus a single A record inside its parent domain’s existing DNS zone — it does not create a new zone and has no mail of
its own. TLS is still issued for the subdomain FQDN. The parent must be an existing
primary or addon domain owned by the account. Subdomains count against the account’s
subdomain limit (0 = unlimited). Accounts are identified by their username, any domain they own, or their numeric account id (see Naming an account)
— find it with corepanel account list.
corepanel subdomain list <account>corepanel subdomain add <account> <label> <parent-domain> [--web-mode <mode>]corepanel subdomain delete <fqdn>corepanel subdomain optimizations show <fqdn>corepanel subdomain optimizations set <fqdn> [--webp on|off|default] [--minify-js on|off|default] [--minify-css on|off|default] [--early-hints on|off|default] [--page-cache off|shadow|on] [--state-cookies <names>]listprints every subdomain the account owns, with its parent, web mode, and document root (core.ListSubdomains).addvalidates the label, parent ownership, global uniqueness (across both domains and subdomains) and the subdomain limit, provisions the docroot + vhost + parent-zone A record viasys.AddSubdomain, and moves the usage counter (core.AddSubdomain).<label>is what the subdomain adds in front of the parent zone: usually one label (blog), but several are allowed for a nested subdomain (dev.blogunderexample.com⇒dev.blog.example.com), which other panels create and an import has to reproduce. As a whole label,wwwandwebmailare reserved (they are the parent’s own hostnames); nothing auto-provisionswww.blog.example.com, sowww.blogis accepted. A nested name is not covered by the parent’s wildcard certificate — a wildcard spans one label — so it gets a certificate of its own.deleteremoves a subdomain by its fully-qualified name (core.DeleteSubdomain): the vhost, the parent-zone A record, the certificate and the document root.optimizationsreads and writes the subdomain’s web optimizations — WebP, CSS/JS minification, Early Hints and the dynamic page cache. Same flags, same defaults and the same edition gating ascorepanel domain optimizationsin Domains above; see that section for what each flag does.
A subdomain is a site of its own — its own document root, usually its own
application — and inherits nothing from its parent domain: switching the page cache on
for example.com does not touch blog.example.com, and vice versa. Each is tuned
separately. (The alias refusal does not apply here: a subdomain serves its own content,
not a copy of another site’s.)
Flags for add:
| Flag | Description |
|---|---|
--web-mode <mode> | Web serving mode plain|cms|spa (empty ⇒ plain) |
corepanel subdomain list 12corepanel subdomain add 12 blog example.comcorepanel subdomain add 12 shop example.com --web-mode cmscorepanel subdomain add 12 dev.blog example.comcorepanel subdomain delete blog.example.comcorepanel subdomain optimizations show blog.example.comcorepanel subdomain optimizations set blog.example.com --page-cache shadowcorepanel subdomain optimizations set blog.example.com --webp off --minify-css onPage cache
Section titled “Page cache”Turning the dynamic page cache on or off is part of a site’s optimizations, above. These two commands cover what is not configuration: emptying the cache, and reading what an evaluation measured.
Sites are named by hostname — a domain, an alias or a subdomain — because that is how the cache itself is keyed.
corepanel cache purge <hostname> [path...]corepanel cache stats [hostname]purge with no paths empties the whole site; otherwise each argument is an absolute path,
optionally ending in * to purge a prefix. A path that is not absolute, or with a *
anywhere but at the end, is rejected rather than silently matching nothing.
Every hostname the site answers on is purged, not just the one you typed: a page is
stored once per hostname a visitor arrived through, so example.com and www.example.com
hold separate copies. The command prints the hostnames it reached — a missing one is a
separate site. Emptying the cache is never blocked by edition: pages cached while the
server was licensed are still on disk, and clearing a wrong page must always be possible.
stats only reports sites in evaluation mode (--page-cache shadow). A site with the
cache switched on produces no counters at all — the question the report answers is “what
would this site gain” — and the tallies reset when the web server restarts. HIT% counts
hits over the requests that were cacheable at all, so a bypassed request is not a miss;
BYPASS% is reported separately for exactly that reason.
corepanel cache purge example.comcorepanel cache purge blog.example.com / /feedcorepanel cache purge example.com "/2026/*"corepanel cache statscorepanel cache stats blog.example.com.htaccess
Section titled “.htaccess”.htaccess compatibility needs no configuration: the web server
compiles the rules it supports and picks a changed file up within a second, however it was
written. These commands answer the two questions a migrated site raises — what did the
server make of my file, and can I see the change now.
corepanel htaccess report <hostname> [--problems] [--json]corepanel htaccess reload <hostname> [directory...]report reads every .htaccess under the site’s document root and says what happened to
each directive:
| Status | Meaning |
|---|---|
applied | The rule is in force. |
no-op | It asks for what already happens here. |
unsupported | Out of scope by design — the explanation says why. |
error | It could not be compiled, and the line is skipped. |
There is deliberately no “pending”. unsupported is a decision, not a queue position:
most of those lines configure something this server does its own way — browser cache
policy, response headers, what runs as PHP — and the site is better off without them. The
ones worth acting on say so in their explanation.
The report reads the files on disk, which is what would apply; --problems hides
the applied lines, and --json prints the whole thing for scripting. The same view lives
in the panel under an account’s Apache rules section.
corepanel htaccess report example.comcorepanel htaccess report example.com --problemsIt drops the site’s compiled rules and the pages stored for them, in that order and in the same call — a cached page is answered before any rule is consulted, so dropping the rules alone would leave a visitor being served exactly what you just changed.
With no directories the whole site is covered; otherwise each argument is a directory
relative to the document root (/ for the root itself). Name the directory, not the
.htaccess file inside it. Dropped: 0 means nothing was compiled for those directories:
normal on a site nobody has visited, a wrong path on a busy one — and the document root
printed above it is what settles that. Never blocked by edition.
corepanel htaccess reload example.comcorepanel htaccess reload example.com wp-content wp-content/uploadsWordPress Manager
Section titled “WordPress Manager”Manage WordPress installations end to end: install, adopt, update, harden, verify and
roll back sites. Every operation runs WP-CLI as the account user (never as root)
with the account’s own PHP version. Accounts are identified by their numeric account
id; instances by the instance id shown in corepanel wp list.
corepanel wp list <account>corepanel wp scan <account>corepanel wp install <account> --domain <fqdn> --title <t> --admin-user <u> --admin-email <e> [flags]corepanel wp attach <account> <docroot-path>corepanel wp detach <account> <instance-id>corepanel wp login <account> <instance-id> [--user <login>]corepanel wp components <account> <instance-id>corepanel wp update <account> <instance-id> [--core] [--all-plugins] [--all-themes] [--plugins a,b] [--themes x,y]corepanel wp plugin|theme <action> <account> <instance-id> <slug>corepanel wp policy <account> <instance-id> <none|minor|all>corepanel wp hardening <account> <instance-id> [--set key=on|off ...]corepanel wp integrity <account> <instance-id> [--reinstall]corepanel wp maintenance|debug|indexing <account> <instance-id> [on|off]corepanel wp set-url <account> <instance-id> <url>corepanel wp restore-point create|list|rollback|deleteHighlights:
- 1-click install —
wp installprovisions the database and DB user, downloads core as the account, writes a hardenedwp-config.php(fresh salts, system-cron takeover, direct filesystem access), creates the native*/5cron job that replaces WordPress’s visitor-paid pseudo-cron, and sets the domain’s web mode tocms. When--admin-passwordis omitted a strong password is generated and shown once. - Adopt existing sites —
wp scanfinds installations anywhere under the account home (including sites migrated with the cPanel importer) and manages them without touching their behavior.wp detachleaves a.corepanel-wp-ignoremarker so the site stays unmanaged. - One-time login links —
wp loginprints a single-use admin URL that expires in 60 seconds. The token is minted inside WordPress and only its hash is stored; no passwords are involved. - Safe updates —
wp update --coreautomatically snapshots the site (files + database) first;wp restore-point rollbackbrings it back. - Reversible hardening — file-editor lockdown, XML-RPC blocking, version hiding,
user-enumeration blocking, and application-password disabling, each individually
switchable (
on= protection enabled). - Integrity —
wp integrityverifies core files against wordpress.org checksums;--reinstallreplaces core files in place (keepingwp-contentandwp-config.php), the standard malware-cleanup step. - Maintenance mode — a server-level 503 page served by the web server itself, so it keeps working even when WordPress is broken mid-upgrade.
Action verbs for wp plugin / wp theme: install (--version, --activate),
activate, deactivate (plugins only), delete, autoupdate-on, autoupdate-off.
Hardening keys for --set: file-editor, xmlrpc, version-hiding,
user-enumeration, app-passwords.
# Install and open the dashboardcorepanel wp install 12 --domain example.com --title "My Blog" --admin-user admin --admin-email me@example.comcorepanel wp login 12 3
# Keep it updated, safelycorepanel wp components 12 3corepanel wp update 12 3 --core --all-pluginscorepanel wp restore-point list 12 3
# Harden and verifycorepanel wp hardening 12 3 --set xmlrpc=on --set user-enumeration=oncorepanel wp integrity 12 3
# Upgrade windowcorepanel wp maintenance 12 3 oncorepanel wp update 12 3 --all-themescorepanel wp maintenance 12 3 offMySQL databases
Section titled “MySQL databases”Create and manage an account’s databases and the users that connect to them.
corepanel database list <account> [--users]corepanel database create <account> <name> [--user <suffix>]corepanel database delete <account> <database> [--yes]corepanel database user list <account> [<database>]corepanel database user create <account> <database> <name> [--host <host>]corepanel database user password <account> <user>corepanel database user privileges <account> <user> --privileges <list>corepanel database user delete <account> <user> [--yes]A schema is always named <account_user>_<suffix>, and so is a user. That is why every
command takes the account first and the suffix after it: the prefix is not yours to
choose, and repeating it on the command line is noise. A database or a user can be named
in full (example_blog), by its suffix (blog) or by its numeric id — and an id that
belongs to a different account is refused here, not sent on.
database is also available as db.
A user is name and host
Section titled “A user is name and host”example_blog@localhost and example_blog@% are two different MySQL users with two
different passwords, and an account can have both. New users are created at localhost,
which is what a site or an application on this server connects as.
A bare name that matches two users is refused, listing both, rather than resolved to one:
$ corepanel database user password example blogError: "blog" names more than one user; a host is part of a user's identity,so say which: example_blog@localhost (on example_blog), example_blog@% (on example_blog)Dropping the wrong credential — or resetting a password that leaves the other user working, while you believe you changed it — is not something to decide by whichever row came back first.
Creating a database
Section titled “Creating a database”# The schema and its user in one command, password promptedcorepanel database create example blog --user blog
# Scripted, reading the password from standard inputprintf '%s' "$DB_PASS" | corepanel database create example shop --user shop --password-stdin--user is what a new database is almost always for; without it the schema exists and
nothing can connect to it yet. The password is resolved before the schema is created,
so a mistyped prompt does not leave a database behind that you have to delete before
retrying the same command. If the user fails once the schema exists, the schema is
kept and the command prints the retry — dropping a database because a user could not
be added is the more destructive answer to a retryable problem.
Creating one counts against the account’s database allowance, like any other.
Users and what they may do
Section titled “Users and what they may do”corepanel database user create example blog reports --privileges select,show\ viewcorepanel database user privileges example blog_reports --privileges selectcorepanel database user password example blog_reportsA grant is always on one database, never on the server, and ALL PRIVILEGES is as
wide as it goes. user privileges replaces the grant rather than adding to it: the
list you pass is what the user ends up with.
Accepted privileges: ALL PRIVILEGES, SELECT, INSERT, UPDATE, DELETE, CREATE,
DROP, ALTER, INDEX, CREATE TEMPORARY TABLES, LOCK TABLES, CREATE VIEW,
SHOW VIEW, EVENT, TRIGGER, REFERENCES. A mistyped one is refused with that list
in hand, rather than three services away as MariaDB’s “invalid privilege”.
--host opens a user to an address other than localhost, and the command says so when
you do. Whether it is reachable at all is a matter of the server’s bind address and its
firewall, both of which are the operator’s decision.
Dropping things
Section titled “Dropping things”corepanel database user delete example blog_reports # asks firstcorepanel database delete example blog --yes # for scriptsDropping a user leaves the database and its data alone; what goes is the credential
and its grant, so anything still connecting as it starts being refused. Dropping a
database takes its tables and its users with it, and nothing in CorePanel brings it
back — only the account’s backup can. Both ask for confirmation, and both refuse outright
when there is no terminal to ask on, so --yes is a deliberate act rather than a default.
An application’s database is protected
Section titled “An application’s database is protected”While an application holds a database, these commands refuse to break it:
corepanel database deleterefuses that schema, naming the application.corepanel database user deleterefuses that user, naming the application.corepanel database user passwordre-injects the new password into the application and restarts it, so the process is never left holding a dead credential.
corepanel app database detach is the way out: it drops the application’s user and keeps
the schema.
Applications
Section titled “Applications”Run a long-lived program for a hosting account and publish it under one of the account’s
domains — a Go API behind /api, a service on its own subdomain, anything that listens
for HTTP. Accounts are identified by their username, any domain they own, or their numeric account id (see Naming an account); an application by that
id plus its slug.
Each application gets a systemd unit of its own (corepanel-app-<user>.<app>.service)
running as the account user in a private network namespace, with memory, CPU and task
limits applied as cgroup properties. It never listens on a TCP port the rest of the
server can reach: CorePanel binds a unix socket for it
(/run/corepanel/apps/<user>/<app>.sock) and the web server proxies to that socket, so
two applications can never collide on a port.
The code is shipped as a .tar.gz the account uploads (over FTP, to
~/apps/<name>.tar.gz by default). CorePanel extracts it as a new release, starts it and
checks it answers before publishing it; a release that does not answer is rolled back
automatically and the previous one keeps serving. Three releases are kept.
corepanel app list [<account>]corepanel app show <account> <name>corepanel app create <account> <name> --domain <fqdn> --port <n> [flags] -- <program> [args...]corepanel app update <account> <name> [flags] [-- <program> [args...]]corepanel app deploy <account> <name> [--artifact <path>]corepanel app rollback <account> <name> [--to <release>]corepanel app start|stop|restart <account> <name>corepanel app releases <account> <name>corepanel app logs <account> <name> [-n 200] [--follow]corepanel app env list|set|unset <account> <name> [KEY=VALUE|KEY] [--secret]corepanel app database show|create|attach|detach <account> <name> [<database>]corepanel app webhook show|rotate|revoke <account> <name>corepanel app delete <account> <name> [--purge-data]
corepanel app runtime statuscorepanel app runtime prepare [--yes]Applications belong to a hosting account, identified by their username, any domain they own, or their numeric account id (see Naming an account) — list
accounts with corepanel account list to find it. Every command
below takes it as <account>, followed by the application’s name.
Creating an application
Section titled “Creating an application”The start command goes after a -- separator, as a program relative to the release
root followed by its arguments:
corepanel app create <account> api --domain example.com --path /api --port 8080 \ --health /healthz --mem 512 --cpu 100 -- server --addr :8080create reserves the name and derives the unit and the socket from it, but it does
not publish the application yet — the route is added by the first deploy that
answers. Pointing the site at a socket nothing is listening on would 502 the path, and
for an application mounted at / that is the whole site.
| Flag | Description |
|---|---|
--domain <fqdn> | Domain or subdomain of the account to publish under (required). A domain alias is refused: it has no site file of its own |
--path </sub> | Path under the site (default /); a sub-path mount gives the application its own root |
--port <n> | Port the application listens on inside its network namespace, 1024-65535 (required) |
--health </healthz> | Path requested after a deploy to decide whether the release is serving; without it the check is a plain connection to the socket |
--link <dir> | Release-relative directory symlinked into the data volume, for applications that write inside their own tree; repeatable |
--mem <mb> | Memory ceiling in MB (default 256) |
--cpu <pct> | CPU quota as a percentage; 100 is one whole core (default 50) |
--tasks <n> | Maximum number of tasks (default 64) |
--network outbound|none | Outbound access, outbound by default. none leaves the application with loopback only — no internet, no DNS — and saves the network backend’s ~25 MB. Neither value exposes services on the server’s own 127.0.0.1, and neither publishes the application’s port |
--runtime go | Application runtime; only go is supported in this release |
update takes the same flags and applies them to the release already running. Omitted
fields are left as they are, the previous settings are restored if the application stops
serving under the new ones, and an application that was stopped stays stopped.
--clear-links removes every linked directory.
Deploying and rolling back
Section titled “Deploying and rolling back”# Deploy the conventional ~/apps/api.tar.gzcorepanel app deploy <account> api
# Or an artifact uploaded somewhere else in the account homecorepanel app deploy <account> api --artifact uploads/api-2026-07-27.tar.gz
# Go backcorepanel app releases <account> apicorepanel app rollback <account> api --to 20260727T101500ZThe artifact must live inside the account’s home — it is extracted as root, so a path
outside the home is refused. A release that does not answer is rolled back to the
previous one, and deploy exits non-zero printing the reason from the application’s
journal.
Looking at what the machine says
Section titled “Looking at what the machine says”corepanel app show <account> apicorepanel app logs <account> api -n 500corepanel app logs <account> api --followshow prints the specification alongside the live systemd status — active state, main
pid, last exit, restarts and whether the socket exists. What CorePanel recorded and what
systemd answers are different questions: an application the panel believes is running can
be crash-looping, which is exactly the case worth diagnosing.
It also prints Health, which is a third question again: the
periodic check asks every running
application whether its endpoint still answers, and this is its last verdict with the time
behind it. not checked means the application is stopped or has never been deployed.
logs prints a snapshot of the journal, which holds both the application’s own output and
the gateway’s. With --follow (-f) it keeps printing what arrives until you interrupt
it, the same way journalctl -f does — the line count applies to the first screenful, and
from there on it only shows what is new.
Environment and secrets
Section titled “Environment and secrets”corepanel app env set <account> api LOG_LEVEL=debugcorepanel app env set <account> api API_TOKEN=s3cr3t --secretcorepanel app env set <account> api NEXT_FLAG=on --no-restartcorepanel app env list <account> apicorepanel app env reveal <account> api API_TOKENcorepanel app env import <account> api ./.envcorepanel app env unset <account> api LOG_LEVELThe variables live in a file systemd reads as root before dropping privileges, so the application receives them while the account cannot read the file that holds them.
A --secret value is hidden from env list; env reveal prints it and records that it
was asked for in the server log. Naming a variable reveals that one, leaving the name out
reveals the whole environment as a single audit entry. No password is asked for: whoever
can run this can already replace the value and deploy code that prints it.
--secret is only sent when you actually pass it, so changing a secret’s value does not
quietly turn it into a plain one. Going the other way — a secret becoming plain text — is
only accepted in a call that carries the value, because a demotion without it would list a
variable in the clear that the caller never held.
| Flag | Command | Effect |
|---|---|---|
--secret | env set | Hide the value from listings |
--no-restart | env set, env import | Store the value without restarting a running application |
--replace | env import | Also remove the variables the file does not mention |
--plain | env import | Do not mark credential-looking names as secret |
env import reads a .env — comments, blank lines, an optional export prefix and
quoted values — and applies the whole file in one restart, whatever its length.
Setting the variables one at a time restarts a running application per line. Use - as
the filename to read standard input.
Names that look like credentials (ending in KEY, TOKEN, SECRET, PASSWORD, DSN,
plus DATABASE_URL) are marked secret on import unless --plain is given.
Setting or removing a variable restarts a running application unless --no-restart says
otherwise. A value can be up to 16 KB, the environment as a whole up to 256 KB, and no
value may contain a line break.
Giving an application a database
Section titled “Giving an application a database”corepanel app database create <account> api # schema + user + passwordcorepanel app database show <account> apicorepanel app database attach <account> api blog # one the account already hascorepanel app database detach <account> api # keeps the datacreate provisions the schema, a user dedicated to the application and a password,
writes the connection into the environment and restarts the process — one command, and
no credential passes through anybody’s clipboard. The application reads it from:
| Variable | What it holds |
|---|---|
CP_DB_NAME | The schema, <account_user>_<suffix> |
CP_DB_USER | The user the application connects as, always @localhost |
CP_DB_PASSWORD | The password, stored as a secret |
CP_DB_SOCKET | The MariaDB unix socket to connect to |
CP_DB_HOST, CP_DB_PORT | Present and empty: there is no TCP route (see below) |
DATABASE_URL | The whole thing as a DSN, for a library that takes one |
These are managed variables: app env set refuses to write one, and the panel shows
them read-only. Detaching is what removes them. DATABASE_URL is the exception — set it
yourself and your value wins, for a driver whose DSN dialect is not the one CorePanel
writes.
--name <suffix> chooses the schema name; the full name is always
<account_user>_<suffix>. Without it the suffix comes from the application’s name, with
a number appended when that one is taken. A suffix you pass may hold letters, digits and
_ only, and <account_user>_<suffix> has to fit in the 32 characters MySQL allows a
user name.
attach links a schema the account already owns — named in full (example_blog), by its
suffix (blog) or by its numeric id — and still creates a new user for the
application. It cannot do anything else: CorePanel does not store MySQL passwords, so
there is no existing credential to hand over, and resetting one would break every
wp-config.php that shares it. The data is untouched and no other grant is changed.
detach drops the user CorePanel created and removes the variables, restarting a running
application. The database and everything in it are kept.
While an application holds a database, the database commands protect it:
corepanel database deleterefuses to drop that schema, naming the application.corepanel database user deleterefuses to drop that user, naming the application.corepanel database user passwordre-injects the new password and restarts the application, so a rotation never leaves the process holding a dead credential.- Deleting the application drops its user and never the schema,
--purge-dataor not. The files under the application’s directory are its own; the database is the customer’s.
Deploying from CI/CD
Section titled “Deploying from CI/CD”A build pipeline can deploy without a person in the loop:
corepanel app webhook rotate <account> api # issues the token and prints both URLscorepanel app webhook show <account> api # prints them againcorepanel app webhook revoke <account> api # disables itThe token works two ways. The pipeline can POST the artifact itself, deploying it in the
same call and needing no FTP account:
curl -fsS -X POST https://panel.example.com/api/apps/upload \ -H 'Authorization: Bearer cpd_...' --data-binary @api.tar.gzOr it can upload to ~/apps/<name>.tar.gz as the account and POST to the deploy URL,
which is what a GitHub or GitLab webhook — a caller that can be given nothing but an
address — has to do.
Either way, what runs is the same deploy as corepanel app deploy, automatic rollback
included. Two properties decide how the pipeline is written: the call answers 202 as
soon as the deploy starts, not when it finishes, and a second call while one is running
is refused with 409.
Full guide, with GitHub Actions and GitLab CI examples, signatures and every status code: Deploying from CI/CD.
Can this server run containers?
Section titled “Can this server run containers?”Applications run natively on every supported release, and nothing on this page needs preparing. Running them in containers — a Podman backend that is still in development — additionally needs podman and a kernel booted with the unified cgroup hierarchy:
corepanel app runtime statusIt reports podman’s version, the cgroup hierarchy the kernel booted with, the controllers delegated to per-user slices and how SELinux labels the container image store, and ends in one of three verdicts: ready, reboot pending or unavailable, each with the step that matches it.
On AlmaLinux 9 and 10 a stock server is ready. On AlmaLinux 8 the kernel boots with the older cgroup hierarchy, and that is not a detail: there, podman accepts a container’s memory and CPU limits, ignores them, and starts the container anyway. CorePanel would rather refuse than run a customer’s application on a limit that does not exist, so the backend is not offered until the server can enforce it.
corepanel app runtime prepareprepare installs the systemd delegation drop-in and adds
systemd.unified_cgroup_hierarchy=1 to every boot entry. It does not reboot the
server — the argument only takes effect on the next boot, and when to take a hosting
machine down is your decision, not the panel’s. Run it, then reboot when it suits you;
status says reboot pending in the meantime. It asks for confirmation unless --yes is
given, and running it twice changes nothing.
The same reading and the same button are on Settings → Container runtime in the panel.
Removing an application
Section titled “Removing an application”corepanel app stop <account> apicorepanel app delete <account> apidelete unpublishes the application, stops it, and removes its unit, program tree and
socket. Its data directory is kept unless --purge-data is given: it is the one thing
a redeploy cannot recreate. Deleting a domain that still publishes an application is
refused with a message naming them; corepanel domain delete --force removes them in
cascade.
corepanel firewall
Section titled “corepanel firewall”Manages the host firewall. CorePanel drives firewalld directly: a corepanel zone
that drops inbound traffic by default, the ports of the installed stack opened
explicitly, and two address lists — blocked addresses and a bastion allowlist. The
concepts are explained in Host Firewall; this is the
command reference.
corepanel firewall statuscorepanel firewall seedcorepanel firewall enable [--yes]corepanel firewall disablecorepanel firewall factory-reset [--yes] [--stop-firewall]
corepanel firewall ports listcorepanel firewall ports open <port|from-to> [--udp] [--source CIDR]... [--comment C]corepanel firewall ports close <port|from-to> [--udp] [--force]corepanel firewall ports edit <port|from-to> [--udp] (--source CIDR... | --any) [--comment C]
corepanel firewall deny <ip|cidr> [--ttl 24h] [--comment C]corepanel firewall deny list [--offset N] [--limit N]corepanel firewall deny remove <ip|cidr>corepanel firewall deny clear [--yes]
corepanel firewall allow <ip|cidr> [--comment C]corepanel firewall allow list [--offset N] [--limit N]corepanel firewall allow remove <ip|cidr>
corepanel firewall autoblockcorepanel firewall autoblock set [--enable|--disable] [--sources L] [--ttl D] [--max-ttl D] [--cooldown D]Four rules explain the whole group:
- Managing the firewall requires Pro or Business;
statusanddisablenever do. A server whose licence lapsed while the firewall was enforcing must always be able to see that state and end it. - This group is the emergency exit. Locked out of the panel? SSH in and run
corepanel firewall disable: firewalld stops immediately and nothing is destroyed. - The seed cannot cut SSH. The port sshd listens on is read from the running daemon,
not guessed from a config file, so enabling the firewall over an SSH session does not
end it. Closing that port, or 443, is refused unless
--forceis passed. - Expiry belongs to CorePanel, not the kernel. Blocks are stored flat and released by a sweeper that runs once a minute and once at startup, so a block survives a reboot and still expires on time.
corepanel firewall status
Section titled “corepanel firewall status”Reports whether firewalld is installed, running and managed by CorePanel, the default zone and its inbound policy, the backend, the rule and list counts, and anything that would stop management from working. When the server is unprotected, or firewalld is running unmanaged, the output says which command fixes it.
corepanel firewall seed
Section titled “corepanel firewall seed”Prints the ports enable would open — port, protocol and the service each one serves —
without opening them. Worth reading before adopting a server you reach over the network.
corepanel firewall enable
Section titled “corepanel firewall enable”Adopts the firewall: builds the zone, creates the address lists, applies the seed, and
only then makes the zone default, so the step that starts dropping traffic happens last.
Prints the seed and asks for confirmation; a non-interactive run must pass --yes.
Running it again on an adopted server repairs anything missing.
Adoption is refused, with an explanation, when firewalld is not installed, when it is on the deprecated iptables backend, or when the host has direct rules.
corepanel firewall disable
Section titled “corepanel firewall disable”Stops firewalld. The host accepts everything again immediately; every rule stays in
firewalld’s permanent configuration and enable puts it back. Asks nothing, and no
edition may refuse it.
corepanel firewall factory-reset
Section titled “corepanel firewall factory-reset”Removes CorePanel’s zone, its rules and both address lists, and hands the default zone
back to what it was before adoption. Blocked and allowed addresses are destroyed with the
lists. This is not how the firewall is turned off — disable is, and it is reversible.
--stop-firewall also stops firewalld afterwards.
corepanel firewall ports
Section titled “corepanel firewall ports”A port is a single number (8080) or a range (40000-50000), TCP unless --udp is
passed. --source restricts it to source networks (repeatable, or comma-separated); with
none, the port is open to everyone.
edit replaces a rule’s sources with the new ones in place before the old ones are
withdrawn, so an address that keeps access never loses it for an instant. The port and
protocol are the rule’s identity and cannot change. Dropping every restriction is
explicit (--any), and omitting --comment keeps the comment the rule already had.
corepanel firewall deny
Section titled “corepanel firewall deny”Blocks an address or network, permanently or until a deadline (--ttl, from 60s to
168h). deny list shows the time left, the provenance and the comment for each entry;
an address the kernel holds that CorePanel has no record for is listed as untracked
rather than hidden. Unblocking something that is not blocked succeeds, so deny remove
is safe in a script that does not check first.
corepanel firewall allow
Section titled “corepanel firewall allow”The bastion allowlist. An allowed address is accepted before every other rule, so it reaches ports that are closed to everyone else and cannot be blocked. For the office or the jump host access must never be lost from — and nothing else.
corepanel firewall autoblock
Section titled “corepanel firewall autoblock”The automatic brute-force policy. CorePanel’s own detectors report the addresses that
cross their threshold, and this decides what happens to them: the first ban lasts --ttl
(30m by default), every repeat offence doubles it, and --max-ttl (7 days) is where the
doubling stops. --cooldown (10m) is how long an address is left alone between
escalations, so the counter measures how often an attacker comes back rather than how
fast they knock.
--sources replaces the whole list, so pass every source that may block; an empty value
means none. set changes only the flags given.
Three addresses can never be banned, whatever the policy says: this server’s own, the loopback, and anything on the bastion allowlist. An address an administrator blocked by hand is never escalated either.
Automatic blocking requires Pro or Business. On Personal the detectors keep reporting, so the attack stays visible, and nothing is blocked; reading the policy works on every edition.
Examples
Section titled “Examples”# What is the firewall doing right now?corepanel firewall status
# See exactly what adopting this server would open, then do itcorepanel firewall seedcorepanel firewall enable
# Provisioning: adopt without a promptcorepanel firewall enable --yes
# Locked out of the panel — SSH in and stop filteringcorepanel firewall disable
# Open the FTP passive range, and a staging port only to the officecorepanel firewall ports open 40000-50000corepanel firewall ports open 8443 --source 203.0.113.0/24 --comment "staging"
# The office moved: replace the sources without closing the portcorepanel firewall ports edit 8443 --source 198.51.100.0/24
# Block an address for a day, and a whole network for goodcorepanel firewall deny 203.0.113.7 --ttl 24h --comment "ssh brute force"corepanel firewall deny 198.51.100.0/24 --comment "scanner"
# What is blocked, and for how much longer?corepanel firewall deny list
# Never lock out the jump hostcorepanel firewall allow 203.0.113.10 --comment "office"
# What gets banned automatically, and for how long?corepanel firewall autoblock
# Ban harder and for longer, but only on panel loginscorepanel firewall autoblock set --enable --sources panel --ttl 1h --max-ttl 48h
# Keep seeing the attacks, stop acting on themcorepanel firewall autoblock set --disablecorepanel auth
Section titled “corepanel auth”Reads the authentication activity of the server’s mail and FTP services.
corepanel-auth brokers every credential check on the box — IMAP, POP3, SMTP, FTP, plus
the certificate and mail-map operations around them — and records each one with the
account, the source address, the country it resolved to and, when it failed, why. The
store itself is described in Mail System.
corepanel auth events [--service S] [--user U] [--ip IP] [--country CC] [--result R] [--reason R] [--since D] [--limit N]corepanel auth attackers [--since D] [--limit N]corepanel auth stats [--since D]corepanel auth user <account> [--since D] [--limit N]Available on every edition. Seeing an attack is never gated; blocking one
automatically is what requires Pro or Business (corepanel firewall autoblock).
Three things are worth knowing before reading the output:
- No password material is ever recorded — not the value, not a hash, not a length. That is a rule of the store, not a display choice, so there is nothing of the kind to print.
- Recording an event never delays a login. When the writer falls behind, the store
drops the event and counts it.
corepanel auth statsprints that count: when it is non-zero, every number beside it is a floor rather than a total. --sincetakes a duration (30m,24h,168h) and defaults to 24 hours.
corepanel auth events
Section titled “corepanel auth events”Lists attempts newest-first. --service accepts ftp, imap, pop3, smtp, extcert
or sync, and the usual aliases (imaps, submission) resolve to the same rows. When
the filter matches more than --limit, the footer says so rather than letting a
truncated list read as the whole story.
corepanel auth attackers
Section titled “corepanel auth attackers”The source addresses with the most failed logins in the window, worst first. The ACCOUNTS column is the one to read: one account failing repeatedly from a single address is usually a stale password on somebody’s phone, while the same address failing against many accounts is a dictionary run. That distinction is what decides whether an address is ever reported to the firewall.
corepanel auth stats
Section titled “corepanel auth stats”The window against the one before it, followed by the health of the audit trail itself: how many events are stored, the retention window, whether the row cap was reached, how many events were dropped, and whether country lookup is working.
A third block reports the brute-force detector: how many addresses it is watching, how many responses it delayed, how many it reported to the firewall — and how many reports it deliberately withheld. Those refusals are listed separately rather than folded into a total, because “nothing was blocked” has several very different meanings:
| Line | What it means |
|---|---|
Addresses with a recent sign-in | Somebody authenticated successfully from them in the last seven days, so they cannot be blocked automatically at all |
Withheld — address had signed in | A report was ready and dropped for exactly that reason |
Withheld — events did not confirm | The running counters proposed a block the stored events did not support |
Slow campaigns found | Addresses working through accounts that do not exist here over the last 24 hours, however slowly — the tier that catches what real attacks look like. Printed even at zero, because “looked and found nothing” is a different answer from “never looked”, and normally higher than the number reported: the gap is the immunity and the cooldowns |
Delays skipped | The concurrency ceiling was reached and a response was answered immediately instead of being held |
Automatic blocking is paused | Core answered that this server will not block — the edition does not include it, or the operator turned it off. Detection and the delays carry on |
The thresholds in force are printed with them, so the numbers can be read without consulting this page. See Access protection for what each one protects.
corepanel auth user
Section titled “corepanel auth user”One account’s record. The header spans its whole retained history — last success, last failure, how many distinct addresses it was used from, which countries — while the list below covers only the requested window, so an empty list means “nothing in the last day”, not “never logged in”.
Examples
Section titled “Examples”# Everything that failed in the last hourcorepanel auth events --result fail --since 1h
# Who is hammering the mail server this weekcorepanel auth attackers --since 168h
# A customer says their mail client stopped workingcorepanel auth user ana@example.com --since 48h
# Only FTP, only from one countrycorepanel auth events --service ftp --country CN
# Is the audit trail keeping up?corepanel auth statscorepanel api-token
Section titled “corepanel api-token”Issues and withdraws the machine credentials that let a program use CorePanel’s JSON-RPC API — a WHMCS install, a provisioning script, a monitoring probe. See API Tokens for what a token can reach and how to use one.
corepanel api-token create <name> --scope S [--scope S ...] [--expires DATE]corepanel api-token list [--all]corepanel api-token revoke <token-id>Issuing a token requires Pro or Business. Listing and revoking work on every edition — a lapsed licence must never stand between an operator and a credential they want to withdraw.
corepanel api-token create
Section titled “corepanel api-token create”Prints the token once and never again. --scope is repeatable and at least one is
required:
| Scope | What it allows |
|---|---|
readonly | List and read. Safe for monitoring. |
provisioning | Create accounts, suspend and unsuspend them, change packages and passwords. Implies readonly. |
terminate | Delete accounts. Never implied by anything. |
--expires takes YYYY-MM-DD or a full RFC3339 timestamp; a bare date means the end
of that day. Omitted, the token does not expire.
corepanel api-token list
Section titled “corepanel api-token list”Shows active tokens with their prefix, scopes, status and last use. Revoked ones are
hidden unless --all is given.
corepanel api-token revoke
Section titled “corepanel api-token revoke”Withdraws a token immediately. The row is kept rather than deleted, so a token named in an old audit line can still be resolved. Revoking an already-revoked token succeeds.
Examples
Section titled “Examples”# A token for WHMCS that provisions and suspends, but cannot deletecorepanel api-token create "WHMCS production" --scope provisioning
# Add cancellations as a second, separate credentialcorepanel api-token create "WHMCS terminations" --scope provisioning --scope terminate
# A monitoring credential that expires at the end of the yearcorepanel api-token create "status page" --scope readonly --expires 2026-12-31
# What can reach this server right now?corepanel api-token list
# The laptop was stolencorepanel api-token revoke 4corepanel agent-token
Section titled “corepanel agent-token”Issues and withdraws the credentials an MCP client presents to read this server on your behalf — your Claude Code, your Cursor, whatever you already work in. See MCP for AI agents for what an agent can ask and what it cannot do.
corepanel agent-token create <name> [--operate] [--expires DATE | --never-expires] [--allow ADDR]...corepanel agent-token list [--all]corepanel agent-token sources <token-id> <address>... | --clearcorepanel agent-token activity [--since D] [--token ID] [--tool NAME] [--action A] [--outcome O] [--ip ADDR] [--before ID] [--limit N] [--details | --json]corepanel agent-token revoke <token-id>Available on every edition, Personal included. It is not an API token, and the differences are the whole design:
- Read-only unless
--operateis asked for. It answers questions about this one server and carries none of the rights that create, suspend or delete an account, at any setting.--operateadds the right to change what already exists, through a two-step confirmation. - It expires in 90 days unless you say otherwise. An API token lives on a server you control; this one lives in a configuration file on a workstation, in a dotfiles repository, in a backup.
- It can be pinned to the addresses it may be used from.
--allow, off unless you ask for it. It is the only measure that stops a credential somebody else got hold of; expiry, revocation and the activity log all deal with a leak once you know about it. - The two are not interchangeable. An agent token presented to the API is refused, and an API token presented to the MCP endpoint is refused.
Minting the first one also turns the MCP endpoint on: until a usable agent token
exists, /mcp answers as though the path did not exist. Revoking the last one turns it
off again.
corepanel agent-token create
Section titled “corepanel agent-token create”Prints the credential once. Only its hash is stored, so nothing can print it again.
--expires takes YYYY-MM-DD or a full RFC3339 timestamp; a bare date means the end
of that day. Omitted, the token expires in 90 days — omitting it does not mean “never”.
--never-expires does, and has to be asked for by name.
--operate grants the write right. What it buys is the ability to change what
already exists — restarting an application, backing up an account, flushing a site’s
page cache — and every such change takes two calls: the agent’s first call returns a plan and changes nothing, and
the change happens only on a second call carrying the confirmation that plan issued,
which lasts 60 seconds and works once. It never reaches an account’s lifecycle, and it
deletes nothing. See Letting it change things.
--allow pins the credential to an address or a CIDR range, IPv4 or IPv6, and is
repeatable. A token presented from anywhere else is refused with the same “invalid
token” a wrong secret produces — the response must not tell a caller which check it
failed, or the failure confirms that the credential it holds is real. Only the log, and
activity --action auth_refused, say what happened.
Omitted, the token works from wherever it is copied to. That is the default because a
list breaks the day you work from somewhere new, and the answer to that is
agent-token sources, not turning the list off.
If you use corepanel mcp on the server itself, put 127.0.0.1 on
the list: the bridge reaches the endpoint over loopback and sends no address of its own.
Loopback is never implicit — every local user on the machine can reach that address.
A token cannot be widened afterwards. Read-only is what you get by not choosing, and changing your mind means revoking and minting again.
The access: line reports the right the token actually got, not the one you asked
for. --operate against a corepanel-core that predates the write right succeeds and
mints a read-only token — an unknown parameter is ignored, not refused — so the command
says so in as many words rather than letting you find out when a change is turned away.
The sources: line is the same shape for the same reason: --allow against such a core
mints a token pinned to nothing, and the command says so.
corepanel agent-token sources
Section titled “corepanel agent-token sources”Replaces the list of addresses a token may be presented from. The list is a replacement, not an addition: whatever you pass is what the token ends up with.
Each entry is an address or a CIDR range. A bare address is stored as a single-host
range, so 192.0.2.7 and 192.0.2.7/32 are the same rule — and what the command prints
back is what the server stored, which is what it will compare against, not what you
typed.
A range with host bits set is refused rather than widened. 10.0.0.5/8 is a typo for one
address far more often than it is a request for sixteen million, and quietly storing
10.0.0.0/8 would grant them; the error names both readings.
--clear removes the restriction and has to be asked for by name. A command that read
“no addresses given” as “allow from anywhere” would unpin a credential on a mistyped
invocation, and nothing would look wrong afterwards — the token keeps working, from
everywhere.
What this command cannot change is what the token may do. That is fixed when the token is minted; widening it is still a revoke and a new token.
| Flag | Meaning |
|---|---|
--clear | Remove the restriction, making the token usable from any address |
corepanel agent-token list
Section titled “corepanel agent-token list”Shows active tokens with their prefix, access, status, expiry, last use and the address
it was last used from. API tokens are never in this list. Revoked ones are hidden unless
--all is given.
The Access column answers “which of these could have changed my server”: read-only
or read + operate. What one of them actually changed is activity --action tool_apply.
The Sources column says where each credential may be used from. any is a token
that works from wherever it is copied to; anything else is one that does not.
The address column is the other one worth reading: a credential in use from an address you do
not recognise. It is half the question — a credential somebody is guessing at never
reaches this table, because it never resolves to a row. That half is activity.
corepanel agent-token activity
Section titled “corepanel agent-token activity”What the /mcp endpoint was asked for, newest first, read from the machine itself. The
panel shows the same trail at Server → Agent tokens → Activity; this is the answer
for a terminal, which on a server is usually where you are.
| Action | What it means |
|---|---|
tool_call | A tool was called. --details shows the arguments it was given |
tool_apply | A change was authorised and carried out on this server |
initialize | An MCP client completed the handshake — somebody attached one |
tools_list | A client asked what this server offers |
auth_refused | A token was presented and did not resolve |
tool_apply is the short answer to “did my agent change anything”. A write tool takes
two calls; the first returns a plan and touches nothing, and is recorded as an ordinary
tool_call. Only the second is a tool_apply, so --action tool_apply is every change
ever made through this endpoint and nothing else. A failed one is still a tool_apply —
the outcome says whether it landed.
auth_refused is what the command is worth running for. A refusal names nobody: the
credential did not resolve, so there is no token to attribute it to, and the string
presented is never stored — the address is the whole of what is knowable, and the TOKEN
column says (refused) rather than going blank. A request that carried no credential at
all is deliberately not recorded; those are port scans and stale bookmarks.
| Flag | What it does |
|---|---|
--since | A duration: 30m, 24h, 168h. Omitted, everything the store still holds |
--token | One agent token, by the id agent-token list shows |
--tool | One tool, e.g. read_logs |
--action | tool_call, tool_apply, initialize, tools_list or auth_refused |
--outcome | ok, tool_error, refused or error |
--ip | One source address |
--before | Only events older than this id — how you page backwards |
--limit | Rows to show, default 25, maximum 1000 |
--details | One block per event, with the arguments a tool was called with |
--json | The page as it came off the wire, for a script |
Paging is by id rather than by offset, because the trail only grows at the top and an
offset would repeat rows. When older events match, the command prints the id to pass to
--before.
Whatever the store dropped is printed under the list without being asked for. The /mcp
endpoint never waits for the audit trail, so a burst of traffic — or a core that was
unreachable — costs events rather than latency, and a trail that is quietly losing rows
looks exactly like a quiet one.
Two answers are not faults, and the command says so rather than reporting a failure: a
corepanel-core that predates the trail, and one whose store did not open. Both leave
your tokens working; what is missing is the record of what they did.
corepanel agent-token revoke
Section titled “corepanel agent-token revoke”Withdraws a token immediately. The id of an API token is not found here — those are
revoked with corepanel api-token revoke.
Examples
Section titled “Examples”# A token for the laptop you work from, expiring in 90 dayscorepanel agent-token create "laptop"
# One that may also change things, through the two-step confirmationcorepanel agent-token create "deploy box" --operate
# One that never expires, for a workstation that never leaves the officecorepanel agent-token create "office desktop" --never-expires
# One that only works from the office, and from this server itselfcorepanel agent-token create "office desktop" --allow 192.0.2.0/24 --allow 127.0.0.1
# Change where an existing token may be used fromcorepanel agent-token sources 3 192.0.2.0/24 198.51.100.0/24
# Let it be used from anywhere againcorepanel agent-token sources 3 --clear
# Which agents can read this server, and from where?corepanel agent-token list
# What has been asked of the endpoint lately?corepanel agent-token activity
# Has anybody been trying credentials that are not mine?corepanel agent-token activity --action auth_refused --since 168h
# Everything an agent has ever CHANGED on this servercorepanel agent-token activity --action tool_apply --since 720h --details
# What did the laptop's token do today, arguments included?corepanel agent-token activity --token 101 --since 24h --details
# That address is not minecorepanel agent-token revoke 7corepanel mcp
Section titled “corepanel mcp”Speaks MCP on stdin and stdout, for a client running on this same server.
corepanel mcp [--token-file PATH] [--url URL]It is a pipe to the endpoint the panel already serves at /mcp: every message is
forwarded, unchanged, to corepanel-api on the loopback address, and the answer is
written back. Nothing else changes — the same tools, the same two-step confirmation
before anything is altered, and the same audit trail, which
corepanel agent-token activity shows exactly as it would a call over HTTPS.
Use it when the client runs on the box itself and pointing it at
https://<panel>/mcp would mean that hostname resolving there and that certificate
being trusted by that client. This talks to http://127.0.0.1:16087/mcp: no hostname,
no certificate, no proxy. Some MCP clients also accept only a command, never a URL.
It needs an agent token and does not create one. Mint it with
corepanel agent-token create, then put it in COREPANEL_AGENT_TOKEN or in a file
named by --token-file. There is deliberately no --token flag: an argument is
visible in ps to every user on the server.
| Flag | Meaning |
|---|---|
--token-file | Read the token from this file instead of $COREPANEL_AGENT_TOKEN. It must contain the token and nothing else; a warning is printed if other users can read it |
--url | The endpoint to forward to (default http://127.0.0.1:16087/mcp) |
Nothing but protocol goes to stdout. Everything a human needs to read is on stderr, where an MCP client will not mistake it for a message.
Examples
Section titled “Examples”# Mint the credential oncecorepanel agent-token create "local agent"
# What a client's configuration looks like# {# "mcpServers": {# "corepanel": {# "command": "corepanel",# "args": ["mcp"],# "env": { "COREPANEL_AGENT_TOKEN": "cpa_..." }# }# }# }
# Or keep the credential in a root-only file instead of the configurationcorepanel mcp --token-file /root/.corepanel/agent-tokenSee MCP for the whole picture.
corepanel whmcompat
Section titled “corepanel whmcompat”Controls whether this server answers the WHM API on :2087 — the compatibility layer that lets a WHMCS which has been provisioning this machine through cPanel keep working after a transform, with nothing changed on the WHMCS side. See WHMCS for what it is and when you want it.
corepanel whmcompat status [--json]corepanel whmcompat enable [--json]corepanel whmcompat disable [--json]corepanel whmcompat sso enablecorepanel whmcompat sso disablecorepanel whmcompat redirects enablecorepanel whmcompat redirects disablecorepanel whmcompat import-credential [--user U] [--kind token|accesshash] [--label L]corepanel whmcompat credentialscorepanel whmcompat revoke-credential [--user U] (--digest PREFIX | --all)corepanel whmcompat selftestcorepanel whmcompat status
Section titled “corepanel whmcompat status”Answers two questions together, because either alone is misleading: whether the server is configured to serve the WHM API, and whether a daemon is actually listening. A switch that is on with no package installed serves nothing; an installed package with the switch off is running and holding no port. Both are normal, and both look like a fault if you only see half of it.
The exit status is 0 in every one of those states — not serving the WHM API is a
supported configuration, not an error. Use --json to branch on it.
corepanel whmcompat enable / disable
Section titled “corepanel whmcompat enable / disable”Open and close :2087. Both take effect within half a minute and neither needs a restart, which matters most in the OFF direction: closing the port must not wait for somebody to remember there is a service to bounce. Requests in flight are allowed to finish.
They also open and close the port in the host firewall, and print which zone they touched:
WHM API compatibility: enabledPasswordless sign-in: disabledDaemon: listening on [::]:2087Credentials held: 1Firewall: port 2087/tcp opened in the "public" firewall zoneThe zone is whichever one is in force — the corepanel zone on a server that adopted
the host firewall, the distribution’s default zone on one
that did not,
which is where the installer already seeds the stack’s ports. On a server with firewalld
stopped or not installed, nothing is filtering the port and the line says so instead of
claiming a change.
corepanel whmcompat sso enable / disable
Section titled “corepanel whmcompat sso enable / disable”A second switch, over the one WHM command that signs a customer into their panel
without their password — create_user_session, the “Log in to cPanel” button in a WHMCS
client area. Off by default, and off on servers that were already serving :2087 before
this switch existed.
It is separate from the layer’s own switch because it is a different kind of thing to grant. Everything else the layer does is loud: a password change stops the customer’s own password working, a suspension takes their site down. A minted session is silent — it is the customer’s own session, which is what makes it the right thing for that button and what makes it worth deciding on. Most billing setups only provision and suspend and never need it.
Both directions take effect within half a minute, without a restart. Turning it on requires Business; turning it off never does.
This does not affect the native CorePanel module for WHMCS. That module reaches the same feature through an API token this server issued, over the port the panel already owns, and its login button works whatever this switch says.
corepanel whmcompat redirects enable / disable
Section titled “corepanel whmcompat redirects enable / disable”A third switch, over the legacy cPanel ports a browser lands on: 2082, 2083, 2086, 2095 and 2096. Off by default, including on servers that already serve the WHM API.
This half of the compatibility layer is not for a billing system at all. On a cPanel
server, :2083 is the URL every customer has bookmarked and :2096 is how they reach
their webmail. After a transform, those are a refused connection — no page, no explanation
— for everybody at once, and it is the most visible breakage of the whole migration.
With it on, this server answers those ports with a redirect and nothing else:
| Port | TLS | A browser is sent to |
|---|---|---|
| 2082 | no | the panel |
| 2083 | yes | the panel |
| 2086 | no | the panel |
| 2095 | no | webmail.<domain>, for the domain it asked for |
| 2096 | yes | webmail.<domain>, for the domain it asked for |
Available on every edition, unlike the two switches above — that is why it is a switch of its own. Enabling the WHM API requires Business, so a transformed Personal or Pro server could otherwise never reach a redirect that costs nothing and grants nothing, while its customers’ bookmarks are just as broken.
What these ports will not do:
- They never redirect an API client. A
/json-api/path, or any request carrying anAuthorizationheader, gets a 404 — or, on:2086alone (cPanel’s cleartext API port), a parseable refusal naminghttps://<hostname>:2087. Redirecting a provisioning call to a login page is a failure that reports success. - They never redirect to an address from the request. The webmail target is built from
the
Hostheader only after the panel confirms this server serves that domain; anything else falls back to the panel. Nothing from the path or query string is ever used. - They never send a 301, and never HSTS.
The ports are opened and closed in the host firewall along with the switch.
status prints the switch in both states and, when it is on, names which of the five
ports are actually bound. Fewer than five means something else on this server holds the
rest.
corepanel whmcompat import-credential
Section titled “corepanel whmcompat import-credential”Teaches this server the credential the billing system already uses, read from stdin.
| Flag | Default | Meaning |
|---|---|---|
--user | root | The WHM user the credential authenticates as |
--kind | token | token (a WHM API token) or accesshash (the contents of /root/.accesshash) |
--label | — | A note for whoever revokes it later |
The secret is read from stdin and never from a flag — a credential in a flag is a
credential in the shell history and in every ps on the box. Importing the same
credential twice changes nothing.
corepanel whmcompat credentials
Section titled “corepanel whmcompat credentials”Lists what the server accepts: user, kind, label, import date, and the digest that identifies each credential. There are no secrets to print — the store keeps digests, which is what makes holding a hosting provider’s token safe.
The digest is what names a credential when revoking one. A label cannot: several credentials can carry the same note.
corepanel whmcompat revoke-credential
Section titled “corepanel whmcompat revoke-credential”Stops this server accepting a credential.
| Flag | Default | Meaning |
|---|---|---|
--user | root | The WHM user whose credential is being revoked |
--digest | — | Digest prefix from credentials; names exactly one credential |
--all | off | Revoke every credential this user holds |
--yes, -y | off | Do not ask for confirmation |
One of --digest or --all is required, and they cannot be combined. Revoking
everything has to be spelled out: an empty store accepts nothing, so --all is how a
server stops answering its billing system entirely — there is no way to ask for that by
leaving a flag out, and it asks for confirmation unless you pass --yes.
A revoke that matched nothing says so rather than reporting a revocation. And since only the digest was ever stored, a revoked credential comes back only by importing the secret again.
corepanel whmcompat selftest
Section titled “corepanel whmcompat selftest”Drives a real WHM API request against this server over loopback — the port, TLS, the credential header, the response envelope and the call into the panel, with nothing stubbed — using a one-shot credential the daemon mints for it.
It proves this side. Only WHMCS proves WHMCS: when it passes, press Test Connection in WHMCS → System Settings → Servers.
Examples
Section titled “Examples”# Is this server serving the WHM API, and is anything listening?corepanel whmcompat status
# After a transform: open the port the hosting provider's WHMCS is already pointed atcorepanel whmcompat enable
# Teach it the token WHMCS already holdscorepanel whmcompat import-credential --user root --kind token < token.txt
# The hosting provider still authenticates with the old access hashcat /root/.accesshash | corepanel whmcompat import-credential --kind accesshash
# What does this server accept, and since when?corepanel whmcompat credentials
# That one belonged to a WHMCS that is gonecorepanel whmcompat revoke-credential --digest a9e70ee8
# Prove our side end to end before touching WHMCScorepanel whmcompat selftest
# Close it againcorepanel whmcompat disablecorepanel php
Section titled “corepanel php”The PHP this server runs: which versions are installed, which can be, what extensions each one loads, and the per-request limits every account is held to. See How PHP Runs for the version model and PHP Isolation and Limits for what each limit does.
corepanel php versionscorepanel php install <version> [--yes]versions lists every PHP version on the server — how many accounts run each,
and how long PHP upstream still supports it — and underneath, the versions
available to install.
“End of life” means end of life upstream. A package repository may go on building a version PHP itself no longer fixes; the accounts on it stop receiving security fixes whatever the repository does.
install adds a version and brings it under CorePanel’s configuration, so it
gets the same per-account pools as every other version. Only versions versions
lists are accepted. Nothing is moved onto it: accounts change one at a time, with
corepanel account update <account> --php.
Which versions are offered depends on the operating system: 7.4 and 8.0 to 8.5 everywhere, plus 5.6 and 7.0 to 7.3 on EL8, the only release Remi still publishes those for. See PHP versions.
It runs a package transaction and can take several minutes; the package manager’s
output is printed when it finishes, including on failure. Installing an
end-of-life version asks for confirmation — allowed, since 7.4 is what most
cPanel migrations arrive on, but never silent. --yes skips the prompt and is
required where there is no terminal to ask on.
# What this server runs, and what it could runcorepanel php versions
# Add the version a cPanel migration needs, then move one account onto itcorepanel php install 8.1corepanel account update acme --php 8.1
# In a provisioning script, where nothing can answer a promptcorepanel php install 7.4 --yescorepanel php extensions [--php <version>]corepanel php extensions install <version> <extension> [--yes]corepanel php extensions remove <version> <extension> [--yes]extensions lists what each installed version loads, from the sixteen CorePanel
manages — imagick, redis, memcached, soap, ldap, imap, bcmath,
posix, ioncube-loader, sodium, gmp, tidy, apcu, igbinary,
mongodb, mcrypt.
Anything an administrator installed over SSH is listed separately as installed
outside CorePanel: it works, it is simply not managed here. See PHP Extensions and ionCube.
An extension belongs to a PHP version, not to an account: there is one php-fpm master per version, so installing one gives it to every account running that version.
The state of each one:
| State | Meaning |
|---|---|
installed | The package is here and the interpreter loads it |
NOT LOADED | The package is here but PHP does not load it — its ini was disabled by hand |
available | Not installed; it can be installed from here |
unavailable | Your package repository does not offer it for that version |
unknown | The repository could not be asked at all — not the same as unavailable |
not for this version | It does not exist for that PHP branch. Normal, not a fault |
install and remove take the extension’s logical name; which package that is
depends on the version and is decided by the daemon, since the repository spells
the same extension differently from one branch to the next.
Either direction restarts that version’s php-fpm master, interrupting
requests in flight for every account on it — the packages carry a trigger that
does it whenever their configuration directory changes. Both ask before running;
--yes skips the prompt and is required where there is no terminal.
A removal is refused when another installed package depends on the one being
removed, and says which: redis requires igbinary, so removing igbinary would
take redis with it and break caching for somebody who was never asked.
The package manager’s output is printed as it arrives. Interrupting the command
stops the watching, not the transaction — that runs in corepanel-sys, and
killing rpm halfway would leave the package database half-applied.
# What every version loadscorepanel php extensions
# Just one versioncorepanel php extensions --php 8.3
# WooCommerce needs imagick; object caching needs rediscorepanel php extensions install 8.3 imagickcorepanel php extensions install 8.4 redis
# Take it away again, having checked nothing else needs itcorepanel php extensions remove 8.4 rediscorepanel php limits [--json]corepanel php limits set [--memory-limit MB] [--max-execution-time S] [--max-input-time S] [--max-input-vars N] [--upload-size MB] [--post-size MB] [--terminate-after S] [--adopt-defaults] [--json]These are per request, not per account: the worst case one account can reach is
pm.max_children (8) multiplied by the memory limit, and the output prints that
product for you.
The header tells you whether the values were configured or are CorePanel’s defaults, and that difference decides what happens to pools. While nothing is configured the limits are a floor: a pool that already names a value keeps it. Once you set anything here, every CorePanel-managed pool is rewritten to match.
| Flag | Sets | Can a site override it? |
|---|---|---|
--memory-limit | memory_limit, in MB (64–16384) | No |
--terminate-after | request_terminate_timeout, in seconds (30–3600) | No |
--max-execution-time | max_execution_time, in seconds (10–3600) | Yes, with a .user.ini |
--max-input-time | max_input_time, in seconds (10–3600) | Yes |
--max-input-vars | max_input_vars (100–100000) | Yes |
--upload-size | upload_max_filesize, in MB (1–16384) | Yes |
--post-size | post_max_size, in MB (1–16384) | Yes |
Only the flags you pass change, and with no flags it does nothing. On a server
where nothing has been configured the values shown are defaults acting as a floor;
holding every pool to them is a real change and takes --adopt-defaults. Applying
reloads one php-fpm master per PHP version, not one per account.
Two rules are enforced, because breaking either produces a silent wrong answer rather than an error:
--terminate-aftermust exceed--max-execution-time, or php-fpm kills the worker before PHP can write the error and every runaway script becomes a 502 with an empty error log.--post-sizemust be at least--upload-size, or PHP rejects a large upload on the POST size first and the upload limit can never be reached.
Requires super-admin: the limits are server-wide, so a reseller cannot read or change them.
# What is every account held to right now?corepanel php limits
# A WooCommerce shop needs more room; everything else stays as it iscorepanel php limits set --memory-limit 512
# Bigger uploads for a media-heavy servercorepanel php limits set --upload-size 256 --post-size 256
# Free stuck workers sooner, keeping PHP as what reports the errorcorepanel php limits set --max-execution-time 60 --terminate-after 120
# For scriptingcorepanel php limits --jsoncorepanel resources
Section titled “corepanel resources”What every hosting account is using, and how much it is allowed. See PHP Isolation and Limits for the mechanism.
corepanel resources list [--json]corepanel resources set --limit N|0|none [--account NAME] [--json]The limit is concurrent PHP requests per account. An account already running
as many as it is allowed has its next request answered 508 while its
neighbours are untouched — that is what turns the per-request memory_limit
into a per-account bound: an account can hold at most its limit multiplied by
that memory limit.
list prints one row per account with memory, CPU, process count, PHP requests
in flight, the peak, and how many were refused; the header adds the ceiling for
all of PHP together and how much of it is in use.
--limit | Meaning |
|---|---|
N | At most N concurrent PHP requests |
0 | Unlimited, deliberately — how one account is exempted on a server that limits the others |
none | Clear it: the server stops limiting, or the account follows the server default again |
0 and none differ on the server: none means nobody has decided anything and
no limits are written at all, which is how a freshly upgraded server behaves.
Nothing is limited until you set a default here.
Applying reloads the web server. Requests already running are never killed by lowering a limit — they finish, and the new ceiling applies to what arrives next.
Requires super-admin.
# Who is using what?corepanel resources list
# Hold every account to 4 concurrent PHP requestscorepanel resources set --limit 4
# One busy shop gets more roomcorepanel resources set --account shop.example --limit 12
# ...and one account is exempt entirelycorepanel resources set --account internal.example --limit 0
# Back to following the server defaultcorepanel resources set --account internal.example --limit none
# For scriptingcorepanel resources list --jsonConfiguration
Section titled “Configuration”The CLI connects to corepanel-core through a Unix socket. By default it uses:
/run/corepanel-core/corepanel-core.sockOverride the socket path with the COREPANEL_CORE_SOCKET environment variable — useful in
non-standard deployments or when testing against a different Core instance:
COREPANEL_CORE_SOCKET=/run/corepanel-core/corepanel-core.sock corepanel status