Skip to content

Instantly share code, notes, and snippets.

@eusonlito
Created September 2, 2025 15:12
Show Gist options
  • Save eusonlito/4e547c1616b9982e55fbf7fa76112e47 to your computer and use it in GitHub Desktop.
Save eusonlito/4e547c1616b9982e55fbf7fa76112e47 to your computer and use it in GitHub Desktop.
Setup script (Ubuntu 22.04 + Apache + PHP 8.4 + MySQL)
#!/usr/bin/env bash
set -Eeuo pipefail
### CONFIG #################################################################
PHP_VERSION="8.4"
SYSTEM_LOCALE="en_US.UTF-8" # final user is English-speaking
TIMEZONE="${TIMEZONE:-UTC}" # override by exporting TIMEZONE before running
USE_PHP_FPM="1" # 1 = Apache + PHP-FPM (recommended), 0 = mod_php
MYSQL_APT_DEB="mysql-apt-config_0.8.34-1_all.deb" # latest as of 2025-09-02
###########################################################################
echo "==> Checking for root privileges..."
if [ "$EUID" -ne 0 ]; then
echo "This script must be run as root."
exit 1
fi
trap 'echo "[ERROR] Failed at line $LINENO"; exit 1' ERR
export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_MODE=a # auto-restart services when safe
echo "==> Refreshing APT & installing base tools..."
apt-get update -o Dpkg::Use-Pty=0
apt-get install -y -o Dpkg::Use-Pty=0 \
software-properties-common ca-certificates lsb-release apt-transport-https \
locales tzdata curl wget gnupg debconf-utils
echo "==> Configuring locale and timezone..."
if ! grep -q "^${SYSTEM_LOCALE}" /etc/locale.gen; then
echo "${SYSTEM_LOCALE} UTF-8" >> /etc/locale.gen
fi
locale-gen "${SYSTEM_LOCALE}"
update-locale LANG="${SYSTEM_LOCALE}" LC_ALL="${SYSTEM_LOCALE}"
if [ -n "${TIMEZONE}" ]; then
timedatectl set-timezone "${TIMEZONE}" || true
fi
echo "==> Adding Ondřej Surý PPAs (Apache & PHP)..."
add-apt-repository -y ppa:ondrej/apache2
add-apt-repository -y ppa:ondrej/php
apt-get update -o Dpkg::Use-Pty=0
apt-get -y -o Dpkg::Use-Pty=0 full-upgrade
############################################################################
# Oracle MySQL APT repo (fixed version, no parsing) #
############################################################################
echo "==> Adding Oracle MySQL APT repo (non-interactive, fixed version)..."
wget -qO /tmp/mysql-apt-config.deb "https://dev.mysql.com/get/${MYSQL_APT_DEB}"
# Preseed to select the LTS server line when available (avoids TUI)
echo "mysql-apt-config mysql-apt-config/select-server select mysql-8.4-lts" | debconf-set-selections || true
echo "mysql-apt-config mysql-apt-config/enable-repo select Enabled" | debconf-set-selections || true
dpkg -i /tmp/mysql-apt-config.deb || apt-get -f install -y -o Dpkg::Use-Pty=0
apt-get update -o Dpkg::Use-Pty=0
############################################################################
# Apache + PHP #
############################################################################
echo "==> Installing Apache and PHP ${PHP_VERSION}..."
if [ "${USE_PHP_FPM}" = "1" ]; then
apt-get install -y -o Dpkg::Use-Pty=0 \
apache2 \
"php${PHP_VERSION}-fpm" "php${PHP_VERSION}-cli" "php${PHP_VERSION}-common" \
"php${PHP_VERSION}-opcache" "php${PHP_VERSION}-readline"
else
apt-get install -y -o Dpkg::Use-Pty=0 \
apache2 \
"libapache2-mod-php${PHP_VERSION}" "php${PHP_VERSION}-cli" "php${PHP_VERSION}-common" \
"php${PHP_VERSION}-opcache" "php${PHP_VERSION}-readline"
fi
echo "==> Installing PHP extensions (full set from your original script)..."
apt-get install -y -o Dpkg::Use-Pty=0 \
"php${PHP_VERSION}" \
"php${PHP_VERSION}-bcmath" \
"php${PHP_VERSION}-bz2" \
"php${PHP_VERSION}-cli" \
"php${PHP_VERSION}-common" \
"php${PHP_VERSION}-curl" \
"php${PHP_VERSION}-gd" \
"php${PHP_VERSION}-fpm" \
"php${PHP_VERSION}-imagick" \
"php${PHP_VERSION}-intl" \
"php${PHP_VERSION}-memcached" \
"php${PHP_VERSION}-mbstring" \
"php${PHP_VERSION}-mysql" \
"php${PHP_VERSION}-opcache" \
"php${PHP_VERSION}-pgsql" \
"php${PHP_VERSION}-readline" \
"php${PHP_VERSION}-redis" \
"php${PHP_VERSION}-tidy" \
"php${PHP_VERSION}-xml" \
"php${PHP_VERSION}-soap" \
"php${PHP_VERSION}-xsl" \
"php${PHP_VERSION}-zip" \
php-apcu \
php-imagick \
php-memcached \
php-redis \
memcached \
redis-server
echo "==> Enabling PHP modules when present..."
if [ -f "/etc/php/${PHP_VERSION}/mods-available/sockets.ini" ]; then
phpenmod -v "${PHP_VERSION}" sockets
fi
if [ -f "/etc/php/${PHP_VERSION}/mods-available/apcu.ini" ]; then
phpenmod -v "${PHP_VERSION}" apcu
fi
if [ -f "/etc/php/${PHP_VERSION}/mods-available/redis.ini" ]; then
phpenmod -v "${PHP_VERSION}" redis
fi
echo "==> Apache hardening and modules..."
if [ -f /etc/apache2/conf-enabled/security.conf ]; then
SEC="/etc/apache2/conf-enabled/security.conf"
else
if [ -f /etc/apache2/conf.d/security ]; then
SEC="/etc/apache2/conf.d/security"
else
SEC=""
fi
fi
if [ -n "${SEC}" ]; then
sed -i -E \
-e 's/^\s*#?\s*ServerTokens\s+.*/ServerTokens Prod/' \
-e 's/^\s*#?\s*ServerSignature\s+.*/ServerSignature Off/' \
-e 's/^\s*#?\s*TraceEnable\s+.*/TraceEnable Off/' \
"${SEC}"
fi
a2enmod deflate expires filter headers include proxy_fcgi rewrite setenvif socache_shmcb ssl
if [ "${USE_PHP_FPM}" = "1" ]; then
if a2query -m mpm_prefork >/dev/null 2>&1; then
a2dismod mpm_prefork
fi
a2enmod mpm_event
a2enconf "php${PHP_VERSION}-fpm"
else
if a2query -m mpm_event >/dev/null 2>&1; then
a2dismod mpm_event
fi
a2enmod "php${PHP_VERSION}" mpm_prefork
fi
echo "==> Applying PHP tuned settings (mods-available/99-custom.ini)..."
CUSTOM_MOD="/etc/php/${PHP_VERSION}/mods-available/99-custom.ini"
cat > "${CUSTOM_MOD}" <<EOF
; Custom PHP settings
expose_php = Off
max_execution_time = 180
max_input_time = 240
memory_limit = 512M
upload_max_filesize = 200M
post_max_size = 2G
max_input_vars = 10000
date.timezone = ${TIMEZONE}
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0
EOF
phpenmod -v "${PHP_VERSION}" -s ALL 99-custom
############################################################################
# MySQL server #
############################################################################
echo "==> Installing MySQL Server..."
apt-get install -y -o Dpkg::Use-Pty=0 mysql-server
echo "==> Securing MySQL root account..."
MYSQL_ROOT_PASSWORD="$(openssl rand -base64 24 | tr -d '\n' | cut -c1-32)"
mysql --protocol=socket -uroot <<SQL || true
ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY '${MYSQL_ROOT_PASSWORD}';
FLUSH PRIVILEGES;
SQL
cat > /root/.my.cnf <<EOF
[client]
user=root
password=${MYSQL_ROOT_PASSWORD}
EOF
chmod 600 /root/.my.cnf
############################################################################
# Final checks and restarts #
############################################################################
echo "==> Verifying key directories..."
if [ ! -d /etc/apache2 ]; then
echo "Apache installation missing."
exit 1
fi
if [ ! -d "/etc/php/${PHP_VERSION}" ]; then
echo "PHP ${PHP_VERSION} installation missing."
exit 1
fi
if [ ! -d /etc/mysql ]; then
echo "MySQL installation missing."
exit 1
fi
echo "==> Enabling & restarting services..."
systemctl enable --now apache2
if [ "${USE_PHP_FPM}" = "1" ]; then
systemctl enable --now "php${PHP_VERSION}-fpm"
fi
systemctl restart apache2
echo
echo "Finished."
echo "MySQL root password saved at /root/.my.cnf (chmod 600)."
echo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment