This is part of a periodic installment of happenings on Discord, a community chat staffed by the developers and curated by its users.

Tracing API commands

Basing the API on SOAP is often much lamented and misunderstood. SOAP is a clunky interface compared to REST, which shares the same HTTP transport layer, but encapsulates its payload on top of XML. One enormous benefit of SOAP, however, is its API integrity amid fluidity. Surrogates can alter visibility, which may change module signatures ("Providers" do not modify module signatures). APNSCP regenerates its WSDL (or schema) on boot taking into account loaded surrogates. A WSDL provides a strict definition of services offered by an endpoint and how those parameters should be formatted. REST doesn't publish such a definition, leaving API details - including data structures - in the dark. Compare for example the WSDLs generated by Hostineer, which is a whitelabel brand of APNSCP and the official demo on Paixhans. Hostineer WSDL is 1,008 KB. Paixhans is a mere 958 KB. SOAP allows structure to morph while ensuring integrity.

Let's look at a simple surrogate that adds a new method called "soap" on the "test" module. This module is only available in debug mode, which can be enabled on-the-fly.

Create the following file in lib/modules/surrogates/test.php.

<?php declare(strict_types=1);

class Test_Module_Surrogate extends \Test_Module {
    public function soap($fn, ...$args) {
        $key = getenv('KEY');
        $uri = 'http://localhost:2082/soap';
        $wsdl = 'http://localhost:2082/apnscp.wsdl';
        $connopts = array(
                'connection_timeout' => 30,
                'location'           => $uri,
                'uri'                => 'urn:apnscp.api.soap',
                'trace'              => true
        $connopts['location'] = $uri . '?authkey=' . $key;
        $client = new class($wsdl, $connopts) extends \SoapClient {
            public function __doRequest($request, $location, $action, $version, $one_way = 0)
                echo "REQUEST:\n", $request, "\n\n";
                $resp = parent::__doRequest($request, $location, $action, $version,
                echo "RESPONSE:\n", $resp, "\n\n-----------------\n";
                return $resp;
        $fn = str_replace([':','-'], '_', $fn);
        return $client->$fn(...$args);

Create an API key within the panel via Dev > API Keys. Run the following command to see the XML payload as it's sent to and received from the API server.

env DEBUG=1 KEY=key-from-dev-keys-in-panel test:soap common:whoami

Congrats on creating your very first API command. Now if you restart APNSCP in debug mode you’ll see the WSDL is updated with the new “test_soap” command.

Evasive maneuvers

Let's say a system is under a deluge of connections from an offending IP or a dozen and conventional threshold-activated blacklisting through mod_evasive fails to fire. How do we implement a routine to examine all open connections to the server and ban the most egregious candidates?

A little shell scripting to the rescue! Name this file /usr/local/sbin/


# Block IPs with over 100 connections open to server
function find_spam {
        netstat -nptu | egrep 'TIME_W|SYN_' | awk '{print $5}' | sort -n | cut -d: -f1 | sort -n \
                | uniq -c | sort -n | tail -n 5 \
                | egrep '^[[:space:]]*[[:digit:]][[:digit:]][[:digit:]][[:digit:]]*'

# ban excessive apache connections
function ban_spam {
        MYIPS=($(ip -o addr | awk '!/^[0-9]*: ?link\/ether/ {gsub("/", " "); print $4}'))
        find_spam | awk '{print $2}' | while read IP ; do
                [[ "${MYIPS[@]}" =~ "${IP}" ]] && continue
                echo "Banning $IP"
                /sbin/iptables -A INPUT -s $IP  -j DROP


Just run when load creeps beyond acceptable levels or better yet, add this as an action to Monit under your physical checks. For example, in /etc/monit.d/physical.conf add a loadavg check beneath the "check system SYSNAME" group,

if loadavg (1min) > 50 then exec "/usr/local/sbin/"

Then restart Monit, systemctl restart monit.

Deciphering failed integrity reports

Integrity reports run every month and scrub your system for anomalies. Any changes are reported back in digest form that should clue you in on exactly what has changed, rectifying any changes along the way. Periodically you may see a failed integrity check with no log and a nonsensical duration.

Partially failed integrity report

This happens when the APNSCP PHP version updates during integrity checks, which triggers a restart of the process group. Abrupt termination forces Ansible to become disassociated from the group and we end up with an incomplete log file that APNSCP tries summarize. There are some ideas to workaround this including spinning off the worker into its own session leader that should resolve this; however, that is work for another time.

Importing SSL

Are there docs on manually installing SSL for a customer? I have a customer that wants to use an EV cert. They've opened a ticket as instructed but I don't know how to tie it into the panel

Two options, programmatically via cpcmd -d domain ssl:install or you can do it the old fashioned way: move the key in siteXX/fst/etc/httpd/conf/ssl.key/server.key, CRT as server.crt in ssl.crt/, chain in ssl.crt/bundle.crt, then in /etc/httpd/conf/siteXX.ssl/, create a file named custom with:

SSLCertificateChainFile /home/virtual/siteXX/fst/etc/httpd/conf/ssl.crt/bundle.crt

Followed by a configuration rebuild and reload: htrebuild && systemctl reload httpd

Alternatively, the API method ssl:install does this automatically. Arguments are key file, certificate, and optional bundle:

cpcmd -d ssl:install "$(cat /path/to/server.key)" "$(cat /path/to/server.crt)" "$(cat /path/to/bundle.crt)"

SSL will activate in 2 minutes or less depending upon what [httpd] => reload_delay is set to in config.ini.

But a greater question exists, why bother with EV when EV certificates are dead?