β
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
- Replace the following placeholders:
yourdomain.com
β your actual domainyou@example.com
β your email for Let’s Encrypt
- 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 withsudo
.
π‘οΈ 3. What SSH hardening changes are made?
These two changes improve SSH security:
Change | Line | Effect |
---|---|---|
π Disable root login | PermitRootLogin no | Prevents attackers from brute-forcing the root account |
π¦ Change default port | Port 2222 | Reduces 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.
Phase | User to SSH as | Port | Authentication Method |
---|---|---|---|
Before script runs | ubuntu | 22 (default) | SSH key (your Lightsail key) |
After script completes | New user (e.g., wpadmin ) | Custom port (e.g., 2222) | SSH key (the key you added in script) |
Root login | discouraged (disabled password login) | custom port | SSH key (if enabled) |