Skip to content

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.

Terminal window
corepanel [command] [subcommand] [flags]
CommandDescription
corepanel system initInteractive wizard to initialize a fresh server
corepanel statusShow the status of all CorePanel services
corepanel quotaEnable disk quotas, inspect state and usage, and set per-account limits
corepanel account listList hosting accounts and their account ids
corepanel account createCreate a new hosting account and its primary domain
corepanel account updateChange an account’s limits, owner details or hosting package
corepanel account importImport a cPanel account (or preview with --dry-run)
corepanel account backupCreate a full local backup of a hosting account
corepanel account restoreRestore a hosting account from a cpbackup archive
corepanel account backup-scheduleManage recurring backup schedules and retention
corepanel domainList, add and remove an account’s secondary (alias/addon) domains
corepanel subdomainList, add and remove an account’s subdomains
corepanel appRun and publish an account’s persistent applications
corepanel cronManage an account’s cron jobs
corepanel wpInstall, update, harden and roll back WordPress sites
corepanel list adminsList administrator accounts
corepanel mail syncResynchronize the Postfix lookup database
corepanel mail listList the mailboxes of a domain
corepanel mail deleteDelete a mailbox and its Maildir
corepanel mail quotaSet a mailbox storage quota
corepanel mail suspend / activateSuspend or reactivate a mailbox
corepanel mail forwarderManage mail forwarders (aliases)
corepanel mail catchallManage a domain catch-all
corepanel mail redirectRedirect all mail for a domain to another domain
corepanel mail vacationManage a mailbox vacation autoresponder
corepanel mail filterManage mailbox Sieve filter rules
corepanel mail spamManage spam thresholds and allow/deny lists
corepanel edition websitesShow how many websites the edition allows and how many are in use
corepanel licenseActivate, inspect, refresh or release this server’s license
corepanel versionPrint the CLI build version

Run corepanel [command] --help at any time to see the available subcommands and flags.

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 administrator identity, and generates (or reuses) the administrator password.

Terminal window
corepanel system init

The wizard walks through six steps: detecting the public IP, prompting for the panel domain (validated as an FQDN), 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.

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.

FlagDescription
--domain <fqdn>Panel domain — the FQDN used to access CorePanel (e.g. panel.example.com)
--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-interactiveSkip all prompts; use flags and defaults for scripted provisioning

--hostname <fqdn> is retained as a deprecated alias for --domain.

For automated installs, pass the values as flags and disable prompts:

Terminal window
corepanel system init \
--domain panel.example.com \
--admin-email admin@example.com \
--no-interactive

When --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.

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.

Terminal window
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.

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.

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:

FilesystemWhat happensReboot
ext2/3/4the option is added to /etc/fstab and the filesystem is remountedno
XFS, separate /homeunmounted and mounted again, when nothing holds it openonly if busy
XFS on /configured on the kernel command line with grubbyyes

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.

Terminal window
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.

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.
Terminal window
corepanel quota status [--json]
Disk quotas: ACTIVE (accounting + enforcement)
Filesystem : /dev/sda4 on / (xfs)
Separate /home : no
Accounting : on
Enforcement : on
App data counted: yes

App 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.

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.

Terminal window
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.

Shows where an account’s disk usage is, not just how much it is.

Terminal window
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:

LocationWhy the account owns files there
/opt/userapps/<user>A published application’s program and releases — deliberately out of reach of the account’s own FTP, so it cannot replace the binary systemd is executing
/var/opt/userapps/<user>That application’s data, which survives every deploy
/var/spool/cron/<user>The account’s crontab
/tmp, /var/tmpNot by design: files left there still count against the quota

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 ... |
+----------------------------+-----------+-------+-----------+---------------------------+

Sets an account’s disk quota in MB. A quota of 0 removes the limit.

Terminal window
corepanel quota set <account> <MB>
Terminal window
# 20 GB for account 12, and no limit at all for dom-7
corepanel quota set 12 20480
corepanel quota set dom-7 0

The 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.

Pushes every account’s stored quota to the kernel and reports which accounts are already over theirs.

Terminal window
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.

Forces an immediate refresh of the stored usage instead of waiting for the hourly job.

Terminal window
corepanel quota refresh

When 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”.

Lists hosting accounts and, crucially, their numeric account id — the value the domain, subdomain, and cron commands take as their <account-id> argument. The id is printed once when the account is created; this command is how you recover it afterwards, or resolve a known domain to its id.

Terminal window
corepanel account list
FlagDescription
--search <domain>Filter accounts by primary domain (case-insensitive substring)
--jsonOutput 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.

Terminal window
# List every hosting account
corepanel account list
# Resolve a domain to its account id
corepanel account list --search example.com
# Scripting: extract the account id for a domain
corepanel account list --search example.com --json | jq '.[0].accountId'

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.

Terminal window
corepanel account create example.com

The account password can be supplied in three ways, listed from most to least secure:

  1. --password-stdin — read the password from standard input. Best for automation, since the secret never appears in the shell history or the process list.
  2. 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.
  3. --password <value> — pass it directly. Convenient but visible in the shell history and in ps.

The password must meet core’s strength requirements: at least 12 characters including lower-case, upper-case, a digit and a special character.

FlagDescription
--password <value>Account password (prefer --password-stdin or the prompt)
--password-stdinRead the account password from standard input
--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.
Terminal window
# Interactive password prompt, default (highest) PHP version
corepanel account create example.com
# Scripted: read the password from stdin, pin PHP 8.3 and a 5 GB quota
printf '%s' "$ACCOUNT_PASSWORD" | \
corepanel account create example.com --password-stdin --php 8.3 --quota 5120

On success the command prints the account ID, Linux username, domain, and home directory.

Changes an existing account’s limits, owner details or hosting package (core.UpdateAccount). The account id is passed as a positional argument — the numeric id from corepanel account list; the dom-<id> form is accepted too.

Terminal window
corepanel account update <account-id> [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.

FlagDescription
--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.
--forceAllow a disk quota below current usage, leaving the account over quota
Terminal window
# Upgrade: more mailboxes and more disk
corepanel account update 7 --max-emails 100 --quota 20480
# Move the account onto a package, then override one of its limits
corepanel account update 7 --package 3 --max-subdomains 50
# Plan downgrade below what the account currently stores
corepanel account update 7 --quota 1024 --force

An account created from a package stays linked to it after a manual edit and is reported as customized — see Editing an Account.

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).

Terminal window
# Preview (read-only)
corepanel account import <archive> --dry-run
# Run the import
corepanel account import <archive> [--package <id>] [--preserve-uid]
FlagDescription
--dry-runParse 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-uidPreserve the source UID/GID via useradd -u/-g
--on-conflict abort|skipPolicy 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);
  • 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).

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:

Terminal window
# Preview a staged cpmove archive without importing
corepanel account import /var/lib/corepanel/import/cpmove-example.tar.gz --dry-run
# Preview an already-extracted account directory
corepanel account import /var/lib/corepanel/import/job-42/cpmove-example --dry-run
# Run the import into package 3, preserving the original UID/GID
corepanel account import /var/lib/corepanel/import/cpmove-example.tar.gz \
--package 3 --preserve-uid --report /root/import-example.json

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 scheduled backups are available in every edition; encrypted and remote (S3 / GCS / MinIO / SFTP) backups are a Pro/Business feature.

Terminal window
corepanel account backup <user> [--wait]

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.

Terminal window
# Start a backup and return immediately with the job id
corepanel account backup pxdemo
# Start a backup and block until it completes
corepanel account backup pxdemo --wait
# Show the status and report of a backup job
corepanel account backup-status 7

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.

Terminal window
corepanel account restore <archive> [--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.

Terminal window
# Preview a restore
corepanel account restore /var/lib/corepanel/backups/cph2/cpbackup-cph2-20260721T090349Z.tar --dry-run --wait
# Restore, blocking until done
corepanel account restore /var/lib/corepanel/backups/cph2/cpbackup-cph2-20260721T090349Z.tar --wait

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.

Terminal window
corepanel account backup-schedule list
corepanel 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).
  • --name <label> — Optional human label.
  • --disabled — Create the schedule disabled.
Terminal window
# Nightly backup of one account, keep the last 7 archives
corepanel 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 days
corepanel account backup-schedule create --all --cron "0 3 * * 0" --days 30
# List schedules, run one now, then disable it
corepanel account backup-schedule list
corepanel account backup-schedule run 3
corepanel account backup-schedule disable 3

Lists administrator accounts and prints their key fields — ID, username, email, seller, and super-admin flag — in a table.

Terminal window
corepanel list admins
FlagDescription
--seller <id>Filter by a specific seller (default: 1)
--allInclude administrators from every seller (overrides --seller)
Terminal window
# All administrators across every seller
corepanel list admins --all
# Administrators for seller 42
corepanel list admins --seller 42

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.

Terminal window
corepanel mail sync

Add -v / --verbose to see a breakdown of how many domains, mailboxes, and aliases were synchronized:

Terminal window
corepanel mail sync --verbose
Synchronizing Postfix lookup database...
Synced:
- Domains: 12
- Mailboxes: 148
- Aliases: 37

Manage mailboxes for a domain. Each command persists the change in corepanel-core and synchronizes it to the Postfix/Dovecot lookup database.

Terminal window
# List the mailboxes of a domain (address, status, quota)
corepanel mail list example.com
# Delete a mailbox: removes the Maildir and revokes the credentials
corepanel 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 it
corepanel mail suspend sales@example.com
corepanel mail activate sales@example.com

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.

Terminal window
# List forwarders for a domain
corepanel mail forwarder list example.com
# Create or replace a forwarder to one or more destinations
corepanel mail forwarder set sales@example.com a@example.com b@partner.tld
# Delete a forwarder
corepanel mail forwarder delete sales@example.com

Manage the domain catch-all: any address that does not match a mailbox or forwarder is routed to the catch-all destinations.

Terminal window
# Show the current catch-all
corepanel mail catchall get example.com
# Route all otherwise-unmatched mail to a destination
corepanel mail catchall set example.com bucket@example.com
# Remove the catch-all
corepanel mail catchall delete example.com

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.

Terminal window
# Show the current whole-domain redirect
corepanel mail redirect get old-example.com
# Redirect every address to the same local part at another domain
corepanel mail redirect set old-example.com example.com
# Remove the redirect
corepanel mail redirect delete old-example.com

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.

Terminal window
# Show the current autoresponder
corepanel 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 yet
corepanel mail vacation set sales@example.com --subject "OOO" --message "" --inactive
# Remove the autoresponder
corepanel mail vacation clear sales@example.com

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
Terminal window
# Print the current rules as JSON
corepanel mail filter get sales@example.com
# Replace the rules from a JSON array
corepanel mail filter set sales@example.com --file rules.json
# Clear all rules
corepanel mail filter clear sales@example.com

Example rules.json:

[
{
"name": "Newsletters to a folder",
"matchAll": true,
"conditions": [{ "field": "subject", "op": "contains", "value": "newsletter" }],
"actions": [{ "type": "fileinto", "arg": "Lists" }, { "type": "stop" }]
}
]

Manage spam thresholds and allow/deny sender lists at a scope, enforced by Rspamd.

  • scope: global (no target), domain <domain>, or mailbox <address>
  • thresholds: reject, add_header, greylist (Rspamd action scores); 0 inherits the Rspamd default, and the effective values must satisfy greylist ≤ add_header ≤ reject
  • lists: allow (never mark matching mail as spam) and deny (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.

Terminal window
# Show a mailbox's thresholds and lists
corepanel 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 senders
corepanel mail spam allow mailbox sales@example.com friend@partner.tld
corepanel mail spam deny global spammer.example
# Remove a list entry by its id (from `show`)
corepanel mail spam remove domain example.com 7

Spam training is automatic: moving a message to the Junk folder teaches the filter it is spam, and moving it back out teaches it as legitimate.

Reports how many websites this server may host and how many are already in use.

Terminal window
corepanel edition websites
corepanel edition websites --json
Edition: personal
Websites: 12 / 20
Available: 8

A 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:

Terminal window
# Bail out early when the server is full
corepanel 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.

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.

Terminal window
corepanel license status [--json]
corepanel license activate [key] [--key-stdin] [--json]
corepanel license refresh [--json]
corepanel license deactivate [--yes]

Two 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.

Reports the licensing state from the license cached locally, so it works offline and is safe to call from a monitoring script.

Edition: pro
Key: CPL-EQ0T6-…-2ES83
Status: active
Bound to: 203.0.113.24
Valid to: 2027-08-01 00:00 UTC
Verified: 2026-08-01 04:12 UTC

The 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_failingCheck-ins have been failing for over 7 days; the edition still holds until the grace deadline
grace_expiredCheck-ins never recovered; the server fell back to Personal
expiredThe license ran out (payment stopped)
revokedThe license was revoked (refund or chargeback)
unverifiableThe 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.

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.

Terminal window
corepanel license activate --key-stdin < license.key

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.

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.

Terminal window
# Monitoring: alert when the paid edition is not in effect
corepanel license status >/dev/null || echo "licensing needs attention"
# Moving to a new server: release here first so the rebind is free
corepanel license deactivate --yes
# Scripting: how long until the grace window closes?
corepanel license status --json | jq -r '.graceExpiresAt // "n/a"'

Prints the CLI build version embedded at compile time.

Terminal window
corepanel version

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 numeric account id — list accounts with corepanel account list to find it.

Terminal window
corepanel cron list <account-id>
corepanel cron add <account-id> --schedule <spec> --command <cmd> [flags]
corepanel cron update <account-id> <job-id> --schedule <spec> --command <cmd> [flags]
corepanel cron remove <account-id> <job-id>
corepanel cron enable|disable <account-id> <job-id>
corepanel cron run <account-id> <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:

FlagDescription
--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)
--disabledCreate/leave the job disabled
--single-instanceSkip 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)
Terminal window
# 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 12
corepanel cron run 12 3 # run once, now, and show the output
corepanel cron disable 12 3

Jobs 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.

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/sh
PATH=/opt/corepanel/bin:/usr/local/bin:/usr/bin:/bin

What that means in practice:

  • It is sh, not bash. No [[ ]], arrays or process substitution. If you need bash features, wrap them: bash -c '...'.
  • php works 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-kb as PHP’s own memory_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/php pins 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, find and the MariaDB client (mysqldump).
  • % needs no escaping. CorePanel escapes it for cron, so date +%Y reaches the shell verbatim.
  • ~ expands to the account home, since cron sets HOME — but only at the start of a word. Inside a flag (--path=~/public_html) it stays literal; write "$HOME/..." there instead.
Terminal window
# Application cron entry points
php ~/public_html/cron.php
php ~/whmcs/crons/cron.php
php ~/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-now
wp --path="$HOME/public_html" plugin update --all
wp --path="$HOME/public_html" db export ~/backups/wp-$(date +%F).sql
# Trigger something over HTTP instead of on disk
curl -fsS https://example.com/tasks/run > /dev/null
curl -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.gz
find ~/backups -name 'db-*.sql.gz' -mtime +14 -delete
# Housekeeping
find ~/tmp -type f -mtime +7 -delete
find ~/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>&1
sh ~/bin/sync-assets.sh
bash -c 'for d in ~/sites/*/; do echo "checking $d"; done'
# Archive a directory
tar -czf ~/backups/uploads-$(date +%F).tar.gz -C ~/public_html uploads

Passing 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.

CommandWhy
/usr/bin/php script.phpNo global PHP on the host — exits 127. Use php.
sudo ..., anything as rootJobs run as the account user; cronie drops privileges first.
Multi-line commandsNewlines and control characters are rejected — a job is one line.
--schedule "@reboot"Not a supported macro (see above).
Reading another account’s filesHomes are 0700; jobs are confined to their own account.

An account may define up to 100 jobs.

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 + a webmail.<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 numeric account id (list them with corepanel account list); domain names are globally unique.

Terminal window
corepanel domain list <account-id>
corepanel domain add <account-id> <domain> [--type alias|addon] [--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]
  • list prints 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.
  • add validates the FQDN, global uniqueness, ownership and the matching per-type limit, provisions the vhost + DNS zone (and, for an addon, its own docroot, DKIM and webmail.<domain>) via sys.AddDomain, and moves the usage counter (core.AddDomain).
  • delete removes a secondary domain (core.DeleteDomain). For an addon this also tears down its mailboxes, DNS zone, DKIM and docroot. The account’s primary domain cannot be removed this way — it is only removed by deleting the account.
  • canonical show prints which hostname of the domain serves the site and what the others do (core.GetDomainCanonicalHost).
  • canonical set picks the real hostname (core.SetDomainCanonicalHost) and re-emits the site config. Every domain answers under both <domain> and www.<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 show prints a domain’s web optimization overrides (core.GetDomainOptimizations).
  • optimizations set sets 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 is Speed Optimizer (Pro/Business) and off by default — it can break sites (source maps, already-minified assets, execution order), so it is opt-in. Use these commands to override each per domain.

Flags for add:

FlagDescription
--type <type>Domain type: alias (parked, default) or addon (independent)
--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):

ValueEffect
noneEvery 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.
wwwwww.<domain> serves; <domain> answers 301 to it. Default for new domains.
nonwww<domain> serves; www.<domain> answers 301 to it.
primaryAlias (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:

FlagDescription
--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.
Terminal window
corepanel domain list 12
corepanel domain add 12 parked.example.com
corepanel domain add 12 shop.example.net --type addon --web-mode cms
corepanel domain add 12 parked.example.com --canonical primary
corepanel domain delete parked.example.com
corepanel domain canonical show example.com
corepanel domain canonical set example.com www
corepanel domain canonical set example.com nonwww
corepanel domain canonical set parked.example.com primary
corepanel domain optimizations show shop.example.net
corepanel domain optimizations set shop.example.net --webp off
corepanel domain optimizations set shop.example.net --webp default
corepanel domain optimizations set shop.example.net --minify-js on --minify-css on
corepanel domain optimizations set shop.example.net --early-hints off

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.

Terminal window
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>
  • list prints the domain’s whole route table in evaluation order (core.GetDomainRoutes). The MANAGED column marks which entries these commands own: yes for a proxy route you added, auto for the docroot fallback CorePanel maintains, no for a hand-written route it leaves untouched. With --check every proxy target is dialed (core.CheckProxyUpstreams) and a STATUS column reports up (Nms) or DOWN: <reason> — the fastest way to tell “my service died” from “my site is broken”, which from the outside are the same 502.
  • add adds 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-running add with the same path edits that route in place instead of adding a second, unreachable one. <upstream> is host:port, never a URL — or the unix socket of an application CorePanel published, written as unix:/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).
  • remove drops 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:

FlagDescription
--strip-prefixRemove the matcher prefix from the proxied request, so /api/* reaches the upstream as /*. Ignored for a catch-all matcher.
--tlsDial the upstream over HTTPS instead of plain HTTP.
--allow-reserved-portConfirm a target on a well-known local service port.
Terminal window
corepanel domain proxy list app.example.com
corepanel domain proxy list app.example.com --check
corepanel domain proxy add app.example.com '/api/*' 127.0.0.1:8080 --strip-prefix
corepanel domain proxy add app.example.com '/*' 127.0.0.1:8080
corepanel domain proxy remove app.example.com '/api/*'

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 numeric account id — find it with corepanel account list.

Terminal window
corepanel subdomain list <account-id>
corepanel subdomain add <account-id> <label> <parent-domain> [--web-mode <mode>]
corepanel subdomain delete <fqdn>
  • list prints every subdomain the account owns, with its parent, web mode, and document root (core.ListSubdomains).
  • add validates the label, parent ownership, global uniqueness (across both domains and subdomains) and the subdomain limit, provisions the docroot + vhost + parent-zone A record via sys.AddSubdomain, and moves the usage counter (core.AddSubdomain). <label> is the left-most part (e.g. blog); the labels www and webmail are reserved.
  • delete removes a subdomain by its fully-qualified name (core.DeleteSubdomain): the vhost, the parent-zone A record, the certificate and the document root.

Flags for add:

FlagDescription
--web-mode <mode>Web serving mode plain|cms|spa (empty ⇒ plain)
Terminal window
corepanel subdomain list 12
corepanel subdomain add 12 blog example.com
corepanel subdomain add 12 shop example.com --web-mode cms
corepanel subdomain delete blog.example.com

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.

Terminal window
corepanel wp list <account-id>
corepanel wp scan <account-id>
corepanel wp install <account-id> --domain <fqdn> --title <t> --admin-user <u> --admin-email <e> [flags]
corepanel wp attach <account-id> <docroot-path>
corepanel wp detach <account-id> <instance-id>
corepanel wp login <account-id> <instance-id> [--user <login>]
corepanel wp components <account-id> <instance-id>
corepanel wp update <account-id> <instance-id> [--core] [--all-plugins] [--all-themes] [--plugins a,b] [--themes x,y]
corepanel wp plugin|theme <action> <account-id> <instance-id> <slug>
corepanel wp policy <account-id> <instance-id> <none|minor|all>
corepanel wp hardening <account-id> <instance-id> [--set key=on|off ...]
corepanel wp integrity <account-id> <instance-id> [--reinstall]
corepanel wp maintenance|debug|indexing <account-id> <instance-id> [on|off]
corepanel wp set-url <account-id> <instance-id> <url>
corepanel wp restore-point create|list|rollback|delete

Highlights:

  • 1-click installwp install provisions the database and DB user, downloads core as the account, writes a hardened wp-config.php (fresh salts, system-cron takeover, direct filesystem access), creates the native */5 cron job that replaces WordPress’s visitor-paid pseudo-cron, and sets the domain’s web mode to cms. When --admin-password is omitted a strong password is generated and shown once.
  • Adopt existing siteswp scan finds installations anywhere under the account home (including sites migrated with the cPanel importer) and manages them without touching their behavior. wp detach leaves a .corepanel-wp-ignore marker so the site stays unmanaged.
  • One-time login linkswp login prints 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 updateswp update --core automatically snapshots the site (files + database) first; wp restore-point rollback brings 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).
  • Integritywp integrity verifies core files against wordpress.org checksums; --reinstall replaces core files in place (keeping wp-content and wp-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.

Terminal window
# Install and open the dashboard
corepanel wp install 12 --domain example.com --title "My Blog" --admin-user admin --admin-email me@example.com
corepanel wp login 12 3
# Keep it updated, safely
corepanel wp components 12 3
corepanel wp update 12 3 --core --all-plugins
corepanel wp restore-point list 12 3
# Harden and verify
corepanel wp hardening 12 3 --set xmlrpc=on --set user-enumeration=on
corepanel wp integrity 12 3
# Upgrade window
corepanel wp maintenance 12 3 on
corepanel wp update 12 3 --all-themes
corepanel wp maintenance 12 3 off

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 numeric account id; 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.

Terminal window
corepanel app list [<account-id>]
corepanel app show <account-id> <name>
corepanel app create <account-id> <name> --domain <fqdn> --port <n> [flags] -- <program> [args...]
corepanel app update <account-id> <name> [flags] [-- <program> [args...]]
corepanel app deploy <account-id> <name> [--artifact <path>]
corepanel app rollback <account-id> <name> [--to <release>]
corepanel app start|stop|restart <account-id> <name>
corepanel app releases <account-id> <name>
corepanel app logs <account-id> <name> [-n 200]
corepanel app env list|set|unset <account-id> <name> [KEY=VALUE|KEY] [--secret]
corepanel app webhook show|rotate|revoke <account-id> <name>
corepanel app delete <account-id> <name> [--purge-data]

Applications belong to a hosting account, identified by its numeric account id — list accounts with corepanel account list to find it. Every command below takes it as <account-id>, followed by the application’s name.

The start command goes after a -- separator, as a program relative to the release root followed by its arguments:

Terminal window
corepanel app create <account-id> api --domain example.com --path /api --port 8080 \
--health /healthz --mem 512 --cpu 100 -- server --addr :8080

create 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.

FlagDescription
--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|noneOutbound 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 goApplication 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.

Terminal window
# Deploy the conventional ~/apps/api.tar.gz
corepanel app deploy <account-id> api
# Or an artifact uploaded somewhere else in the account home
corepanel app deploy <account-id> api --artifact uploads/api-2026-07-27.tar.gz
# Go back
corepanel app releases <account-id> api
corepanel app rollback <account-id> api --to 20260727T101500Z

The 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.

Terminal window
corepanel app show <account-id> api
corepanel app logs <account-id> api -n 500

show 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.

logs is a snapshot of the journal, which holds both the application’s own output and the gateway’s. To follow it live, use journalctl -fu corepanel-app-<user>.<app> on the server.

Terminal window
corepanel app env set <account-id> api LOG_LEVEL=debug
corepanel app env set <account-id> api API_TOKEN=s3cr3t --secret
corepanel app env list <account-id> api
corepanel app env unset <account-id> api LOG_LEVEL

The 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 never displayed back — it can still be replaced, only not read. Setting or removing a variable restarts a running application.

A build pipeline can deploy without a person in the loop:

Terminal window
corepanel app webhook rotate <account-id> api # issues the token and prints both URLs
corepanel app webhook show <account-id> api # prints them again
corepanel app webhook revoke <account-id> api # disables it

The token works two ways. The pipeline can POST the artifact itself, deploying it in the same call and needing no FTP account:

Terminal window
curl -fsS -X POST https://panel.example.com/api/apps/upload \
-H 'Authorization: Bearer cpd_...' --data-binary @api.tar.gz

Or 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.

Terminal window
corepanel app stop <account-id> api
corepanel app delete <account-id> api

delete 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.

The CLI connects to corepanel-core through a Unix socket. By default it uses:

/run/corepanel-core/corepanel-core.sock

Override the socket path with the COREPANEL_CORE_SOCKET environment variable — useful in non-standard deployments or when testing against a different Core instance:

Terminal window
COREPANEL_CORE_SOCKET=/run/corepanel-core/corepanel-core.sock corepanel status