A simple way to put WordPress on Debian/Ubuntu

WordPress is not evil, as many developers tend to say. Actually, given it’s ease of use especially for non-techy users, it’s a strong choice when it comes to building a non-static website. Therefore, we will learn about an easy way to install it on a Debian based system.

The information in the current article can be run on any LAMP based configuration, but that on which it will be tested is described here:

Prerequisites and specs

In the context of the current article, we will assume the following:

  • The LAMP server / Virtualmin host is already setup;
  • We already know the database connection information needed to setup WordPress.

We will want to install some plugins as well:

Manual installation

Manual installation of WordPress is surprisingly straightforward, irrespective of the variety of options your server may present.

One approach could be to download the latest WP version, unzip it locally and the upload the files to the server via FTP. In the context of Virtualmin, the whole archive could be uploaded to the server and then unzipped there, directly. We could also SSH into our host and perform the download directly there.

Once the WP files are on the server, we can access the URL of the website to run the installation script. Those are some easy steps, out of which the most technical probably is providing the database connection credentials.

As the install script ran smoothly, the next step will be to download and install the plugins mentioned above. This is a slightly tedious process and it’s the main reason why I prefer an automated approach.

More info:

Automating the process

We will mainly use bash to download WordPress, the plugin list and any theme we might want. A bash script which removes itself at the end is the cleanest approach I can think of for our use case.

We will try to keep the code as clean as possible, with clean and pure functions to do repetitive tasks, clearly defined variables and a logical structure to the things we want to do:

  • Check the required utils (wget, mysql, unzip);
  • Ask the user for credentials to the database and test them;
  • Download the latest version of WordPress and the predefined plugins and themes;
  • Another important step is to update the wp-config.php file;
  • We will also save a list of the plugins in a custom file, which we can use with the script later, should we need that.
#!/bin/bash

# Configuration
DEFAULT_DB_ADDRESS="localhost"
DEFAULTS_FILE=".wp_install_defaults"

# Load defaults from file if it exists
if [ -f "$DEFAULTS_FILE" ]; then
    IFS=';' read -r -a PLUGINS THEMES < "$DEFAULTS_FILE"
else
    PLUGINS=(
        "https://downloads.wordpress.org/plugin/password-protected.2.6.5.1.zip" 
        "https://downloads.wordpress.org/plugin/wordpress-seo.21.5.zip" 
        "https://downloads.wordpress.org/plugin/wp-dashboard-notes.1.0.10.zip" 
        "https://downloads.wordpress.org/plugin/sucuri-scanner.1.8.39.zip" 
        "https://downloads.wordpress.org/plugin/simple-page-ordering.2.6.3.zip" 
        "https://downloads.wordpress.org/plugin/regenerate-thumbnails.3.1.6.zip" 
        "https://downloads.wordpress.org/plugin/litespeed-cache.5.7.0.1.zip"  
        "https://downloads.wordpress.org/plugin/login-recaptcha.1.7.1.zip"
        "https://downloads.wordpress.org/plugin/limit-login-attempts-reloaded.2.25.26.zip" 
        "https://downloads.wordpress.org/plugin/just-an-admin-button.1.2.0.zip" 
        "https://downloads.wordpress.org/plugin/instant-images.6.1.0.zip" 
        "https://downloads.wordpress.org/plugin/health-check.1.7.0.zip" 
        "https://downloads.wordpress.org/plugin/wpcf7-redirect.3.0.1.zip" 
        "https://downloads.wordpress.org/plugin/contact-form-cfdb7.zip" 
        "https://downloads.wordpress.org/plugin/contact-form-7.5.8.3.zip"
        "https://downloads.wordpress.org/plugin/child-theme-wizard.1.4.zip"
    )
    THEMES=()
fi

# Color constants
RED="0;31"
GREEN="0;32"
YELLOW="0;33"
BLUE="0;34"

# Function for colored echo messages
colored_echo() {
    local COLOR_CODE="$1"
    local MESSAGE="$2"
    local OPTIONS="${@:3}"

    echo -e $OPTIONS "\033[${COLOR_CODE}m${MESSAGE}\033[0m"
}

# Function to test database credentials
test_db_credentials() {
    local DB_NAME="$1"
    local DB_USER="$2"
    local DB_PASS="$3"
    local DB_ADDRESS="$4"

    # Attempt to connect to the database
    if mysql -h "$DB_ADDRESS" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "quit" 2>/dev/null; then
        return 0  # Success
    else
        return 1  # Failure
    fi
}

# Function to check if a string is a URL
is_url() {
    local URL="$1"
    [[ $URL =~ ^https?:// ]]
}

# Unified function to download and unzip files
download_and_unzip() {
    local URL="$1"
    local DESTINATION="$2"
    local FILE_NAME=$(basename "$URL")

    colored_echo $GREEN "Downloading $URL..."
    if ! wget -q -O "$FILE_NAME" "$URL"; then
        colored_echo $RED "Failed to download $FILE_NAME. Exiting."
        
        if [ -f "$FILE_NAME" ]; then
            rm "$FILE_NAME"
        fi

        return 1
    fi

    if ! unzip -q "$FILE_NAME" -d "$DESTINATION"; then
        colored_echo $RED "Failed to unzip $FILE_NAME."
        
        if [ -f "$FILE_NAME" ]; then
            rm "$FILE_NAME"
        fi

        return 1
    fi

    rm "$FILE_NAME"

    return 0
}

# Function to create and update wp-config.php
create_wp_config() {
    local DB_NAME="$1"
    local DB_USER="$2"
    local DB_PASS="$3"
    local DB_ADDRESS="$4"
    
    local WP_CONFIG_FILE="wordpress/wp-config.php"
    local WP_SAMPLE_CONFIG_FILE="wordpress/wp-config-sample.php"

    # Copy the sample config file to wp-config.php
    cp "$WP_SAMPLE_CONFIG_FILE" "$WP_CONFIG_FILE"

    # Replace database details in wp-config.php
    sed -i "s/database_name_here/$DB_NAME/g" "$WP_CONFIG_FILE"
    sed -i "s/username_here/$DB_USER/g" "$WP_CONFIG_FILE"
    sed -i "s/password_here/$DB_PASS/g" "$WP_CONFIG_FILE"
    sed -i "s/localhost/$DB_ADDRESS/g" "$WP_CONFIG_FILE"

    return 0
}

# Update .htaccess to ignore the defaults file
update_htaccess() {
    local HTACCESS_FILE="wordpress/.htaccess"
    if [ -f "$HTACCESS_FILE" ]; then
        if ! grep -q ".wp_install_defaults" "$HTACCESS_FILE"; then
            echo -e "\n# Ignore .wp_install_defaults file\n<Files .wp_install_defaults>\n\tOrder allow,deny\n\tDeny from all\n</Files>" >> "$HTACCESS_FILE"
        fi
    fi
}

# Ensure the script is not running as root
if [ "$(id -u)" == "0" ]; then
    colored_echo $RED "This script should not be run as root for security reasons."
    exit 1
fi

# Check for required utilities
REQUIRED_UTILS=("wget" "mysql" "unzip")
for UTIL in "${REQUIRED_UTILS[@]}"; do
    if ! command -v $UTIL &> /dev/null; then
        colored_echo $RED "The required utility $UTIL is not installed. Please install it and rerun this script."
        exit 1
    fi
done

# Check if the WordPress directory already exists
if [ -d "wordpress" ]; then
    colored_echo $RED "Error: A 'wordpress' directory already exists. Please remove or rename it before running this script."
    exit 1
fi

# Main script logic
echo

# Ask for database details
colored_echo $YELLOW "Provide the database connection credentials"

read -p "Enter database address [default: $DEFAULT_DB_ADDRESS]: " DB_ADDRESS
DB_ADDRESS=${DB_ADDRESS:-$DEFAULT_DB_ADDRESS}
read -p "Enter database name: " DB_NAME
read -p "Enter database username (leave empty to use '$DB_NAME' as username): " DB_USER
DB_USER=${DB_USER:-$DB_NAME}
read -sp "Enter database password: " DB_PASS
echo

# Test database credentials
if ! test_db_credentials "$DB_NAME" "$DB_USER" "$DB_PASS" "$DB_ADDRESS"; then
    colored_echo $RED "Invalid database credentials or database name. Exiting."
    exit 1
else
    colored_echo $GREEN "The provided credentials are valid."
fi

colored_echo $YELLOW "Proceed with installation? (y/n): " -n
read -n 1 CONFIRM
echo

if [ "$CONFIRM" != "y" ]; then
    colored_echo $RED "Installation aborted."
    exit 1
fi

# Download WordPress, themes, and plugins
download_and_unzip "https://wordpress.org/latest.zip" "."

for PLUGIN in "${PLUGINS[@]}"; do
    if is_url "$PLUGIN"; then
        download_and_unzip "$PLUGIN" "wordpress/wp-content/plugins"
    else
        colored_echo $RED "${PLUGIN} is not a valid URL."
    fi
done

for THEME in "${THEMES[@]}"; do
    if is_url "$THEME"; then
        download_and_unzip "$THEME" "wordpress/wp-content/themes"
    else
        colored_echo $RED "${THEME} is not a valid URL."
    fi
done

# Create wp-config.php (basic example, further configuration may be required)
create_wp_config "$DB_NAME" "$DB_USER" "$DB_PASS" "$DB_ADDRESS"

# Update .htaccess
update_htaccess

# Prompt Nginx users for manual configuration
colored_echo $BLUE "If you are using Nginx, please manually update the server block to deny access to .wp_install_defaults."

# Save the plugins and themes to the defaults file
echo "${PLUGINS[*]};${THEMES[*]}" > "$DEFAULTS_FILE"
if [ -f "$DEFAULTS_FILE" ]; then
    mv "$DEFAULTS_FILE" "wordpress/$DEFAULTS_FILE"
fi

# Prompt for new path
read -p "Enter a new path for WordPress files or leave empty to keep in the current location: " NEW_PATH
if [ -n "$NEW_PATH" ]; then
    mkdir -p "$NEW_PATH"
    mv wordpress/* "$NEW_PATH/"
    rm -rf wordpress
fi

# Prompt to delete the script
colored_echo $YELLOW "Installation complete. Delete this script? (y/n): " -n
read -n 1 DEL_SCRIPT
echo
if [ "$DEL_SCRIPT" == "y" ]; then
    rm -- "$0"
else
    colored_echo $BLUE "The script file was not deleted."
fi

# End of the script
echo

Once the script was run, you can access the URL of the page and there do what remains of the installation process.

Then you will still have to activate and configure each plugin, but you saved the time needed to search and download them.

A shorthand for installing this command is the following:

  • sudo wget -O /usr/local/bin/install_wp https://raw.githubusercontent.com/cristidraghici/debian-server-bash-scripts/master/install_wp.sh && sudo chmod +x /usr/local/bin/install_wp

Composer as an alternative path

Another way to automate the installation is to use composer.json as a base for your installation. With some tricks and some php and bash scripts, you will be able to run the install with little hustle. Do remember that for security reasons, it would be nice to prevent your server from delivering these files.

One upside of this aproach is that you can easily create a git repository for your website, using a combination of composer.json and .gitignore. Namely, you can add to the git ignore file whatever you have already specified in composer.json. This way, you will easily spot the differences.

Another upside is that you will have easy and good control over the versions inside your website. You will be able to easily reinstall everything with clean downloads from the sources.

wp-cli

We will manage WordPress easier from the command line by using wp-cli. To do that, we will do the following steps:

  • curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
  • php wp-cli.phar --info
  • chmod +x wp-cli.phar
  • mkdir -p ~/bin
  • sudo mv wp-cli.phar ~/bin/wp
  • make sure that ~/bin is set in the path of your shell (e.g. .bashrc or .zshrc)

We can also use a bash script for this operation:

#!/bin/bash

# Define the user's home directory
BIN_DIR="$HOME/bin"
WP_CLI_FILE="$BIN_DIR/wp"

# Check if WP-CLI already exists
if [ -x "$WP_CLI_FILE" ]; then
  echo "WP-CLI is already installed in $BIN_DIR. Installation skipped."
  exit 0
fi

# Download WP-CLI
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar

# Make WP-CLI executable
chmod +x wp-cli.phar

# Create the 'bin' directory if it doesn't exist
mkdir -p $BIN_DIR

# Move WP-CLI to the 'bin' directory
mv wp-cli.phar $WP_CLI_FILE

# Function to update PATH in the specified file
update_path() {
    local file=$1
    if [ -f "$file" ]; then
        if ! grep -q "$BIN_DIR" "$file"; then
            echo 'export PATH="$PATH:$HOME/bin"' >> "$file"
            source "$file"
        fi
    fi
}

# Update PATH in .zshrc and .bashrc
update_path "$HOME/.zshrc"
update_path "$HOME/.bashrc"

# Verify the installation
wp --version

We can also make this a command that is available in the whole system:

  • sudo touch /usr/local/bin/install_wp_cli
  • sudo nano /usr/local/bin/install_wp_cli # and paste the script shown above
  • sudo chmod a+x /usr/local/bin/install_wp_cli

A single command is:

  • sudo wget -O /usr/local/bin/install_wp_cli https://raw.githubusercontent.com/cristidraghici/debian-server-bash-scripts/master/install_wp_cli.sh && sudo chmod +x /usr/local/bin/install_wp_cli

More info: