[Linux / Apache] Build a WordPress site and enable SSL with a single command line! How to create an automated script using Apache + WP-CLI

Introduction

Long time no see!
a chick 🐤, an engineer . I
call myself a newbie, but it's already my third year...
Anyway, I've recently become interested in creating scripts.
So,
I've created a script that lets you create a WordPress site for testing, which always takes up a surprising amount of time during verification, with just one command!

About the script

overview

This scriptan automated script that completes everything from building a new WordPress site to enabling SSL in one go.
It automates the time-consuming and laborious process of setting up a web server, making it easy to launch a website.

■ Main processing flow (execution details)

  1. Creating a database and users for WordPress
    → Create a database and individual users based on your domain name and grant them the necessary access permissions.
  2. Creating a document root
    automatically creates the directory structure for web publication (/var/www/vhosts/...).
  3. Downloading, installing, and initially configuring WordPress
    → Using WP-CLI, we'll handle everything in one go, from obtaining the core files and generating wp-config.php to registering the initial administrator account.
  4. Creating a virtual host (HTTP/HTTPS)
    → Automatically generates VirtualHost configuration files for Apache for port 80 (HTTP) and port 443 (HTTPS).
  5. Obtaining a Let's Encrypt certificate
    → Use Certbot to obtain a Let's Encrypt certificate and apply it to the configuration file path.

* If the script is run multiple times for the same domain, already configured items will be automatically skipped, and
  only items that are not configured or have been updated will be processed.

Prerequisites/Environment

This script is designed based on the following testing environment:

item Conditions/Environment
OS AlmaLinux 10.1
DB MySQL 8.4.8
Web server Apache + PHP-FPM
Executing user root
DNS settings The A record for the target domain must be pointing to the relevant server
Required Tools WP-CLI, Certbot, MySQL Client

* Even if certificate acquisition (step 8) fails due to reasons such as DNS not being propagated, only the certificate acquisition step will be skipped, and the HTTP environment settings will be applied and the site itself will be built.
(An error is prevented by applying a default dummy certificate to the HTTPS conf✨)

In that case, you can still perform certificate acquisition and SSL implementation later by re-running the same procedure after DNS settings and propagation!

Source code

Here is the script!
However, it's quite long and the detailed processing may be difficult to understand, so I will explain it in the next section.

#!/bin/bash # =================================================== # Individual settings (please change according to your environment) # =================================================== # --- DB connection information --- DB_HOST="DB IP address or localhost" DB_ADMIN_USER="DB administrator user" DB_ADMIN_PW="DB administrator password" DB_ADMIN_DB="database name" # --- Common settings for the WordPress admin screen --- WP_USER="administrator user" WP_PW="administrator password" WP_EMAIL="administrator email address" # --- Certbot settings --- CERTBOT_BIN="/usr/bin/certbot" CERT_BASE_DIR="/etc/letsencrypt/live" # Path to default certificate DEFAULT_CERT="/etc/pki/tls/certs/localhost.crt" DEFAULT_KEY="/etc/pki/tls/private/localhost.key" # --- WP-CLI --- WP_CLI="php -d memory_limit=512M /usr/local/bin/wp --allow-root" # --- Argument check (domain name and DB password) --- for arg in "$@"; do case $arg in --domain=*) DOMAIN="${arg#*=}" ;; --pass=*) DB_INDIVIDUAL_PW="${arg#*=}" ;; esac done # --- Validation --- if [ -z "${DOMAIN}" ] || [ -z "${DB_INDIVIDUAL_PW}" ]; then echo "Error: Missing arguments." echo "How to use: sudo /usr/local/TOOLS/bin/wordpress.sh --domain='xxxxxx' --pass='xxxxxxxx'" exit 1 fi echo "=== Creating a new site / Starting SSL implementation ===" # Flags RELOAD_APACHE=false RESTART_APACHE=false # =================================================== # Function definition # =================================================== # Function for error handling error_exit() { echo "--------------------------------------------------" echo "!!! An error occurred and processing has been interrupted !!!" echo "[Error location]: $1" echo "[Error details]: $2" echo "--------------------------------------------------" exit 1 } # Function to create Apache configuration file generate_conf() { local TYPE=$1 local CONF_PATH=$2 local TMP_FILE=$(mktemp /tmp/${DOMAIN}_tmp_${TYPE}.conf.XXXXXX) if [ "$TYPE" == "HTTP" ]; then cat <<EOF > "${TMP_FILE}"<VirtualHost *:80> ServerName ${DOMAIN} DocumentRoot "${DOC_ROOT}" DirectoryIndex index.php index.html index.xml<Directory "${DOC_ROOT}" > Options +FollowSymLinks -Indexes AllowOverride all Require all granted</Directory> CustomLog "/var/log/httpd/${DOMAIN}-access_log" combined ErrorLog "/var/log/httpd/${DOMAIN}-error_log"<FilesMatch \.(php|phar|html)$> SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost"</FilesMatch></VirtualHost> EOF else cat <<EOF > "${TMP_FILE}"<VirtualHost *:443> ServerName ${DOMAIN} DocumentRoot "${DOC_ROOT}" DirectoryIndex index.php index.html index.xml<Directory "${DOC_ROOT}" > Options +FollowSymLinks -Indexes AllowOverride all Require all granted</Directory> CustomLog "/var/log/httpd/${DOMAIN}-access_ssl_log" combined ErrorLog "/var/log/httpd/${DOMAIN}-error_ssl_log" SSLEngine on SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite HIGH:!aNULL:!eNULL:!MD5:!3DES:!RC4:!DES SSLHonorCipherOrder on SSLCertificateFile ${DEFAULT_CERT} SSLCertificateKeyFile ${DEFAULT_KEY}<FilesMatch \.(php|phar|html)$> SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost"</FilesMatch></VirtualHost> EOF fi # Check for the existence and differences of the configuration file if [ -f "${CONF_PATH}" ]; then if diff -q "${CONF_PATH}" "${TMP_FILE}" > /dev/null; then echo ">> [SKIP] The Apache ${TYPE} configuration file already exists and its contents have not changed" rm "${TMP_FILE}" return else echo ">> [UPDATE] The contents of the Apache ${TYPE} configuration file have been updated" fi else echo ">> [NEW] The Apache ${TYPE} configuration file has been created" fi sudo mv "${TMP_FILE}" "${CONF_PATH}" if [ $? -ne 0 ]; then error_exit "Failed to place Apache configuration file" "failed to mv ${CONF_PATH}" fi sudo chmod 644 "${CONF_PATH}" # Flag if [ "$TYPE" == "HTTP" ]; then RELOAD_APACHE=true else RESTART_APACHE=true fi } # =================================================== # Processing details # =================================================== # --- Applying naming conventions --- # 1. Convert the dot in the domain to an underscore N_DOMAIN="${DOMAIN//./_}" # 2. Database name = wp_each_domain_name DB_NAME="wp_${N_DOMAIN}" # 3. DB user -= each_domain_name_user DB_USER="${N_DOMAIN}_user" # 4. Document root of the virtual host DOC_ROOT="/var/www/vhosts/${DOMAIN}/public_html" # 5. Site title in WordPress admin panel SITE_TITLE="${DOMAIN}" # 6. Apache configuration file (HTTP) HTTP_CONF="/etc/httpd/conf.d/${DOMAIN}_http.conf" # 7. Apache configuration file (HTTPS) SSL_CONF="/etc/httpd/conf.d/${DOMAIN}_ssl.conf" # 8. For permission adjustment PARENT_DIR=$(dirname "${DOC_ROOT}") echo "--------------------------------------------------" echo "Processing: ${DOMAIN}" # 1 - Creating database and user for WordPress echo "[1/8] Creating DB (skips if already exists)...: ${DB_NAME} / User: ${DB_USER}" DB_ERR=$(mysql -h "${DB_HOST}" -P 3306 -u "${DB_ADMIN_USER}" -p"${DB_ADMIN_PW}" -D "${DB_ADMIN_DB}" 2>&1 < <EOF CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER IF NOT EXISTS '${DB_USER}'@'%' IDENTIFIED BY '${DB_INDIVIDUAL_PW}'; GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${DB_USER}'@'%'; FLUSH PRIVILEGES; EOF ) if [ $? -ne 0 ]; then error_exit "データベース・ユーザー作成処理に失敗しました" "${DB_ERR}" fi # 2 - ドキュメントルートの作成 if [ -d "${DOC_ROOT}" ]; then echo "[2/8] [SKIP] ドキュメントルートは既に存在します: ${DOC_ROOT}" else echo "[2/8] ドキュメントルートの作成中..." MKDIR_ERR=$(sudo mkdir -p "${DOC_ROOT}" 2> &&1) if [ $? -ne 0 ]; then error_exit "Failed to create document root" "${MKDIR_ERR}" fi fi CHOWN_ERR=$(sudo chown -R apache:apache "${PARENT_DIR}" 2>&1) [ $? -ne 0 ] && error_exit "Failed to change owner" "${CHOWN_ERR}" CHMOD_ERR=$(sudo chmod -R 2775 "${PARENT_DIR}" 2>&1) [ $? -ne 0 ] && error_exit "Failed to change permissions" "${CHMOD_ERR}" cd "${DOC_ROOT}" || error_exit "Failed to move to directory" "Cannot cd to ${DOC_ROOT}" # 3 - Downloading WordPress if [ -f "wp-load.php" ]; then echo ''3/8] [SKIP] WordPress file already exists" else echo ''3/8] Downloading WordPress..." WP_DL_ERR=$(${WP_CLI} core download --locale=ja 2>&1) [ $? -ne 0 ] && error_exit "WordPress download failed" "${WP_DL_ERR}" fi # 4 - Creating wp-config.php if [ -f "wp-config.php" ]; then echo ''4/8] [SKIP] wp-config.php already exists" else echo ''4/8] Creating wp-config.php..." WP_CFG_ERR=$(${WP_CLI} config create \ --dbname="${DB_NAME}" \ --dbuser="${DB_USER}" \ --dbpass="${DB_INDIVIDUAL_PW}" \ --dbhost="${DB_HOST}" \ --dbcharset="utf8mb4" \ --dbcollate="utf8mb4_unicode_ci" 2>&1) [ $? -ne 0 ] && error_exit "Failed to create wp-config.php" "${WP_CFG_ERR}" fi # 5 - Installing WordPress if ${WP_CLI} core is-installed 2>/dev/null; then echo "[5/8] [SKIP] WordPress is already installed" else echo "[5/8] WordPress installation process in progress..." WP_INST_ERR=$(${WP_CLI} core install \ --url="https://${DOMAIN}" \ --title="${SITE_TITLE}" \ --admin_user="${WP_USER}" \ --admin_password="${WP_PW}" \ --admin_email="${WP_EMAIL}" \ --skip-email 2>&1) if [ $? -ne 0 ]; then error_exit "WordPress installation failed" "${WP_INST_ERR}" fi fi # 6 - Apache HTTP configuration echo ">"6/8] Checking Apache HTTP configuration..." generate_conf "HTTP" "${HTTP_CONF}" # Reload only if new or updated if [ "$RELOAD_APACHE" = true ]; then echo ">> Checking HTTP configuration changes. Reloading Apache..." if sudo httpd -t; then sudo systemctl reload httpd RELOAD_APACHE=false else error_exit "Apache syntax error" "See details with 'sudo httpd -t'" fi fi # 7 - Apache SSL configuration echo ">"7/8] Creating SSL configuration file..." generate_conf "SSL" "${SSL_CONF}" # 8 - Certbot Certificate acquisition if [ -d "${CERT_BASE_DIR}/${DOMAIN}" ]; then echo "8/8] [SKIP] Certificate already obtained: ${DOMAIN}" else # Run Dry-run echo "8/8] Obtaining a new Let's Encrypt certificate..." echo ">> Running a test because the certificate has not been obtained..." certbot_out_test=$(sudo "${CERTBOT_BIN}" certonly --webroot -w "${DOC_ROOT}" \ -d "${DOMAIN}" \ --non-interactive \ --agree-tos \ --register-unsafely-without-email \ --dry-run 2>&1) if [ $? -ne 0 ]; then echo "!!! Warning: Dry-run failed, skipping certificate acquisition" "${certbot_out_test}" else echo ">> Obtaining Let's Encrypt certificate..." certbot_out=$(sudo "${CERTBOT_BIN}" certonly --webroot -w "${DOC_ROOT}" \ -d "${DOMAIN}" \ --non-interactive \ --agree-tos \ --register-unsafely-without-email 2>&1) if [ $? -ne 0 ]; then echo "!!!Warning: An error occurred! Skipping certificate acquisition" "${certbot_out}" else echo ">> Successfully acquired certificate" fi fi fi # Replace certificate path in configuration file (Execute only if certificate has been acquired and not replaced) if [ -d "/etc/letsencrypt/live/${DOMAIN}" ]; then if [ -f "${SSL_CONF}" ]; then # Check if not replaced if grep -q "${DEFAULT_CERT}" "${SSL_CONF}"; then echo ">> Replacing configuration file path..." # Take backup sudo cp -p "${SSL_CONF}" "${SSL_CONF}.bak" # Replace sudo sed -i "s|${DEFAULT_CERT}|${CERT_BASE_DIR}/${DOMAIN}/fullchain.pem|g" "${SSL_CONF}" sudo sed -i "s|${DEFAULT_KEY}|${CERT_BASE_DIR}/${DOMAIN}/privkey.pem|g" "${SSL_CONF}" # Check for differences if sudo diff "${SSL_CONF}.bak" "${SSL_CONF}" > /dev/null; then echo ">> [SKIP] The path to the SSL configuration file is already correct or no file to replace was found" sudo rm "${SSL_CONF}.bak" else echo ">> [SUCCESS] ${DOMAIN}_ssl.conf The certificate path has been updated. RESTART_APACHE=true sudo rm "${SSL_CONF}.bak" fi else echo ">> [SKIP] The SSL configuration file has already been replaced." fi else echo "!!! Warning: SSL configuration file not found (${SSL_CONF})" fi fi # Final permission adjustment sudo find "${PARENT_DIR}" -type d -exec chmod 2775 {} + sudo find "${PARENT_DIR}" -type f -exec chmod 664 {} + if [ $? -ne 0 ]; then error_exit "Failed to adjust directory permissions." "failed to find" fi # Apply settings if [ "$RESTART_APACHE" = true ]; then echo "--------------------------------------------------" echo "Configuration update detected, restarting will be performed." if sudo httpd -t; then sudo systemctl restart httpd echo "All processes completed!" " else echo "!!!Warning: There is an error in the Apache configuration. Restart aborted!!!" exit 1 fi else echo "--------------------------------------------------" echo "No configuration changes were needed, so the process will now end." fi

 

Commentary

This script requires you to specify some settings to suit your environment!
I will briefly explain the "parameters to specify in the pre-configuration" and "the processing details of all 8 steps that will be executed."

1. Setting items

→ Before installing the script, rewrite the variables at the beginning to match your server environment!

Setting items variable name Description/Specifications
DB administrator information DB_HOST
DB_ADMIN_USER
DB_ADMIN_PW
Specify the connection information for an account that has database creation privileges (such as root)
WP initial administrator WP_USER
WP_PW
WP_EMAIL
Specify the login information for the initial administrator that will be set when creating the WordPress site
Certbot / Certificate DEFAULT_CERT
DEFAULT_KEY
Specify the path to the temporary certificate (such as a self-signed certificate) on the server that will be used when initially creating the SSL certificate
WP-CLI command WP_CLI The execution path for WP-CLI.
--allow-root allows root execution.

2. Execution details of each process

* Please adjust the document root path, virtual host configuration, etc., as needed to match your environment!

[1/8] DB/User Creation
→ Automatically generates a DB name (wp_xxx) and username (xxx_user) from the domain name, and then creates the DB and user and grants permissions.
*Example (if --domain='example.com'):
DB name: wp_example_com, Username: example_com_user

[2/8] Create document root
→ Create /var/www/vhosts/domain name/public_html

[3/8] Download WordPress
→ Download the WordPress core files using WP-CLI.

[4/8] wp-config.php Create
→ Automatically generate configuration file based on the DB connection information set in Step 1.

[5/8] WordPress Installation
→ Register the site title, initial administrator account, and site URL (https) and then proceed with the installation.

[6/8] Apache HTTP configuration (port 80)
→ Generate VirtualHost configuration file.
Compare with existing file, and perform syntax check and Apache reload only if changes are found.

[7/8] Apache SSL configuration (port 443)
→ To mitigate errors when DNS has not yet propagated, generate a VirtualHost configuration file specifying the path to the temporary certificate.

[8/8] Obtain Let's Encrypt certificate and update the path
→ After confirming connectivity with Certbot's Dry-run (test), if the production certificate is
successfully obtained, replace the path in the configuration file fullchain.pem and restart Apache.

Installation procedure

1. Installing certbot

dnf install certbot

2. Download
the WP-CLI setup :

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

Behavior check:

php wp-cli.phar --info

☑ Confirm that WP-CLI and the current environment information are displayed in a list

Granting execution permissions:

chmod 755 wp-cli.phar

Move to /usr/local/bin/:

mv wp-cli.phar /usr/local/bin/wp

Testing the operation of the wp command:

wp --version --allow-root

☑ Verify that the WP-CLI version is displayed, such as "WP-CLI x.xx.xx"

3. Create a directory for placing scripts

mkdir -p /usr/local/TOOLS/bin/

4. Install the script

vi /usr/local/TOOLS/bin/wordpress.sh

→ Copy and paste the prepared source code into the file "wordpress.sh" and save it

Granting execution permissions:

chmod 755 /usr/local/TOOLS/bin/wordpress.sh

5. Execute

/usr/local/TOOLS/bin/wordpress.sh --domain='domain name' --pass='password'

→ Specify the domain name to be created and the individual DB password, then execute.
☑ Confirm that the message "All processing is complete!" is displayed.

■ Results from actual testing

=== Creating a new site / Starting SSL implementation === -------------------------------------------------- Processing: example.com [1/8] Creating DB (skipping if already exists)...: wp_example_com / User: example_com_user [2/8] Creating document root... [3/8] Downloading WordPress... [4/8] Creating wp-config.php... [5/8] Performing WordPress installation... [6/8] Checking Apache HTTP settings... >> [NEW] Apache HTTP configuration file created >> Confirming HTTP settings changes. Reloading Apache... Syntax OK [7/8] Creating SSL configuration file... >> [NEW] Apache SSL configuration file created [8/8] Obtaining a new Let's Encrypt certificate... >> Test run as certificate not yet obtained... >> Obtaining Let's Encrypt certificate... >> Certificate obtained successfully >> Replacing configuration file path... >> [SUCCESS] Certificate path in example.com_ssl.conf updated -------------------------------------------------- Configuration update detected, restarting will be performed Syntax OK All processes completed!

Finally

I apologize for the lengthy explanation.
Since this was created by an individual, there may be some imperfections in the processing specifications, so please bear with me.

This time, we introduced a basic batch creation script, but
by modifying this script, you can also create batch files for multiple sites in CSV format, etc., so please try improving it to suit your needs!
In the future, I hope to create more scripts that are useful and make everyday work a little easier 🐤

Thank you for reading to the end!

If you found this article helpful,please give it a "Like"!
1
1
3
X Facebook Hatena Bookmark pocket

The person who wrote this article

About the author

Chick

I'm a new graduate engineer who joined the company in 2024. I
have a liberal arts background and no prior experience. I'm in the Systems Solutions Department.