Users' guide
Control panels (cPanel, DirectAdmin, Plesk)
ScalWS can replace the web server of a hosting control panel and serve the panel’s domains itself. This is cPanel (with Apache as its web server), DirectAdmin (with the web server CustomBuild installed) and Plesk (its nginx; Apache keeps running behind it); the panel keeps running everything else, including mail, DNS, FTP and databases. The sections up to “Not supported in panel mode” describe cPanel; DirectAdmin and Plesk list what is different there. A server without a panel is not affected: panel mode is off until you turn it on, and nothing in a normal installation depends on it.
Panels and systems
This is every pair of panel and system as tested. Panel mode needs a licensed cPanel:
without a licence panel enable refuses before it changes anything.
- Tested: the whole checklist passed.
- Tested in part: some rows passed; what is open is named.
- Not supported by the vendor: the panel does not run on that system, or the vendor does not support it.
- Deferred: not run yet.
| System | cPanel | DirectAdmin | Plesk |
|---|---|---|---|
| AlmaLinux 8 | Tested | Tested | Tested |
| AlmaLinux 9 | Tested in part: everything up to upcp passed; the reboot, repeated enable and disable, forced rollback and first-install takeover rows are open, because panel mode needs a licensed cPanel (whmapi1) | Tested, with Apache and with nginx_apache | Tested |
| AlmaLinux 10 | Deferred to a host with x86-64-v3: cPanel’s Perl needs it and refuses the x86_64_v2 build | Tested (x86_64_v2 build) | Tested under CPU emulation (Plesk’s installer needs x86-64-v3) |
| Rocky Linux 8 | Not supported by the vendor | Tested | Tested |
| Rocky Linux 9 | Not supported by the vendor (cPanel refuses it) | Tested, with Apache and with nginx_apache | Not supported by the vendor |
| Rocky Linux 10 | Not supported by the vendor | Deferred | Not supported by the vendor |
| Oracle Linux | Not supported by the vendor | Not supported: on 9 the panel installs but CustomBuild fails | Not supported: Plesk publishes no build for Oracle Linux 9 |
| Fedora 44 | No panel supports it | No panel supports it | No panel supports it |
| Debian 12, 13 | Deferred | Deferred | Deferred |
| Ubuntu 22.04 | Deferred | Deferred | Deferred |
| Ubuntu 24.04 | Tested | Tested | Tested |
| Ubuntu 26.04 | When the vendor supports it (it does not yet) | Tested | Tested |
| openSUSE Leap, SLES | Not supported by the vendor | Not supported by the vendor | Not supported by the vendor |
| Alpine Linux | Not supported by the vendor | Not supported by the vendor | Not supported by the vendor |
On the RHEL family, Plesk was tested with SELinux enforcing. cPanel and DirectAdmin need SELinux disabled and their installers see to it, so ScalWS loads no SELinux policy there. Oracle Linux 8 and 10 were not run with any panel.
Let’s Encrypt in panel mode was verified live on Ubuntu (DirectAdmin and Plesk). The RHEL family virtual machines used certificates the panel provided or that were uploaded.
For the packages see installation; for the commands see the scalwsctl reference.
What panel mode does
- ScalWS reads cPanel’s own data (the domain list, the account vhosts, the AutoSSL
certificates, the suspended accounts) and writes one site file per vhost. It never writes
into cPanel’s directories (
/var/cpanel,/etc/userdatadomains,/opt/cpanel,/etc/apache2). - ScalWS takes ports 80 and 443 from Apache. Apache stays installed but is disabled in cPanel’s service manager, so that cPanel does not start it again.
- PHP runs through the PHP-FPM pools that cPanel manages for each account. Every domain is converted to PHP-FPM when you turn panel mode on.
- A sync keeps the site files in step with cPanel: when you add, change, suspend or remove a domain in cPanel, ScalWS follows.
Turn it on
Check what would happen first:
sudo scalwsctl panel detect
sudo scalwsctl panel enable cpanel --dry-runThen switch, as root:
sudo scalwsctl panel enable cpanelThe first install of the package does this by itself on a panel server; install with
--panel=none (SCALWS_PANEL=none for the package) to skip it (see
installation).
enable refuses, and changes nothing, when:
- cPanel is not installed (
/usr/local/cpanel/cpanelis missing); - panel mode is already on;
- another program than Apache (
httpd) or ScalWS listens on port 80 or 443; - a TLS listener in
scalws.yamlalready has acertificates_fileof its own (panel mode sets that key; move those certificates into a site’stlsor remove the line, then run it again); scalws.yamldoes not parse.
The steps, in this order:
- The current state is recorded in
/var/lib/scalws-panel/state.json: whether Apache is enabled and monitored, whether new accounts get PHP-FPM, each vhost’s PHP handler, whetherscalws-controllerwas enabled and running./var/lib/scalws-panel/belongs to root (mode0711; the certificate copies incerts/,0750with groupscalws;state.jsonandstatus.json, which list every account’s domains,0600); a panel command refuses to write there when the directory has another owner or can be written by others. - PHP-FPM becomes the default and every domain is converted to it.
enablefileprotectruns (cPanel’s standard protection: document roots areuser:nobodywith mode0750) and thescalwsuser joins the groupnobody, so that ScalWS can read the document roots. When cPanel has no/var/cpanel/suspendedyet (a server where no account was ever suspended), it is created as cPanel creates it (root:mail, mode0710), so the sync’s path unit can watch it. cPanel converts the domains to PHP-FPM in the background:enablewaits up to 30 seconds for every vhost’s pool before the first sync.scalws.yamlgets these changes and no others: the entrysites.d/panel/*.yamlis appended toinclude(after the existing entries, soscalwsctl site addkeeps writing tosites.d/), each TLS listener getscertificates_file: "-/etc/scalws/panel/certificates.yaml", and, if the packaged welcome site is still configured, its document root becomes cPanel’s default document root/var/www/html. Comments and everything else in the file stay as they are.- The first sync writes the panel’s sites, and the whole configuration is checked while Apache still serves.
- Apache is disabled in cPanel and stopped, and
scalws-controlleris enabled (so that it starts at boot instead of Apache) and restarted. The command waits up to 10 seconds for ScalWS to listen on ports 80 and 443, reloads it with the edited configuration (a restarted controller resumes the configuration it ran before; the reload is retried for up to 30 seconds while the controller’s instance has no admin socket yet), and waits up to 10 seconds more until it answers an HTTP request on port 80. - The units
scalws-panel-sync.pathandscalws-panel-sync.timerare enabled.
Expect a short interruption between the moment Apache stops and the moment ScalWS
listens, normally a second or two. If a step fails, the steps before it are undone in
reverse order: scalws-controller is stopped together with its instances (they keep
running when only the controller stops, so the command terminates them by process id) and
left disabled unless it was enabled before, Apache is enabled in cPanel and then started
(while cPanel has it disabled, /etc/apachedisable, systemctl start httpd does nothing;
when port 80 is still free after that, Apache is started once more), and the
configuration and the generated files are restored. When scalws-controller was running
before panel enable (for example on ports 80 and 443 while Apache was disabled in WHM),
it is started again at the end and reloaded with the restored configuration (it would
otherwise resume its latest generation, the failed one with the panel sites); the
rollback says that ScalWS serves again only once that generation is active, and reports
the failure otherwise. This includes ScalWS not taking the
ports, a refused reload and no HTTP answer (see journalctl -u scalws-controller). The
command then looks at port 80: it reports that Apache (or ScalWS, when it served the ports
before) serves only when it listens there, and otherwise names the process that holds the
port. Two steps are not undone,
because they are harmless under Apache, and the message lists them as kept: the PHP-FPM
conversion (step 2) and FileProtect (step 3).
Panel commands (enable, disable and every sync, by hand or from the units) take a
lock on /var/lib/scalws-panel/lock, so they never run at the same time; a second one
waits up to 60 seconds, then fails with a message.
What is served
| cPanel | ScalWS |
|---|---|
| Main, sub, addon and parked domains of every account | One site per vhost: the account’s document root, its PHP-FPM pool and the vhost’s other names (www. and aliases). |
Service subdomains (cpanel., whm., webmail., webdisk., cpcalendars., cpcontacts., autodiscover., autoconfig.) | Proxied to the local cpsrvd ports: cpanel 2082, whm 2086, webmail 2095, webdisk 2077, cpcalendars and cpcontacts 2079; autodiscover and autoconfig go to 2082. |
| The server’s hostname | The default page from /var/www/html. |
| Suspended accounts | A “suspended” page (/var/lib/scalws-panel/suspended/index.html) instead of the site, for every path. |
A vhost with keys that this version of ScalWS does not know is still served; unknown keys
are ignored. A vhost whose document root does not exist yet (an account that is being
created) is skipped until it exists. A vhost without a PHP-FPM pool still gets PHP, on a
socket that never exists: its PHP scripts are refused (an error, never their source) until
the pool exists, and panel status warns about it. cPanel writes a new vhost’s pool a moment after the domain itself, so a sync that sees a
vhost without a pool for the first time starts one more sync 30 seconds later
(scalws-panel-sync-retry, through systemd-run), and so does a sync that rewrote site
files while a vhost still has no pool (cPanel’s queued conversion of a new domain to
PHP-FPM rewrites the vhost a second before it writes the pool). A retry that still finds
a vhost without its pool schedules the next one, at most three more times; after that,
the timer picks up a pool that appears later. On DirectAdmin the same retry, 5 seconds
later, serves a domain whose document root DirectAdmin made after its hook ran. The retry
also leaves the sync request (/var/lib/scalws-panel/sync-requested), so a sync that runs
at that moment runs once more.
Document roots and symbolic links: an account owns the directories in its home directory,
so it could replace public_html with a link to another account’s files or to the
certificate keys. The sync writes each account’s document root in canonical form (a link
above the home directory that root set up, such as /home -> /home2, is resolved) and
skips, with a message in panel status, a vhost whose document root has a symbolic link
anywhere below the home directory. Each account site also gets
root_symlinks: refuse, so the server itself opens the
document root without following links, also when the link appears between two syncs.
Links inside a document root never lead outside it, as on any ScalWS site.
New accounts: when cPanel creates a document root with the account’s own group
(user:user 0750), the server could not read it. The sync gives such a document root the
group nobody (FileProtect’s setting), only when the account owns it and never through
a link; directories above it that the server cannot enter are reported in panel status
and left as they are. The server opens document roots as the user scalws (a member of
nobody), while the sync runs as root: a vhost whose document root scalws cannot open
is skipped with a message in panel status instead of making the reload fail for every
account. The sync asks the kernel: one short child process (scalwsctl panel probe-access) runs as scalws, with its groups, and opens each document root the way
the server does (from /, one directory at a time, no symbolic link followed), so ACLs
and every other rule count, not only the mode bits. If that process cannot run, panel status warns and the mode bits decide. A root
that becomes unreadable between two syncs does not stop the server either: at the next
start, or a reload that leaves its site file as it was, only that site answers 503
(the error is in the log), the other accounts are served, and the next sync leaves the
site out. A new or changed site whose document root cannot be opened still makes the
reload fail: scalws-controller gives the instances of the new generation the previous
generation’s configuration (scalwsd --previous-config), they fail to start, the
controller rolls the generation back, and the sync restores the previous files. Once a
generation has replaced the previous one completely, instances that restart or are added
get no previous configuration: nothing could take over from them, so a site whose root
became unreadable answers 503 there as well instead of keeping the server down. The
same applies to a reload on a single instance without a spare slot (instances.max
equal to the desired count): the old instance drains before the new one starts, so a new
site whose root cannot be opened answers 503 instead of failing the reload.
A refused reload is traced to its site: the controller’s rollback carries the failed
instance’s start error, which names the application (tenants[i].applications[j]); the
sync leaves that site out (it is listed under skipped in panel status) and reloads
again, at most three times. Only a refusal that names no generated site restores the
previous files of every account. So one account whose new document root changes after
the sync looked at it (renamed, closed, replaced) holds back no other account’s change.
A site the sync puts back to its previous file for such a refusal never takes back a
name its account gave up in the same pass when another account’s site now serves it,
and a site refused again after it was put back is left out for the pass: either way the
site is left out, and the other accounts’ changes stay.
Each generated site has its own tenant and application name: the name derived from its
domain plus 24 hex digits (96 bits) of the domain’s SHA-256
(shop-example-com-1a2b3c4d5e6f7a8b9c0d1e2f), so domains such as shop.example.com and
shop-example.com never share one, and no account can practically pick a domain whose
name equals another account’s. The service and hostname
sites use reserved names (scalws-panel-cpanel-<service>, scalws-panel-hostname).
The configuration takes at most 10000 site files. Panel mode writes at most 9000 (the
rest is kept for your own): past that, vhosts of the account with the most vhosts are
skipped, each with a message in panel status.
The site files are in /etc/scalws/sites.d/panel/<vhost>.yaml. They are generated: do
not edit them, the next sync overwrites them. Put your own sites in /etc/scalws/sites.d/
as usual. A file in sites.d/panel/ without the “Generated by scalwsctl panel sync”
header is yours: the sync and panel disable leave it alone. A panel domain whose site
file would have that name is skipped (listed in panel status), so your file is never
replaced.
Certificates
cPanel’s AutoSSL certificates (and any certificate installed in cPanel) are reused;
ScalWS does not order certificates for these domains. The sync copies each vhost’s
certificate to /var/lib/scalws-panel/certs/<vhost>.pem (root:scalws, mode 0640) and
lists the copies in /etc/scalws/panel/certificates.yaml, which the listeners read
through certificates_file (see
certificates from a file). A certificate that covers
only some of the vhost’s names is used for the names it covers. An expired or self-signed
certificate is still served. The server hostname’s certificate is also the default for
names that match no site.
Keeping in step
Three units run scalwsctl panel sync:
| Unit | When |
|---|---|
scalws-panel-sync.path | /etc/userdatadomains, /var/cpanel/ssl/apache_tls or /var/cpanel/suspended changes. |
scalws-panel-sync.timer | 2 minutes after boot, then every 5 minutes. |
scalws-panel-sync.service | The oneshot service that both of them start. |
The units are in the package but stay disabled, and they do nothing while
/var/lib/scalws-panel/state.json is missing; panel enable turns them on and panel disable turns them off.
The path unit sees /var/cpanel/ssl/apache_tls itself change (a certificate installed for
a new vhost), not the files inside its vhost directories: an AutoSSL renewal that only
replaces apache_tls/<vhost>/combined is picked up by the timer, within 5 minutes. The
old certificate is served until then.
A sync writes only what changed (unchanged files keep their modification time), checks the
whole configuration and reloads once. If a site does not pass the check, that site is left
out and the others are served; the same for a reload refused because of one site (see
above). If the reload is refused for another reason, or the check fails for a reason that
no generated file explains, the previous files are restored and the error is kept in the
status. scalws-controller answers a reload before an instance started on the new
configuration, so the sync waits (up to 60 seconds) until an instance of the new
generation is active; a generation the controller rolled back counts as a refused reload.
A check that runs out of file descriptors blames no site: the sync stops with that error
and restores. scalwsctl raises its soft limit of open files to the hard one, and
scalws-panel-sync.service sets LimitNOFILE=1048576. To run a sync by hand:
sudo scalwsctl panel syncTo see the result of the last one:
sudo scalwsctl panel status
sudo scalwsctl panel status --jsonstatus shows the time of the last sync, the number of sites, whether the server was
reloaded, the skipped domains and the warnings, and error: when the last sync failed.
The same status file (/var/lib/scalws-panel/status.json, readable by root only) feeds
the console.
In the console
When panel mode is on, the console’s Manage, Domains page lists the cPanel-managed sites
read-only, each marked “cPanel”, under a banner with the last sync, the skipped domains,
the warnings and the sync error if there is one. The console cannot add domains in panel mode (the Add button is
hidden and the API answers 409): add cPanel domains in cPanel, and sites of your own with
sudo scalwsctl site add. Removing a cPanel-managed domain is refused too; removing a
site of your own from sites.d/ still works.
Turn it off
sudo scalwsctl panel disable
sudo scalwsctl panel disable --restore-phpdisable stops the sync units, disables and stops scalws-controller and terminates its
instances (they keep running when only the controller stops; SIGTERM, then SIGKILL after
10 seconds, by process id), enables and starts Apache again in cPanel and checks that it
listens on port 80, removes the panel lines from scalws.yaml and deletes the generated
files (the sites, the certificate list and copies, the status and the suspended page).
Every step runs even when one fails; the state file stays then, so that you can run the
command again after fixing the cause.
What it keeps:
- PHP-FPM. It works under Apache too, so the conversion is undone only with
--restore-php, which puts back the recorded default and each vhost’s recorded handler. - FileProtect and the group membership of
scalws. - An
includeentry forsites.d/panel/*.yamlthat was inscalws.yamlbefore panel mode was turned on.
ScalWS is stopped and disabled afterwards, whatever it was before panel mode: Apache
takes ports 80 and 443 at boot, and ScalWS must not race it for them. To run ScalWS on
other ports, change the listeners in scalws.yaml, then run
sudo systemctl enable --now scalws-controller.
Removing the scalws package while panel mode is on runs scalwsctl panel disable first,
so that Apache serves again. If that fails, the removal prints a warning with the commands
that give the ports back to Apache (whmapi1 configureservice service=httpd enabled=1 monitored=1 and systemctl start httpd) and continues.
Not supported in panel mode (cPanel)
Apache features without a ScalWS equivalent are not carried over:
- CGI scripts (
/cgi-bin/); - Server Side Includes (SSI);
- per-user directories (
/~user/); - Apache include files under
/etc/apache2/conf.d/userdata/.
Sites that depend on them need to stay on Apache or be changed. Real client addresses
behind Cloudflare are not carried over either: cPanel configures them in Apache
(mod_remoteip), and the matching ScalWS setting is generated in a later step.
DirectAdmin
On DirectAdmin ScalWS replaces the web server that CustomBuild installed (Apache, nginx,
nginx with Apache, LiteSpeed or OpenLiteSpeed; the units httpd, nginx, lshttpd,
lsws). The commands, the generated files, the certificate list, the sync units, the
rollback and the console work as described above for cPanel; this section lists what is
different.
sudo scalwsctl panel detect
sudo scalwsctl panel enable directadmin --dry-run
sudo scalwsctl panel enable directadminenable refuses, and changes nothing, when DirectAdmin is not installed
(/usr/local/directadmin/directadmin is missing), when cPanel panel mode is on, when
another program than the CustomBuild web server (httpd, nginx, litespeed, lshttpd,
openlitespeed) or ScalWS holds port 80 or 443, and when PHP is not on PHP-FPM (below).
What enable does:
-
It records the web server units (enabled, running) and their lines in
/usr/local/directadmin/data/admin/services.statusin/var/lib/scalws-panel/state.json. -
It adds the user
scalwsto the groupaccess(where it exists), which opens the home directories, the document roots and the accounts’ PHP-FPM sockets (DirectAdmin leaves them atuser:access 0710anduser:access 0660). A PHP-FPM socket directory in use (/usr/local/php<version>/sockets/, usuallyapache:apache 0710) thatscalwscannot search otherwise adds its group too, but neverrootor another system group (id below 1000) exceptapache: such a directory stopsenable. Thenscalwsmust be able to pass through each socket directory and use the webapps socket (webapps.sock); if not (or a PHP version in use has no socket directory),enablestops and undoes its steps, naming the directory or the socket. An account’s own socket thatscalwscannot use does not stopenable: each sync refuses that account’s PHP scripts instead andpanel statuswarns. No document root mode is changed.disableremoves the memberships it added. -
The first sync writes the sites and the configuration is checked while the old web server still serves.
-
The web server is handed over. In
services.statusits line (httpd=,nginx=,lshttpd=,lsws=) is set toOFF, so that DirectAdmin’s service monitor does not start it again; this is the only change ScalWS makes in that file. The unit is stopped and disabled, and a systemd drop-in/etc/systemd/system/<unit>.service.d/scalws-panel.confkeeps it from starting while panel mode is on, also when a CustomBuild run (da build ...) restarts the web server:[Unit] ConditionPathExists=|!/var/lib/scalws-panel/state.json ConditionPathExists=|!/usr/bin/scalwsctlBoth conditions are triggering (
|): the unit starts as soon as either file is missing, so a drop-in left behind once ScalWS is gone does not keep the web server down. The unit is not masked:panel disableremovesstate.jsonand the drop-in. -
scalws-controllerstarts,scalws-panel-sync.timerandscalws-panel-sync-request.pathare enabled and the hook is installed. The cPanel path unit (scalws-panel-sync.path) is not used on DirectAdmin.
Keeping in step: the hook
DirectAdmin runs a hook after it rewrites an account’s web configuration (domain,
subdomain, pointer, SSL, PHP version, suspension), after it deletes an account and after
it saves a certificate. enable writes the same small script for the three hooks:
/usr/local/directadmin/scripts/custom/user_httpd_write_post/scalws-panel-sync.sh
/usr/local/directadmin/scripts/custom/user_destroy_post/scalws-panel-sync.sh
/usr/local/directadmin/scripts/custom/ssl_save_post/scalws-panel-sync.shThe script only creates the empty file /var/lib/scalws-panel/sync-requested, runs
systemctl start --no-block scalws-panel-sync.service and exits 0. It starts systemctl
with an empty environment and never reads or logs its own: DirectAdmin puts passwords
there. A sync that is already running when the hook fires (the start does nothing then)
finds the file when it finishes and runs once more, and the path unit
scalws-panel-sync-request.path (enabled by enable on DirectAdmin only) starts a sync
whenever the file exists and no sync runs. A change in DirectAdmin is served within
seconds; the 5-minute timer is the fallback (a certificate renewal, for example). These
scripts are the only files ScalWS writes below /usr/local/directadmin, and disable
removes them. A hook directory that is a symbolic link is refused.
What is served
| DirectAdmin | ScalWS |
|---|---|
Main and additional domains (domains.list) | One site per domain: public_html of the domain, the account’s PHP-FPM pool, www. and the names of its alias pointers. |
| Subdomains | One site per subdomain, with its own document root when DirectAdmin set one (it must stay below the account’s home directory). |
Pointers of type alias | Served like the domain, also for its subdomains. |
Pointers of type pointer | A 301 redirect to the domain, keeping the path. |
Force SSL (force_ssl=yes) | Redirect from HTTP to HTTPS. |
| Suspended user or domain | The suspension page. |
| The server hostname and unknown names | The default page from /var/www/html (no symbolic link in the path of a root is followed). |
Webmail and phpMyAdmin: every site also serves DirectAdmin’s shared paths, and an account’s files never replace them:
/roundcubefrom the directory/var/www/html/roundcubelinks to,/phpMyAdmin,/phpmyadminand/pmafrom the one/var/www/html/phpMyAdminlinks to (the URL prefix maps to that directory:/pma/xis<phpMyAdmin>/x), with PHP in thewebappspool (/usr/local/php<version>/sockets/webapps.sock). The path alone (/roundcube) answers 301 to the path with a slash (/roundcube/, the query kept), so that the application’s relative links stay below it. The sync resolves the links each time; a target that is missing or not a directory, or where it, a directory above it or a directory above the link is not owned by root orwebappsor is writable by group or others, or thatscalwscannot open, is left out with a warning inpanel status; ifscalwscannot open/var/www/htmlitself, all of these paths are left out (and the hostname’s site). The sites stay served either way. phpMyAdmin’slogdirectory answers 403, as under Apache, also under the versioned directory’s own name on the hostname (/phpMyAdmin-<version>/log);/config, rewritten to/redirect.phpin/var/www/htmland the same pool;/.well-known/acme-challenge, static, from/var/www/html;/.well-known/autoconfig/mail/config-v1.1.xml, proxied tounix:/usr/local/directadmin/shared/internal.sock(mail autoconfiguration).
PHP
Every PHP slot in use (phpN_release in
/usr/local/directadmin/custombuild/options.conf is not no) must run as php-fpm.
ScalWS does not convert PHP: if a slot runs as something else, enable stops before it
changes anything and names the command, for example
da build set php1_mode php-fpm && da build phpRun it, then enable again. Each domain uses the pool of its PHP slot (php1_select),
the socket named in the account’s php/php-fpm<version>.conf. The suffixes .php,
.phtml, .inc and the other script suffixes always go to PHP-FPM and are never served
as files. A domain with PHP on but without a usable pool gets a socket that never
exists: its scripts are refused (an error, never their source) and panel status warns,
as on cPanel.
Certificates
The certificates DirectAdmin stores for a domain (Let’s Encrypt or uploaded: the
certificate, the key and, when there is one, the chain) are copied as one combined PEM to
/var/lib/scalws-panel/certs/ (root:scalws, mode 0640) and listed in
/etc/scalws/panel/certificates.yaml. The copy holds only the certificates and the key
that parsed (and match), written again as PEM; nothing else of the files reaches it. The
files a domain’s configuration names (SSLCertificateFile and its siblings) are used only
when they are regular files in the account’s own
/usr/local/directadmin/data/users/<user>/domains/ directory, reached without a symbolic
link, or the server certificate and key; otherwise the domain’s own <domain>.cert,
.key and .cacert are. Every certificate file is read as a regular file of at most
1 MiB, without following a symbolic link. A certificate serves the names of the site that it
covers (Let’s Encrypt certificates from DirectAdmin usually cover www., uploaded ones
may not). A subdomain uses its own certificate, otherwise the domain’s when that covers its
name. The server certificate (/etc/httpd/conf/ssl.crt/server.crt.combined and
/etc/httpd/conf/ssl.key/server.key) is the default for names that no site matches.
ScalWS does not order certificates for these domains.
Turn it off
sudo scalwsctl panel disableremoves the hooks, stops the sync timer, takes ScalWS off ports 80 and 443, removes the
drop-ins, enables and starts the recorded web server units again, restores their
services.status lines, removes the generated files, the configuration lines and the group
memberships it added, and checks that the old web server answers on port 80. PHP was not
changed, so --restore-php has nothing to do. Removing the package while panel mode is on
runs disable first.
Not supported on DirectAdmin
Per-domain custom snippets (CMD_CUSTOM_HTTPD), mod_security rules, CGI (cgi-bin) and
nginx templates are not translated; sites that depend on them need to stay on the
original web server. A domain with custom httpd or nginx configuration (a non-empty
<domain>.cust_httpd or <domain>.cust_nginx in the account’s domains directory) is
still served, and panel status warns that the configuration is not applied.
Plesk
On Plesk ScalWS replaces nginx (the unit nginx), which holds ports 80 and 443. The
Apache behind it on 127.0.0.1:7080 and 7081 (the unit apache2) keeps running and is not
touched: Plesk regenerates Apache’s configuration on every web change (a new domain, a PHP
handler change, plesk repair web) and restarts it gracefully, which fails while Apache is
stopped. ScalWS does not use it. Plesk’s own panel (sw-cp-server, ports 8443 and 8880), mail, DNS, FTP and databases are not touched.
The commands, the generated files, the sync units, the rollback and the console work as
described above for cPanel; this section lists what is different.
sudo scalwsctl panel detect
sudo scalwsctl panel enable plesk --dry-run
sudo scalwsctl panel enable pleskenable refuses, and changes nothing, when Plesk is not installed (/usr/local/psa/version
or /opt/psa/version is missing), when another panel’s panel mode is on, and when another
program than nginx or ScalWS holds port 80 or 443. That includes Apache alone on port 80
(nginx turned off in Plesk): turn nginx on in Plesk first.
ScalWS only reads Plesk’s data: fixed SELECT queries through plesk db, the PHP-FPM
pool files below /opt/plesk/php/*/etc/php-fpm.d/, the webmail configuration under
/etc/nginx/plesk.conf.d/webmails/ and /etc/apache2/plesk.conf.d/webmails/ and the
certificates in /opt/psa/var/certificates/. The only things it writes in Plesk’s
territory are the systemd drop-in of the nginx unit and the event handlers it registers
(below).
What enable does:
- It records the state of the unit
nginx(enabled, running) in/var/lib/scalws-panel/state.json, and every PHP handler it changes in step 2. - PHP is switched to PHP-FPM, as on cPanel. A domain whose handler is not FPM gets the
FPM handler of the same PHP version (
plesk-php<version>-fpm, withplesk bin site --update <domain> -php_handler_id ...), and the default handler of each service plan is switched the same way (plesk bin service_plan --update, with-ownerfor a reseller’s plan) so that new domains start on FPM. A domain with an operating system handler (notplesk-php...) is not switched; its scripts are refused andpanel statuswarns. The previous handlers are recorded with the domain’s and plan’s ids. A domain or plan whose switch fails is a warning (in the report andpanel status): it keeps its handler, is not recorded andenablegoes on; only when every switch fails doesenablestop. - It adds the user
scalwsto the grouppsaserv(Plesk’s own model: nginx and Apache are members), where the group exists andscalwsis not in it. - The first sync writes the sites and the configuration is checked while nginx still serves.
- nginx is handed over: when it was enabled or running it is stopped and disabled; when it
exists but neither ran nor was enabled it is held (it is not started or enabled, ever).
Either way a drop-in
/etc/systemd/system/nginx.service.d/scalws-panel.conf(the same two triggering conditions as on DirectAdmin) keeps it from starting while panel mode is on, also when Plesk’s watchdog orplesk repair webtries to. Apache is not stopped, disabled or given a drop-in. scalws-controlleris enabled and restarted on ports 80 and 443. Then the webmail unit starts (below) and, once its socket exists, a second sync serves the webmail (when that sync fails, panel mode stays on with the first sync’s sites andpanel statuswarns; the next sync serves the webmail). The sync timer andscalws-panel-sync-request.pathare enabled, the hook script is copied to/var/lib/scalws-panel/plesk-hook.shand the event handlers are registered.
Any failing step undoes the earlier ones in reverse order, handlers and PHP switches included, and the message says what was undone.
Keeping in step: event handlers
Plesk runs a command as root on its events. enable registers the hook script for 35
events with plesk bin event_handler --create -command /var/lib/scalws-panel/plesk-hook.sh -priority 50 -user root -event <event>:
domain_*,site_*,subdomain_*,site_subdomain_*,domain_alias_*andsite_alias_*for create, delete and update (andstatus_updateandmovewhere Plesk has them), andphys_hosting_create,phys_hosting_update,phys_hosting_delete;- the certificate events
ssl_web_binding_update,ssl_web_mail_binding_update,ssl_mail_binding_updateandssl_panel_binding_update; client_status_update,client_delete,reseller_status_updateandreseller_delete(a customer or reseller suspended, activated or deleted).
The script is the DirectAdmin hook script: it creates the empty file
/var/lib/scalws-panel/sync-requested, runs systemctl start --no-block scalws-panel-sync.service with an empty environment and exits 0. The path unit
scalws-panel-sync-request.path runs one more sync when a request arrives during a sync
(at most 10 a minute); a sync that runs longer than 15 minutes is stopped.
A change in Plesk is served within seconds; the 5-minute timer is the fallback.
Plesk gives a new handler an id. ScalWS reads the ids from Plesk’s database (the rows of
event_handlers that are new and whose command is the hook script) and records them in
state.json; disable deletes those that still run the hook script (an id Plesk has
given to another handler since is left alone), and also any other handler that runs the
hook script. A handler runs the hook script when its command is exactly the script’s path
(spaces and quotes around it aside); a handler of your own whose command only names the
script among other words is never deleted. When Plesk’s handlers cannot be listed, none is
deleted. When a handler cannot be deleted, or the handlers cannot be listed, the hook
script stays (a handler left behind still finds it)
and the message says what may remain. A failed enable deletes the handlers the same
way, also when their ids could not be read. The script is the only file ScalWS keeps
there; it is root’s and not writable by the console.
What is served
| Plesk | ScalWS |
|---|---|
| Hosting of a domain, additional domain or subdomain | One site per domain: the document root (www_root, which must stay below the subscription’s home directory under /var/www/vhosts), the domain’s PHP-FPM pool, the names <domain>, www.<domain> and ipv4.<domain>. |
Active domain aliases with web (web='true') | Without seoRedirect: the names <alias> and www.<alias> of the site, and <label>.<alias> for a subdomain. |
Aliases that redirect to the domain (seoRedirect='true', Plesk’s default) | A site of their own for <alias> and www.<alias>: a 301 to the domain (over HTTPS when it redirects there), keeping the path; ACME challenges are answered. A subdomain’s <label>.<alias> redirects the same way to the subdomain. |
Redirect from HTTP to HTTPS (sslRedirect) | Redirect from HTTP to HTTPS (ACME challenges excepted). |
| Suspended domain, subscription or parent domain | The suspension page. |
| Forwarding hostings (standard or frame) | Not served; panel status lists them as skipped. |
| The server’s hostname and unknown names | The default site from /var/www/vhosts/default/htdocs, with the server certificate. |
Webmail (webmail.<domain>) | See below. |
Per-domain additional nginx or Apache directives, Apache-only features (mod_fcgid
handlers, CGI, Perl, Python, ASP) and .htaccess features ScalWS does not support are not
translated; sites that depend on them need to stay on nginx and Apache.
PHP
Each domain uses the pool Plesk runs for it: the socket named in the pool file below
/opt/plesk/php/<version>/etc/php-fpm.d/ (/var/www/vhosts/system/<domain>/php-fpm.sock),
running as the subscription’s system user. The suffixes .php, .phtml, .inc and the
other script suffixes always go to PHP-FPM and are never served as files. A domain with PHP
on but without a usable pool gets a socket that never exists: its scripts are refused (an
error, never their source) and panel status warns.
Webmail
Plesk serves Roundcube through Apache and mod_fcgid. ScalWS serves webmail.<domain> (and
webmail.<alias> for each alias with mail) for each domain that has a webmail
configuration and whose webmail is not turned off in Plesk, from
/usr/share/psa-roundcube, and runs its PHP through the unit
scalws-panel-webmail.service: a PHP-FPM master with the PHP binary Plesk’s webmail
configuration names (for example /opt/plesk/php/8.3/sbin/php-fpm), the ini file
/etc/psa-webmail/roundcube/php.ini and the user and group of the webmail configuration’s
SuexecUserGroup (for example roundcube_sysuser). The sync writes its configuration
below /var/lib/scalws-panel/webmail/ (root’s alone) and restarts the unit when it
changes (and starts it when it does not run, also after a failure); a PHP binary that
does not exist (that PHP version removed from Plesk) is a warning in panel status that
names it. Webmail is served over HTTPS only, as Plesk does: plain HTTP redirects to HTTPS.
One pool runs the webmail of every domain: the user, group and PHP handler most domains’
Apache webmail configurations share (on a tie, those of the first domain by name). A
domain whose configuration is missing, cannot be read, runs as root or names another user,
group or handler loses only its own webmail names, and panel status names it. The user
and group must exist and be neither uid 0 nor gid 0, else webmail is not served (a
warning). An open_basedir, disable_functions or disable_classes value of the ini file
that the pool cannot take (a quote, ; or $ in it) keeps the webmail from starting: no
configuration is written and panel status says why.
A webmail name is never taken from another site: not from a domain or alias Plesk knows
(also a forwarding domain or one without hosting), nor from a site file of your own
outside /etc/scalws/sites.d/panel/ (panel status warns). When the check or a reload
still blames the webmail site through one of its names, only that name is left out.
Roundcube’s internals (/config, /logs, /vendor, /bin, /SQL, /program/actions,
the enigma plugin’s key store /plugins/enigma/home, its documentation files and .ht
files) are not served, and, as Roundcube’s own rules do, neither are files
without an extension (/CHANGELOG, /INSTALL, /README and the like, answered with
403); /, its scripts, its assets and its installer stay. enable starts the unit, waits
for its socket and syncs again; disable stops and disables it.
Certificates
The certificate of a domain (Let’s Encrypt or uploaded; the combined file that holds the
key and certificate and, when there is one, the chain) is copied as one combined PEM to
/var/lib/scalws-panel/certs/ and listed in /etc/scalws/panel/certificates.yaml, as on
DirectAdmin. Only the files named in Plesk’s certificates table that are plain files
below /opt/psa/var/certificates/ are used: no symbolic link is followed, and each file is
read as a regular file of at most 1 MiB. The server certificate that nginx’s server.conf
names is the default for names that no site matches; a domain without a certificate of
its own uses it too, as on Plesk (the IP address’s default certificate). ScalWS does not
order certificates: issue and renew them in Plesk (a renewal fires a certificate event).
Plesk’s Let’s Encrypt extension writes the HTTP-01 challenges of every domain to
/var/www/vhosts/default/htdocs/.well-known/acme-challenge/. Every Plesk site (domains,
webmail and the hostname) serves /.well-known/acme-challenge/<token> from there, ahead
of its other paths and over plain HTTP also where the site redirects to HTTPS, so that
issuing and renewing in Plesk works while panel mode is on. Files whose name starts with
a dot are not served there. When the server user cannot open that directory, the route is
left out and panel status warns.
Turn it off
sudo scalwsctl panel disabledeletes the event handlers it registered (and any other handler that runs the hook
script) and the hook script, stops the webmail unit, stops the sync timer, takes ScalWS off
ports 80 and 443, removes the drop-in, enables and starts nginx (a held nginx is left
stopped and disabled) and waits until it holds port 80, removes the group membership it
added and removes
the generated files. PHP-FPM stays on, because it works under nginx and Apache;
--restore-php puts the recorded handlers back on the domains and service plans that
still exist with the recorded id (a deleted one is skipped; one deleted and created again
under the same name is left alone with a warning). Removing the package while panel mode is on runs
disable first. A state written by panel enable before 2026-10-27 also handed apache2
over: disable removes its drop-in too and enables and starts it before nginx.
SCALWS_PANEL=none (--panel=none at install) keeps the install from taking over; the
package prints sudo scalwsctl panel enable plesk instead.
Taking over at the first install
The first install of the package on a server with cPanel, DirectAdmin or Plesk runs
scalwsctl panel enable <panel> by itself, when the panel’s own web server (or nothing)
holds ports 80 and 443, and prints the result. Only the first install does this: it needs
the package manager’s first-install signal and the absence of the root-owned marker in
/var/lib/scalws-pkg, so an upgrade, a reinstall over the installed package or an
install after panel disable never turns panel mode on again. Removing the package
deletes the marker: an install after the package was purged (deb) or removed (rpm) is a
first install again and takes over, unless you pass --panel=none. If enable fails,
it has undone its steps, the package stays installed and the message names the command
to retry. On DirectAdmin a PHP slot that is not php-fpm stops the automatic enable:
the package stays installed, ScalWS stopped, and the message names the da build
command above. A server without a panel is unchanged and ScalWS never stops another web
server there.
Install with --panel=none (the installer passes SCALWS_PANEL=none; set it yourself
when you install the package directly, after sudo: sudo SCALWS_PANEL=none apt-get install scalws) to skip the takeover. On a panel server the package never starts ScalWS
on ports 80 and 443 next to the panel’s web server: when it does not take over
(SCALWS_PANEL=none, an upgrade, a failed enable) it prints the command that does.
Details: installation.
Troubleshooting
-
sudo scalwsctl panel statusshows the last sync, the skipped domains, the warnings and the last error; start there. -
systemctl status scalws-panel-syncandjournalctl -u scalws-panel-syncshow the last sync run, whether the timer, the path unit (cPanel) or a hook (DirectAdmin, Plesk) started it. -
DirectAdmin, a change is not served within seconds: check that the three hook files exist and are executable (
ls -l /usr/local/directadmin/scripts/custom/*/scalws-panel-sync.sh), then runsudo scalwsctl panel syncby hand. The timer picks the change up within 5 minutes. -
DirectAdmin, the old web server runs although panel mode is on: its line in
services.statusmust beOFF(grep -E '^(httpd|nginx|lshttpd|lsws)=' /usr/local/directadmin/data/admin/services.status) and/etc/systemd/system/<unit>.service.d/scalws-panel.confmust exist. If either is missing, runpanel disableandpanel enableagain. -
DirectAdmin, the package was removed and
panel disablefailed (the removal prints the steps): DirectAdmin’s web server stays down until you give it the ports back:rm -f /etc/systemd/system/httpd.service.d/scalws-panel.conf # and nginx, lshttpd, lsws systemctl daemon-reload # set httpd=ON (or nginx, lshttpd, lsws) in # /usr/local/directadmin/data/admin/services.status systemctl enable --now httpdWithout
/usr/bin/scalwsctlthe drop-in no longer stops the unit, so the first two steps only tidy up. -
Plesk, a change is not served within seconds: list the handlers (
plesk bin event_handler --list; they run/var/lib/scalws-panel/plesk-hook.sh), check that the script exists and is executable, then runsudo scalwsctl panel syncby hand. The timer picks the change up within 5 minutes. -
Plesk, nginx runs although panel mode is on (every sync warns): the drop-in
/etc/systemd/system/nginx.service.d/scalws-panel.confmust exist. If it is missing, runpanel disableandpanel enableagain. Apache runs behind it on 127.0.0.1 only, as it should. -
Plesk, the package was removed and
panel disablefailed (the removal prints the steps): nginx stays down until you give it the ports back, and the event handlers still run the hook script (theapache2lines only for a panel mode turned on before 2026-10-27, which handed Apache over too):plesk db -Ne "SELECT id,command FROM event_handlers" | grep /var/lib/scalws-panel/plesk-hook.sh plesk bin event_handler --delete <id> # for each id listed rm -f /var/lib/scalws-panel/plesk-hook.sh # once none is left rm -f /etc/systemd/system/nginx.service.d/scalws-panel.conf /etc/systemd/system/apache2.service.d/scalws-panel.conf systemctl daemon-reload systemctl enable --now apache2 # only if its drop-in was there systemctl enable --now nginx systemctl disable --now scalws-panel-webmail.service -
The PHP scripts of a site answer an error: its PHP-FPM pool is missing or not running;
panel statusnames the domain. -
enablefails: the message names the step and what was undone.panel enable <panel> --dry-runshows the preconditions without changing anything.