Suspension is the lever you pull when a customer's invoice is 14 days overdue, when an account is mid-compromise, or when abuse has just been reported and you want to stop the bleeding before you investigate. It's reversible, it's logged, and it's the single most common write operation a billing system performs against WHM.
The mechanics aren't obvious, though — suspension touches at least five subsystems (shell, FTP, mail, MySQL, Apache), and the behaviour around mail and DNS in particular catches resellers out the first time a suspended customer asks why a bounce was sent to their sender.
What suspension actually does
When you suspend bob, cPanel performs these operations in order:
- Locks the system user (
passwd -l bob), which kills SSH password and key auth. - Sets the shell to
/usr/local/cpanel/bin/noshellso even an existing SSH key returns an error message instead of a shell. - Adds the account to
/var/cpanel/suspended/with a file named after the account that contains the suspension reason. - Rewrites the Apache vhost so HTTP/S requests return the suspended-account page (the
"Account Suspended" template at
/var/cpanel/webtemplates/root/english/suspended.tmpl, or your custom override). - Disables outbound mail by setting
/etc/exim_outgoing_remote_smtp_denyflags and flagging the user in/etc/cpanel/local/suspendedmail(depending on version). - Disables FTP via Pure-FTPd or ProFTPd's per-user shell check.
- Leaves MySQL untouched — databases stay accessible from other accounts and from any service authenticating with stored credentials.
That last point is the most common surprise. A suspended account's WordPress site still hits MySQL fine; if you've redirected the vhost to a "suspended" page, the database keeps running queries from whatever scripts still reach it (cron, the cPanel API, restored backups). If you need to fully cut access, suspend the cPanel account and revoke MySQL grants — but think first, because re-granting them on unsuspension is manual.
Mail behaviour is the other gotcha. By default, suspending an account stops outbound mail but inbound mail still flows into the mailboxes. Customers expect "I'm suspended so I won't get email" and discover six weeks of unread mail when they pay. To bounce inbound too, enable Suspend incoming mail for suspended accounts under WHM → Server Configuration → Tweak Settings.
Suspend from WHM
Under WHM → Account Functions → Manage Account Suspension, pick the user, type a
reason, and click Suspend. The reason is visible to other resellers managing the same
account and ends up in /var/cpanel/suspended/<user>. It is not shown to the customer.
A few non-obvious checkboxes on that page:
- Disallow un-suspension by reseller — gates the reseller who owns the account from un-suspending it themselves. Use this when you're suspending for ToS/abuse and the reseller has been the abuse vector. See the reseller ACL reference for which ACLs map to suspend/unsuspend permissions.
- Enable Email Notification on Suspension — sends to the reseller's contact email, not the customer's. To notify the customer, your billing system has to do it, since WHM has no concept of the customer's email outside the cPanel contact field.
Suspend from the CLI or API
WHM exposes both legacy and modern entry points. The modern call:
whmapi1 suspendacct user=bob reason="Non-payment - invoice 4821" disallow=1
The legacy script still works and is what many WHMCS/Blesta modules call under the hood:
/scripts/suspendacct bob "Non-payment - invoice 4821" 1
The trailing 1 means "disallow reseller unsuspension." Omit or set to 0 to allow it.
For machine-driven suspensions from a billing system, use the API with a scoped
WHM API token rather than root credentials, with a custom
ACL list that includes suspend-acct but not kill-acct. If your billing system's
provisioning module ever goes rogue, you want it to be able to pause accounts, not delete
them.
To check the current state without unpacking the suspended/ directory:
whmapi1 listsuspended | head -20
whmapi1 accountsummary user=bob | grep -i suspend
Customise the suspension page and reason templates
The default suspended.tmpl is plain HTML that says "Account Suspended" — it leaks zero information, which is the point, but it also looks like a hosting-default error page. Most resellers want one of two things: a branded "this site is temporarily unavailable" page, or a clear "your hosting is past due, click here to pay" page for billing suspensions.
You can override per-reseller (preferred) or globally:
# Per-reseller
mkdir -p /var/cpanel/webtemplates/<reseller>/english/
cp /var/cpanel/webtemplates/root/english/suspended.tmpl \
/var/cpanel/webtemplates/<reseller>/english/suspended.tmpl
# Edit the copy. Variables [% ACCTNAME %] and [% RESELLER %] are interpolated.
# Global override (affects all suspended accounts not covered by a reseller template)
cp /var/cpanel/webtemplates/root/english/suspended.tmpl \
/var/cpanel/webtemplates/root/english/suspended.tmpl.bak
# Edit /var/cpanel/webtemplates/root/english/suspended.tmpl in place
Apache reads the template on every request to a suspended vhost — no rebuild needed. Cached responses on a CDN in front (Cloudflare, QUIC.cloud) will still serve the old content until they expire, so purge after editing.
For the reason strings shown in WHM dropdowns, edit
/var/cpanel/suspendedreasons.json. Pre-populating it with your own common reasons
(Non-payment, Abuse - phishing, Abuse - spam, Investigation - awaiting customer response) makes suspension consistent across staff and grep-able later when you're
producing a report for an abuse complaint.
Automate from your billing system
WHMCS and Blesta with the cPanel provisioning module
both call suspendacct on overdue invoices and unsuspendacct on payment. Two things to
verify when you wire this up:
- The token's IP allow-list should include the billing server's IP and only the billing server's IP. A leaked WHMCS token with full suspend-acct access on the internet is a fast way to have someone suspend your entire customer base.
- The "suspension days past due" setting in the billing system should be coordinated with your dunning emails. A standard pattern: day 0 invoice issued, day 7 reminder, day 14 second reminder + late fee, day 21 suspend, day 45 terminate. Anything tighter produces angry customers; anything looser is bad-debt territory.
For bespoke automation outside the billing system,
WHM standardised hooks fire on
Cpanel::Accounts::suspendacct and unsuspendacct. Use these to push a Slack message
into your ops channel, append to an audit log, or kick off a backup before suspension so
you have something to restore from if the customer is wrongly suspended.
Bulk suspend and reseller suspensions
Suspending a whole reseller suspends all their owned accounts in a single operation:
whmapi1 suspendreseller user=acmehost reason="Reseller payment overdue"
This is materially different from suspending only the reseller's own cPanel account —
suspendreseller walks the ownership tree and suspends every child account too. It's
the right tool for "this reseller hasn't paid in 60 days," and the wrong tool for
"this reseller's main cPanel account was compromised but their customers are fine."
For bulk operations across non-reseller accounts, drive a loop against the API:
for u in $(whmapi1 --output=jsonpretty listaccts | jq -r '.data.acct[] | select(.partition=="legacy-vol") | .user'); do
whmapi1 suspendacct user="$u" reason="Migration freeze - ticket 9912"
done
This pattern — filter on a property, loop and suspend — is the workhorse for migrations, where you suspend the source to prevent customer changes between rsync and DNS cutover. Unsuspend on the destination once the new home is live.
Unsuspend — what comes back and what doesn't
whmapi1 unsuspendacct user=bob
# or
/scripts/unsuspendacct bob
Unsuspension reverses the steps from earlier in roughly the same order. Things that generally do come back automatically: shell access, FTP, the original vhost, outbound mail, the original cron entries (they were never removed; cron jobs continue to run during suspension unless you also disable them).
Things that may not come back without manual intervention:
- MySQL grants you revoked outside of cPanel. Suspension doesn't touch them and
neither does unsuspension. If you ran
REVOKE ALLduring the abuse investigation, you need to re-grant manually. - API tokens the user issued from cPanel — these are preserved across suspension, so any external system using a customer's API token continues to work during suspension unless the token's IP allow-list happens to be cached behind the suspended vhost.
- Mail queue. If inbound mail was suspended too, queued bounces accumulate in
/var/spool/eximand are delivered on unsuspension — sometimes hundreds of bounce notifications hit the customer's inbox at once. Optionally drain the queue withexim -bp | grep bobandexim -Mrm <msgid>before unsuspending. - Cached suspension pages on Cloudflare or QUIC.cloud. Purge the customer's domain cache after unsuspending or visitors get the suspension page for up to the cache TTL.
Next steps
- Wire suspension into your billing flow with the Blesta cPanel provisioning module or audit the permissions your existing system uses via WHM API tokens.
- For automated abuse response, hook WHM standardised hooks on suspendacct to notify your ops channel and snapshot the account before lockout.
- If you're sizing a new server and want to know which suspension and reseller features are gated by tier, see the cPanel license breakdown.