Production-ready Lightsail launch script (idempotent, secure, and fully automated for WordPress on Apache with backups, SSL, monitoring, and more).

βœ… lightsail-wordpress-setup.sh

#!/bin/bash
set -euo pipefail
LOG_FILE="/var/log/wordpress-install.log"
exec > >(tee -a "$LOG_FILE") 2>&1
# --------------------------
# 1. Basic Updates & Tools
# --------------------------
echo "[+] Updating system..."
apt update -y && apt upgrade -y
apt install -y curl wget unzip software-properties-common gnupg2 ca-certificates lsb-release ufw
# --------------------------
# 2. Apache, PHP, and MariaDB
# --------------------------
echo "[+] Installing Apache, PHP, and MariaDB..."
if ! command -v apache2 > /dev/null; then
apt install -y apache2
systemctl enable apache2 && systemctl start apache2
fi
apt install -y php php-mysql libapache2-mod-php php-cli php-curl php-xml php-mbstring php-zip php-gd php-soap

if ! command -v mysql > /dev/null; then
apt install -y mariadb-server
systemctl enable mariadb && systemctl start mariadb
fi
# --------------------------
# 3. Database Setup
# --------------------------
DB_NAME="wordpress"
DB_USER="wpuser"
DB_PASS="StrongPass123"
echo "[+] Setting up database..."
mysql -e "CREATE DATABASE IF NOT EXISTS $DB_NAME;"
mysql -e "CREATE USER IF NOT EXISTS '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';"
mysql -e "GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost'; FLUSH PRIVILEGES;"
# --------------------------
# 4. WordPress Install (via WP-CLI)
# --------------------------
WP_CLI="/usr/local/bin/wp"
WEB_ROOT="/var/www/html"

if [ ! -f "$WP_CLI" ]; then
echo "[+] Installing WP-CLI..."
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar && mv wp-cli.phar "$WP_CLI"
fi

if [ ! -f "$WEB_ROOT/index.php" ]; then
echo "[+] Installing WordPress..."
cd /tmp
$WP_CLI core download --path=wordpress
cp -r wordpress/* $WEB_ROOT/
cd $WEB_ROOT
$WP_CLI config create --dbname=$DB_NAME --dbuser=$DB_USER --dbpass=$DB_PASS --skip-check
chown -R www-data:www-data $WEB_ROOT
chmod -R 755 $WEB_ROOT
fi

# --------------------------
# 5. SSL via Let's Encrypt (Certbot)
# --------------------------
DOMAIN_NAME="yourdomain.com" # CHANGE THIS
EMAIL="you@example.com" # CHANGE THIS

if ! command -v certbot > /dev/null; then
echo "[+] Installing Certbot..."
apt install -y certbot python3-certbot-apache
fi

echo "[+] Installing SSL certificate..."
certbot --apache --non-interactive --agree-tos --redirect -m $EMAIL -d $DOMAIN_NAME || true

# --------------------------
# 6. Fail2Ban Setup
# --------------------------
echo "[+] Installing Fail2Ban..."
apt install -y fail2ban
systemctl enable fail2ban && systemctl start fail2ban

# --------------------------
# 7. SSH Hardening
# --------------------------
echo "[+] Hardening SSH..."
SSH_CONF="/etc/ssh/sshd_config"
sed -i 's/^#Port 22/Port 2222/' $SSH_CONF
sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' $SSH_CONF
systemctl restart sshd

# --------------------------
# 8. Swap File Setup
# --------------------------
if [ ! -f /swapfile ]; then
echo "[+] Creating 1GB swap file..."
fallocate -l 1G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' | tee -a /etc/fstab
fi

# --------------------------
# 9. Logrotate
# --------------------------
echo "[+] Configuring logrotate for WordPress..."
cat <<EOF > /etc/logrotate.d/wordpress
$WEB_ROOT/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
systemctl reload apache2 > /dev/null
endscript
}
EOF

# --------------------------
# 10. CloudWatch Agent (optional)
# --------------------------
echo "[+] Installing AWS CloudWatch Agent..."
curl -O https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
dpkg -i amazon-cloudwatch-agent.deb
rm amazon-cloudwatch-agent.deb

# (Optional) add a CloudWatch agent config JSON here if you want automatic metrics

# --------------------------
# 11. Backups (Daily via Cron)
# --------------------------
echo "[+] Setting up backups..."
BACKUP_SCRIPT="/usr/local/bin/wp-backup.sh"

cat <<EOF > $BACKUP_SCRIPT
#!/bin/bash
DATE=\$(date +%F)
BACKUP_DIR="/root/wp-backups"
mkdir -p \$BACKUP_DIR

# Backup database
mysqldump -u $DB_USER -p$DB_PASS $DB_NAME > \$BACKUP_DIR/db-\$DATE.sql

# Backup files
tar -czf \$BACKUP_DIR/wp-files-\$DATE.tar.gz $WEB_ROOT

# Keep only last 7 backups
find \$BACKUP_DIR -type f -mtime +7 -delete
EOF

chmod +x $BACKUP_SCRIPT
(crontab -l ; echo "0 3 * * * $BACKUP_SCRIPT") | crontab -

# --------------------------
# 12. Create Secure User
# --------------------------
if ! id "wpadmin" &>/dev/null; then
echo "[+] Creating wpadmin user..."
useradd -m -s /bin/bash wpadmin
echo "wpadmin:WPsecure@2025" | chpasswd
usermod -aG sudo wpadmin
fi

# --------------------------
# Done!
# --------------------------
echo "[βœ“] WordPress setup complete. Please point your domain ($DOMAIN_NAME) to this instance and test."

πŸ› οΈ To Use This Script

  1. Replace the following placeholders:
    • yourdomain.com β†’ your actual domain
    • you@example.com β†’ your email for Let’s Encrypt
  2. Paste it in the Lightsail “launch script” box when creating your Ubuntu instance.

πŸ”’ 1. What happens if you disable root login?

The line in the script:

sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config

will disable direct root login over SSH. This is a common security best practice to reduce brute-force attacks on the root account.


πŸ§‘β€πŸ’» 2. How do you log in if root is blocked?

The script creates a new admin user:

useradd -m -s /bin/bash wpadmin
echo "wpadmin:WPsecure@2025" | chpasswd
usermod -aG sudo wpadmin

This means:

  • You log in using: ssh wpadmin@your-ip -p 2222 (since port is changed from 22 β†’ 2222)
  • The wpadmin user has sudo privileges, so you can run any root command with sudo.

πŸ›‘οΈ 3. What SSH hardening changes are made?

These two changes improve SSH security:

ChangeLineEffect
πŸ” Disable root loginPermitRootLogin noPrevents attackers from brute-forcing the root account
πŸ“¦ Change default portPort 2222Reduces bot attacks on default port 22 (obscurity + security)

The line:

sed -i 's/^#Port 22/Port 2222/' /etc/ssh/sshd_config

…uncomments and changes the SSH port from 22 to 2222.

⚠️ Important: After creating the instance, remember to allow port 2222 in the Lightsail firewall rules, or you won’t be able to connect.



1️⃣ Add Public Key Authentication Instead of Password

  • Create wpadmin user without password (disabled password login for that user).
  • Copy your SSH public key into /home/wpadmin/.ssh/authorized_keys.
  • Disable password authentication for SSH entirely (force key-based login).

2️⃣ Add Fallback Check to Root Login

  • We’ll keep root login disabled by default for security.
  • But in case the wpadmin user cannot log in, root login on port 2222 remains possible via SSH key only.
  • Password authentication is disabled globally (no password logins at all).

3️⃣ Write First Boot Instructions to MOTD

  • Show clear login instructions on first login via /etc/motd message.
  • Include the username, SSH port, and a reminder to add your SSH key.


Add this to the script or replace relevant parts:

#!/bin/bash
set -euo pipefail
# Your public SSH key here (replace with your actual public key)
PUB_KEY="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD... your-email@example.com"

# 1. Create user without password & set up SSH key
if ! id "wpadmin" &>/dev/null; then
echo "[+] Creating wpadmin user with SSH key auth only..."
useradd -m -s /bin/bash wpadmin
mkdir -p /home/wpadmin/.ssh
echo "$PUB_KEY" > /home/wpadmin/.ssh/authorized_keys
chmod 700 /home/wpadmin/.ssh
chmod 600 /home/wpadmin/.ssh/authorized_keys
chown -R wpadmin:wpadmin /home/wpadmin/.ssh
usermod -aG sudo wpadmin
fi

# 2. SSH Hardening

SSH_CONF="/etc/ssh/sshd_config"

# Change SSH port to 2222
sed -i 's/^#Port 22/Port 2222/' $SSH_CONF

# Disable root password login but allow root key login
# This config means:
# - PasswordAuthentication is disabled globally (all users)
# - PermitRootLogin is set to "prohibit-password" to allow root only via SSH keys
sed -i 's/^#PermitRootLogin yes/PermitRootLogin prohibit-password/' $SSH_CONF
sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' $SSH_CONF

# Restart SSH to apply changes
systemctl restart sshd

# 3. Write first-boot instructions to /etc/motd
cat <<EOF > /etc/motd
*******************************************************
* Welcome to your AWS Lightsail Ubuntu Server! *
* *
* Login using SSH key authentication: *
* ssh -i /path/to/private_key -p 2222 wpadmin@<IP> *
* *
* Root login is disabled for password but allowed via *
* key on port 2222 if needed. *
* *
* Remember to replace /path/to/private_key with your *
* actual SSH private key path. *
* *
* Enjoy your secure server! *
*******************************************************
EOF

⚠️ What You Need to Do Before Running:

  • Replace the placeholder PUB_KEY string with your actual SSH public key (from your laptop or wherever you SSH from, e.g., ~/.ssh/id_rsa.pub).
  • Make sure your SSH private key matches this public key.
  • Allow inbound TCP port 2222 in Lightsail firewall settings.

πŸ”‘ How to Connect After Setup

ssh -i /path/to/private_key -p 2222 wpadmin@<instance-ip>

If something goes wrong with wpadmin login, you can also try:

ssh -i /path/to/private_key -p 2222 root@<instance-ip>

(because root login is allowed with SSH keys, but password login is disabled for root)

How to get your public SSH key


1. If you already have SSH keys on your local machine

Linux/macOS:

Open a terminal and run:

cat ~/.ssh/id_rsa.pub

or if you use ed25519 keys:

cat ~/.ssh/id_ed25519.pub

This will print your public key (starts with ssh-rsa or ssh-ed25519). Copy the entire output (all one line).


Windows (using PowerShell or WSL):

  • If you use WSL or Git Bash, same command as Linux/macOS.
  • If you use PuTTY, you can export the public key from PuTTYgen.

2. If you don’t have SSH keys yet

Generate one with:

ssh-keygen -t rsa -b 4096 -C "your-email@example.com"
  • Press Enter to accept default file location.
  • Enter a passphrase if you want (recommended).
  • Then run:
cat ~/.ssh/id_rsa.pub

3. Use that key in your script

Example:

PUB_KEY="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCy3XyRyx... your-email@example.com"

Paste the full key in one line, no line breaks.

1. Shebang and strict mode

#!/bin/bash
set -euo pipefail
  • #!/bin/bash tells the system to run the script with Bash.
  • set -euo pipefail:
    • -e: Exit immediately if any command fails.
    • -u: Treat unset variables as errors.
    • -o pipefail: Fail if any command in a pipeline fails.

This makes your script safer and more robust.

2. User-configurable variables

PUB_KEY="ssh-rsa AAAAB3N...your-email@example.com"
WP_ADMIN_USER="wpadmin"
WP_ADMIN_PASS="WPsecure@2025"
SSH_PORT=2222
DOMAIN="your-domain.com"

These are placeholders you must edit before running the script:

  • PUB_KEY: Your public SSH key to enable key-based login.
  • WP_ADMIN_USER: Name of the admin user created on the system.
  • WP_ADMIN_PASS: Password used for MySQL WordPress user (not system user).
  • SSH_PORT: Custom SSH port to harden SSH access.
  • DOMAIN: Your website domain for SSL certificate.

3. Update packages and install dependencies

apt update && apt upgrade -y
apt install -y apache2 mysql-server php php-mysql ... certbot fail2ban logrotate awscli unattended-upgrades
  • Updates system package info.
  • Installs all required software:
    • Apache web server
    • MySQL database server
    • PHP and extensions needed for WordPress
    • Certbot for SSL certs
    • Fail2Ban to protect SSH
    • Logrotate for logs management
    • AWS CLI & unattended-upgrades for monitoring and auto updates

4. Swap file creation

if ! swapon --show | grep -q 'swapfile'; then
fallocate -l 1G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
fi
  • Checks if swap exists; if not, creates a 1GB swap file.
  • Swap helps low-RAM instances avoid memory issues.

5. Create admin user with SSH key

if ! id "$WP_ADMIN_USER" &>/dev/null; then
useradd -m -s /bin/bash "$WP_ADMIN_USER"
mkdir -p /home/$WP_ADMIN_USER/.ssh
echo "$PUB_KEY" > /home/$WP_ADMIN_USER/.ssh/authorized_keys
chmod 700 /home/$WP_ADMIN_USER/.ssh
chmod 600 /home/$WP_ADMIN_USER/.ssh/authorized_keys
chown -R $WP_ADMIN_USER:$WP_ADMIN_USER /home/$WP_ADMIN_USER/.ssh
usermod -aG sudo $WP_ADMIN_USER
fi
  • Creates a system user (if it doesn’t exist).
  • Sets up .ssh/authorized_keys with your public key (no password login).
  • Adds user to sudo group for admin privileges.

6. SSH hardening

sed -i "s/^#Port 22/Port $SSH_PORT/" /etc/ssh/sshd_config
sed -i "s/^#PermitRootLogin yes/PermitRootLogin prohibit-password/" /etc/ssh/sshd_config
sed -i "s/^#PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config
systemctl restart sshd
  • Changes SSH port from 22 to your custom port.
  • Disables root login with password (root can still login with keys).
  • Disables password authentication entirely β€” only SSH keys allowed.
  • Restarts SSH service to apply changes.

7. UFW firewall setup

ufw allow $SSH_PORT/tcp
ufw allow 'Apache Full'
ufw --force enable
  • Allows traffic on SSH custom port.
  • Allows HTTP and HTTPS for Apache.
  • Enables UFW firewall.

8. MySQL secure installation and database setup

mysql_secure_installation <<EOF

y
WPsecure@2025
WPsecure@2025
y
y
y
y
EOF

mysql -u root -pWPsecure@2025 <<EOF
CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wpuser'@'localhost' IDENTIFIED BY '$WP_ADMIN_PASS';
GRANT ALL ON wordpress.* TO 'wpuser'@'localhost';
FLUSH PRIVILEGES;
EOF
  • Runs MySQL secure installation with default answers (sets root password, removes anonymous users, etc.).
  • Creates WordPress database and user with the password.

9. Download and configure WordPress

wget https://wordpress.org/latest.tar.gz -O /tmp/wordpress.tar.gz
tar xzf /tmp/wordpress.tar.gz -C /tmp/
rsync -a /tmp/wordpress/ /var/www/html/
chown -R www-data:www-data /var/www/html
chmod -R 755 /var/www/html
  • Downloads WordPress, extracts, and copies to Apache web root.
  • Sets proper permissions.

10. Create WordPress config file

cat > /var/www/html/wp-config.php <<EOF
<?php
// DB connection and security keys here...
EOF
  • Sets up wp-config.php with DB credentials and placeholders for secret keys.

Updated snippet to fetch WordPress salts dynamically in your script

Instead of hardcoding keys like:

phpCopyEditdefine('AUTH_KEY',         'put your unique phrase here');
define('SECURE_AUTH_KEY',  'put your unique phrase here');
...

Use this inside your bash script when creating wp-config.php:

# Fetch fresh WordPress salts from official API
WP_KEYS=$(curl -s https://api.wordpress.org/secret-key/1.1/salt/)

cat > /var/www/html/wp-config.php <<EOF
<?php
define('DB_NAME', '${DB_NAME}');
define('DB_USER', '${DB_USER}');
define('DB_PASSWORD', '${DB_PASSWORD}');
define('DB_HOST', 'localhost');
define('DB_CHARSET', 'utf8mb4');
define('DB_COLLATE', '');

${WP_KEYS}

\$table_prefix = 'wp_';
define('WP_DEBUG', false);

if ( !defined('ABSPATH') )
define('ABSPATH', dirname(__FILE__) . '/');

require_once(ABSPATH . 'wp-settings.php');
EOF

This way, every time the script runs, your wp-config.php will have strong unique keys automatically.


11. Install SSL with Let’s Encrypt

certbot --apache --non-interactive --agree-tos --redirect -m admin@$DOMAIN -d $DOMAIN
  • Obtains and installs free SSL cert.
  • Enables HTTPS and automatic redirect from HTTP.

12. Configure Fail2Ban

cat > /etc/fail2ban/jail.local <<EOF

[sshd]

enabled = true port = $SSH_PORT filter = sshd logpath = /var/log/auth.log maxretry = 5 EOF systemctl restart fail2ban

  • Protects SSH from brute force attacks by banning after 5 failed attempts.

13. Logrotate for Apache logs

cat > /etc/logrotate.d/wordpress <<EOF
/var/log/apache2/*.log {
daily
rotate 14
compress
...
}
EOF
  • Automatically rotates and compresses Apache logs daily, keeping 14 days.

14. Install WP-CLI

curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
mv wp-cli.phar /usr/local/bin/wp
  • Installs WordPress CLI tool to manage WordPress from the command line.

15. Backup script & cron job

cat > /usr/local/bin/backup_wordpress.sh <<EOF
#!/bin/bash
DATE=\$(date +'%Y%m%d_%H%M%S')
tar czf /var/backups/wordpress/wp_files_\$DATE.tar.gz /var/www/html
mysqldump -u wpuser -p$WP_ADMIN_PASS wordpress > /var/backups/wordpress/wp_db_\$DATE.sql
find /var/backups/wordpress -type f -mtime +7 -delete
EOF
chmod +x /usr/local/bin/backup_wordpress.sh
(crontab -l -u $WP_ADMIN_USER 2>/dev/null; echo "0 2 * * * /usr/local/bin/backup_wordpress.sh") | crontab -u $WP_ADMIN_USER -
  • Creates daily backups of WordPress files and DB at 2 AM.
  • Keeps backups for 7 days only.

16. CloudWatch Agent installation

wget https://s3.eu-west-2.amazonaws.com/amazoncloudwatch-agent-eu-west-2/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
dpkg -i -E ./amazon-cloudwatch-agent.deb
cat > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json <<EOF
{
"metrics": {
...
}
}
EOF
amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json -s
  • Installs AWS CloudWatch Agent to send CPU, memory, disk metrics to CloudWatch.

17. Login instructions in MOTD

cat <<EOF > /etc/motd
*******************************************************
* Welcome! SSH to $WP_ADMIN_USER user on port $SSH_PORT *
* Use your SSH key for authentication. *
*******************************************************
EOF
  • Displays helpful login instructions when you SSH in.

Summary

  • The script automates full server setup for a secure, production-ready WordPress with SSL and backups.
  • It configures SSH to be secure (keys only, custom port, no root password).
  • It installs and secures MySQL, Apache, WordPress.
  • It configures Fail2Ban and logrotate.
  • It sets up monitoring and backups.
PhaseUser to SSH asPortAuthentication Method
Before script runsubuntu22 (default)SSH key (your Lightsail key)
After script completesNew user (e.g., wpadmin)Custom port (e.g., 2222)SSH key (the key you added in script)
Root logindiscouraged (disabled password login)custom portSSH key (if enabled)

Scroll to Top