Published on May 26 2026 in Directadmin Softaculous Non-Java

Make Softaculous Use DirectAdmin’s Selected PHP Version

Softaculous may detect the server’s default PHP version instead of the PHP version selected for the target domain in DirectAdmin. The result is confusing: Softaculous can reject an installation or upgrade because it thinks PHP is too old, even though phpinfo() for the domain shows a newer version.

This workaround makes Softaculous ask DirectAdmin’s domain configuration for the effective PHP version before install/upgrade checks run.

Overview

The solution has three parts:

  1. A root-owned helper script that returns the PHP version for one domain or subdomain.
  2. A sudo wrapper that allows DirectAdmin users to call the helper safely.
  3. A Softaculous hook that defines php_version from the helper result.

The helper returns only a numeric version such as:

8.1
8.2
8.3

1. Install The DirectAdmin PHP Version Helper

Save this as in your preferred location for such utility scripts e.g.:

/path/to/da/helpers/da_domain_php_version.sh
#!/bin/bash
set -u

OPTIONS_CONF=/usr/local/directadmin/custombuild/options.conf
DOMAINOWNERS=/etc/virtual/domainowners
DA_USERS_DIR=/usr/local/directadmin/data/users

usage() {
    printf 'Usage: %s DOMAIN_OR_SUBDOMAIN\n' "$0" >&2
}

die() {
    printf 'ERROR: %s\n' "$*" >&2
    exit 1
}

php_version_for_slot() {
    local slot=$1 ver

    ver=$(awk -F= -v k="php${slot}_select" '$1 == k {print $2; exit}' "$OPTIONS_CONF")
    if [ -z "$ver" ]; then
        ver=$(awk -F= -v k="php${slot}_release" '$1 == k {print $2; exit}' "$OPTIONS_CONF")
    fi

    [ -n "$ver" ] || die "php${slot}_release not found in $OPTIONS_CONF"
    printf '%s\n' "$ver"
}

url_decode() {
    local raw=${1//+/ }
    printf '%b' "${raw//%/\\x}"
}

[ "$#" -eq 1 ] || {
    usage
    exit 2
}

name=${1%.}
[ -n "$name" ] || die "empty domain"
[ -r "$OPTIONS_CONF" ] || die "cannot read $OPTIONS_CONF"
[ -r "$DOMAINOWNERS" ] || die "cannot read $DOMAINOWNERS"

domain=
username=
candidate=$name

while :; do
    username=$(awk -F':[[:space:]]*' -v d="$candidate" '$1 == d {print $2; exit}' "$DOMAINOWNERS")
    if [ -n "$username" ]; then
        domain=$candidate
        break
    fi

    case "$candidate" in
        *.*) candidate=${candidate#*.} ;;
        *) break ;;
    esac
done

[ -n "$domain" ] || exit 1

sudo_user=${SUDO_USER:-}
if [ -n "$sudo_user" ] && [ "$username" != "$sudo_user" ]; then
    exit 1
fi

domain_conf="$DA_USERS_DIR/$username/domains/$domain.conf"
[ -r "$domain_conf" ] || die "cannot read $domain_conf"

domain_slot=$(awk -F= '$1 == "php1_select" {print $2; exit}' "$domain_conf")
[ -n "$domain_slot" ] || domain_slot=1

if [ "$name" = "$domain" ]; then
    php_version_for_slot "$domain_slot"
    exit 0
fi

sub_prefix=${name%."$domain"}
[ -n "$sub_prefix" ] && [ "$sub_prefix" != "$name" ] || die "$name is not a subdomain of $domain"

override_file="$DA_USERS_DIR/$username/domains/$domain.subdomains.docroot.override"
if [ -r "$override_file" ]; then
    raw_config=$(awk -F= -v s="$sub_prefix" '$1 == s {sub(/^[^=]*=/, ""); print; exit}' "$override_file")
    if [ -n "$raw_config" ]; then
        decoded_config=$(url_decode "$raw_config")
        sub_slot=$(
            printf '%s\n' "$decoded_config" |
                awk '
                    match($0, /php[1-9]_select=[1-9]/) {
                        v = substr($0, RSTART, RLENGTH)
                        sub(/^.*=/, "", v)
                        print v
                        exit
                    }
                    match($0, /(^|[&?])php=[1-9]/) {
                        v = substr($0, RSTART, RLENGTH)
                        sub(/^.*=/, "", v)
                        print v
                        exit
                    }
                '
        )
        [ -n "$sub_slot" ] || sub_slot=$domain_slot
        php_version_for_slot "$sub_slot"
        exit 0
    fi
fi

standard_file="$DA_USERS_DIR/$username/domains/$domain.subdomains"
if [ -r "$standard_file" ] && awk -v s="$sub_prefix" '$0 == s {found=1} END {exit !found}' "$standard_file"; then
    php_version_for_slot 1
    exit 0
fi

die "subdomain not found for $name"

Make it executable:

chmod 755 /path/to/da/helpers/da_domain_php_version.sh

The script follows DirectAdmin’s PHP-selection rules:

2. Add A Safe User-Facing Wrapper

DirectAdmin users cannot read all DirectAdmin domain config files directly, so provide a very narrow sudo rule and a wrapper command.

Adjust the helper path if you install it somewhere else.

cat > /etc/sudoers.d/domain-php-version <<'EOF'
Defaults!/path/to/da/helpers/da_domain_php_version.sh !requiretty
ALL ALL = (root) NOPASSWD: /path/to/da/helpers/da_domain_php_version.sh
EOF

cat > /usr/local/bin/domain-php-version <<'EOF'
#!/bin/bash
exec sudo /path/to/da/helpers/da_domain_php_version.sh "$@"
EOF

chmod 755 /usr/local/bin/domain-php-version

Test as root:

/path/to/da/helpers/da_domain_php_version.sh example.com

Test as a DirectAdmin user:

sudo -u USER /usr/local/bin/domain-php-version example.com

If example.com belongs to another DirectAdmin user, the helper exits silently with error code 1.

3. Add The Softaculous Hook

Softaculous hooks live here:

/usr/local/directadmin/plugins/softaculous/enduser/hooks/

Create PHP hook files from the templates if they do not already exist:

cd /usr/local/directadmin/plugins/softaculous/enduser/hooks/
cp -n pre_install.txt pre_install.php
cp -n pre_upgrade.txt pre_upgrade.php

In both pre_install.php and pre_upgrade.php, add this block under the comment that says:

// Do stuff here e.g. is as follows
if (!empty($_REQUEST['softdomain'])) {
    $domain_input = trim($_REQUEST['softdomain']);
    $domain_pattern = '/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i';

    if (preg_match($domain_pattern, $domain_input)) {
        $version_raw = exec('/usr/local/bin/domain-php-version ' . $domain_input, $lines, $rc);
        $version = trim($version_raw);

        if ($rc === 0 && preg_match('/^\d+\.\d+(?:\.\d+)*$/', $version)) {
            define('php_version', $version);
        }
    }
}

The domain is validated before being passed to the wrapper. The regex allows only letters, digits, dots, and dashes, so no extra shell quoting is needed for this restricted value.

4. Verify

Run the wrapper as the DirectAdmin user who owns the domain:

sudo -u USER /usr/local/bin/domain-php-version example.com

Expected output:

8.2

Then retry a Softaculous install or upgrade for that domain. Softaculous should now evaluate the PHP requirement using the version selected in DirectAdmin rather than the server-wide default PHP version.

Notes