Script to Add a WordPress Plugin to Single or Multiple Domains

Script to Add a Wordpress Plugin to Single or Multiple Domains

You do not need root access to install a WordPress plugin from cPanel Terminal. If your hosting provider gives you Terminal or SSH access and WP-CLI is installed, you can check, install and activate a WordPress.org plugin from the command line.

The reusable Bash script in this guide works inside a normal cPanel account. It can process one WordPress site, several specified sites, or automatically scan the account for WordPress installations.

What The Script Does

  • Checks whether the supplied directory contains a working WordPress installation.
  • Skips the site when the selected plugin is already active.
  • Activates the plugin when it is installed but inactive.
  • Downloads, installs and activates the plugin when it is missing.
  • Prints errors immediately in cPanel Terminal.
  • Saves timestamped error logs inside your cPanel home directory.
  • Prevents the script from processing directories outside your own cPanel account.

Requirements

  • Access to cPanel Terminal or SSH.
  • WP-CLI installed and available through the wp command.
  • Permission to modify the WordPress files in your cPanel account.
  • The WordPress.org slug of the plugin you want to install.
  • A current backup before making changes across several websites.

Step 1: Open cPanel Terminal

Log in to cPanel and open Terminal, or connect to the cPanel account through SSH. Do not use sudo with the commands in this guide.

Confirm your cPanel username and home directory:

whoami
echo "$HOME"

The home directory will normally look similar to:

/home/username

Step 2: Confirm WP-CLI Is Available

Run the following command:

wp --info

If WP-CLI is available, the command will display information about WP-CLI, PHP and the server environment.

For a WordPress site installed in public_html, confirm WP-CLI can detect it:

wp --path="$HOME/public_html" core version

Step 3: Find The WordPress Document Root

The primary domain is normally installed in $HOME/public_html. An addon domain may use a directory such as $HOME/example.com.

Search the cPanel account for WordPress configuration files:

find "$HOME" -maxdepth 3 -type f -name wp-config.php -print

Example results:

/home/username/public_html/wp-config.php
/home/username/example.com/wp-config.php

The corresponding WordPress document roots are:

/home/username/public_html
/home/username/example.com

Step 4: Create The cPanel Plugin Installer

Copy the complete command below, paste it into cPanel Terminal and press Enter. It creates the installer in $HOME/wp-plugin-installer, applies safe permissions and checks the Bash syntax without changing WordPress.

Step 5: Find The WordPress.org Plugin Slug

set -e

mkdir -p "$HOME/wp-plugin-installer/logs"
chmod 700 "$HOME/wp-plugin-installer"
chmod 700 "$HOME/wp-plugin-installer/logs"

cat > "$HOME/wp-plugin-installer/install-wp-plugin.sh" <<'EOF'
#!/usr/bin/env bash

set -uo pipefail
umask 077
shopt -s nullglob

SCRIPT_DIR="$HOME/wp-plugin-installer"
LOG_DIR="$SCRIPT_DIR/logs"

usage() {
    cat <<USAGE
Usage:
  $0 <plugin-slug> [wordpress-path ...]

Examples:
  $0 pubsubhubbub
  $0 pubsubhubbub "$HOME/public_html"
  $0 pubsubhubbub "$HOME/public_html" "$HOME/example.com"

With no WordPress path, the script scans public_html and immediate
domain-named directories below your cPanel home directory.
USAGE
}

if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
    usage
    exit 0
fi

if (( $# < 1 )); then
    usage >&2
    exit 2
fi

PLUGIN_SLUG="$1"
shift

if [[ ! "$PLUGIN_SLUG" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
    printf '[FATAL] Invalid plugin slug: %s\n' "$PLUGIN_SLUG" >&2
    printf 'Use only lowercase letters, numbers and hyphens.\n' >&2
    exit 2
fi

mkdir -p "$LOG_DIR"

RUN_ID="$(date '+%Y%m%d-%H%M%S')"
ERROR_LOG="$LOG_DIR/${PLUGIN_SLUG}-errors-$RUN_ID.log"
LATEST_ERROR_LOG="$LOG_DIR/${PLUGIN_SLUG}-latest-errors.log"
: > "$ERROR_LOG"
ln -sfn "$(basename "$ERROR_LOG")" "$LATEST_ERROR_LOG"

WP_CLI_BIN="$(command -v wp 2>/dev/null || true)"
if [[ -z "$WP_CLI_BIN" ]]; then
    printf '[FATAL] WP-CLI was not found in PATH.\n' | tee -a "$ERROR_LOG" >&2
    exit 1
fi

HOME_REAL="$(readlink -f -- "$HOME" 2>/dev/null || true)"
if [[ -z "$HOME_REAL" || ! -d "$HOME_REAL" ]]; then
    printf '[FATAL] Could not resolve your cPanel home directory.\n' | tee -a "$ERROR_LOG" >&2
    exit 1
fi

auto_scan=0
if (( $# > 0 )); then
    CANDIDATES=("$@")
else
    auto_scan=1
    CANDIDATES=("$HOME/public_html" "$HOME"/*
fi

declare -A SEEN=()

candidates=0
checked=0
already_active=0
activated=0
installed=0
errors=0

record_error() {
    local site="$1"
    local stage="$2"
    local detail="${3:-No command output was returned.}"

    ((errors += 1))
    printf '[ERROR] %s -- %s\n' "$site" "$stage" >&2
    printf '        %s\n' "$detail" >&2

    {
        printf '[%s] SITE: %s\n' "$(date '+%F %T')" "$site"
        printf 'PLUGIN: %s\n' "$PLUGIN_SLUG"
        printf 'STAGE: %s\n' "$stage"
        printf '%s\n\n' "$detail"
    } >> "$ERROR_LOG"
}

run_wp() {
    "$WP_CLI_BIN" \
        --path="$CURRENT_SITE" \
        --skip-plugins \
        --skip-themes \
        --no-color \
        "$@"
}

printf 'cPanel WordPress plugin run started: %s\n' "$(date '+%F %T')"
printf 'cPanel user: %s\n' "$(id -un)"
printf 'Plugin slug: %s\n\n' "$PLUGIN_SLUG"

for requested_path in "${CANDIDATES[@]}"; do
    if [[ ! -d "$requested_path" ]]; then
        if (( auto_scan == 0 )); then
            record_error "$requested_path" "Directory check" "Directory does not exist."
        fi
        continue
    fi

    candidate="$(readlink -f -- "$requested_path" 2>/dev/null || true)"
    if [[ -z "$candidate" ]]; then
        record_error "$requested_path" "Path check" "Could not resolve the directory path."
        continue
    fi

    case "$candidate" in
        "$HOME_REAL"/*) ;;
        *)
            record_error "$candidate" "Path safety check" "The directory is outside your cPanel home directory."
            continue
            ;;
    esac

    if (( auto_scan == 1 )); then
        name="${candidate##*/}"
        if [[ "$name" != "public_html" && ! "$name" =~ ^[A-Za-z0-9][A-Za-z0-9.-]*\.[A-Za-z]{2,63}$ ]]; then
            continue
        fi
    fi

    if [[ -n "${SEEN[$candidate]+present}" ]]; then
        continue
    fi
    SEEN["$candidate"]=1
    ((candidates += 1))

    if [[ ! -f "$candidate/wp-config.php" ]]; then
        record_error "$candidate" "WordPress detection" "wp-config.php was not found; directory was not processed."
        continue
    fi

    CURRENT_SITE="$candidate"
    ((checked += 1))

    if ! output="$(run_wp core is-installed 2>&1)"; then
        record_error "$candidate" "WordPress validation" "${output:-wp core is-installed returned a non-zero status.}"
        continue
    fi

    if run_wp plugin is-active "$PLUGIN_SLUG" >/dev/null 2>&1; then
        ((already_active += 1))
        printf '[SKIP - ACTIVE] %s\n' "$candidate"
        continue
    fi

    if run_wp plugin is-installed "$PLUGIN_SLUG" >/dev/null 2>&1; then
        if output="$(run_wp plugin activate "$PLUGIN_SLUG" 2>&1)"; then
            ((activated += 1))
            printf '[ACTIVATED] %s\n' "$candidate"
        else
            record_error "$candidate" "Plugin activation" "$output"
        fi
    else
        if output="$(run_wp plugin install "$PLUGIN_SLUG" --activate 2>&1)"; then
            ((installed += 1))
            printf '[INSTALLED + ACTIVATED] %s\n' "$candidate"
        else
            record_error "$candidate" "Plugin installation/activation" "$output"
        fi
    fi
done

printf '\n%s\n' '================ SUMMARY ================'
printf 'Plugin:                    %s\n' "$PLUGIN_SLUG"
printf 'Candidate document roots:  %d\n' "$candidates"
printf 'WordPress sites checked:   %d\n' "$checked"
printf 'Already active/skipped:    %d\n' "$already_active"
printf 'Existing plugin activated: %d\n' "$activated"
printf 'Installed and activated:   %d\n' "$installed"
printf 'Errors:                    %d\n' "$errors"
printf 'Error log: %s\n' "$ERROR_LOG"
printf 'Latest error log: %s\n' "$LATEST_ERROR_LOG"

((errors == 0)) || exit 1
exit 0
EOF

chmod 700 "$HOME/wp-plugin-installer/install-wp-plugin.sh"

bash -n "$HOME/wp-plugin-installer/install-wp-plugin.sh"

echo "The cPanel plugin installer was created successfully."

The script accepts a WordPress.org plugin slug rather than the plugin’s displayed name. The slug is the final part of the plugin’s WordPress.org URL.

For example, the WebSub plugin URL is:

https://wordpress.org/plugins/pubsubhubbub/

Its plugin slug is therefore:

pubsubhubbub

This version accepts official WordPress.org plugin slugs containing lowercase letters, numbers and hyphens. It does not accept premium plugin ZIP files or arbitrary download URLs.

Install A Plugin On The Primary Domain

If WordPress is installed in public_html, supply the plugin slug followed by the document root. This example installs WebSub:

"$HOME/wp-plugin-installer/install-wp-plugin.sh" \
    pubsubhubbub \
    "$HOME/public_html"

Install A Plugin On An Addon Domain

If the addon domain’s document root is $HOME/example.com, run:

"$HOME/wp-plugin-installer/install-wp-plugin.sh" \
    pubsubhubbub \
    "$HOME/example.com"

Install A Plugin On Several Domains

You can supply several WordPress document roots in the same command:

"$HOME/wp-plugin-installer/install-wp-plugin.sh" \
    pubsubhubbub \
    "$HOME/public_html" \
    "$HOME/example.com" \
    "$HOME/anotherdomain.com"

Only the specified WordPress installations will be processed.

Automatically Scan The cPanel Account

When no WordPress path is supplied, the script checks $HOME/public_html and immediate domain-named directories below $HOME:

"$HOME/wp-plugin-installer/install-wp-plugin.sh" pubsubhubbub

The automatic scan recognises layouts such as:

/home/username/public_html
/home/username/example.com
/home/username/anotherdomain.net

If an addon domain uses a nested or nonstandard document root, provide its full path explicitly instead of relying on the automatic scan.

Install A Different WordPress Plugin

Replace pubsubhubbub with the slug from the required plugin’s WordPress.org URL:

"$HOME/wp-plugin-installer/install-wp-plugin.sh" \
    plugin-slug \
    "$HOME/public_html"

View The Help Screen

"$HOME/wp-plugin-installer/install-wp-plugin.sh" --help

Understanding The Status Messages

SKIP – ACTIVE

[SKIP - ACTIVE] /home/username/public_html

The selected plugin is already installed and active. No changes were made.

ACTIVATED

[ACTIVATED] /home/username/public_html

The plugin was already installed but inactive. The script activated it.

INSTALLED + ACTIVATED

[INSTALLED + ACTIVATED] /home/username/public_html

The plugin was missing, so the script downloaded, installed and activated it.

ERROR

[ERROR] /home/username/public_html -- Plugin activation

The WordPress site or plugin operation failed. The error appears in Terminal and is also written to the plugin’s error log. The script continues checking the remaining sites.

Review The Error Logs

All error logs are stored under:

$HOME/wp-plugin-installer/logs

Display the most recent WebSub error log with:

cat "$HOME/wp-plugin-installer/logs/pubsubhubbub-latest-errors.log"

List every saved log with:

ls -lh "$HOME/wp-plugin-installer/logs"

Can The Script Be Run Again?

Yes. The script is designed to be rerun. Websites where the selected plugin is already active are skipped automatically.

The script does not update a plugin that is already installed and active. Its purpose is to detect, install and activate the selected plugin while producing a clear record of any WordPress installations that could not be processed.

Using The Script Across Separate cPanel Accounts

A normal cPanel user can only modify files owned by that account. If your websites are spread across separate cPanel accounts, log in to each account individually, create the installer under that account’s home directory and run it from that account’s Terminal.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *