Running WordPress on ARM-based Graviton instances delivers up to 40% better price-performance compared to x86 equivalents. This guide provides production-ready scripts to deploy an optimised WordPress stack in minutes, plus everything you need to migrate your existing site.
Why Graviton for WordPress?
Graviton3 processors deliver:
- 40% better price-performance vs comparable x86 instances
- Up to 25% lower cost for equivalent workloads
- 60% less energy consumption per compute hour
- Native ARM64 optimisations for PHP 8.x and MariaDB
The t4g.small instance (2 vCPU, 2GB RAM) at ~$12/month handles most WordPress sites comfortably. For higher traffic, t4g.medium or c7g instances scale beautifully.
Architecture
┌─────────────────────────────────────────────────┐
│ CloudFront │
│ (Optional CDN Layer) │
└─────────────────────┬───────────────────────────┘
│
┌─────────────────────▼───────────────────────────┐
│ Graviton EC2 Instance │
│ ┌─────────────────────────────────────────────┐│
│ │ Caddy (Reverse Proxy) ││
│ │ Auto-TLS, HTTP/2, Compression ││
│ └─────────────────────┬───────────────────────┘│
│ │ │
│ ┌─────────────────────▼───────────────────────┐│
│ │ PHP-FPM 8.3 ││
│ │ OPcache, JIT Compilation ││
│ └─────────────────────┬───────────────────────┘│
│ │ │
│ ┌─────────────────────▼───────────────────────┐│
│ │ MariaDB 10.11 ││
│ │ InnoDB Optimised, Query Cache ││
│ └─────────────────────────────────────────────┘│
│ │
│ ┌─────────────────────────────────────────────┐│
│ │ EBS gp3 Volume ││
│ │ 3000 IOPS, 125 MB/s baseline ││
│ └─────────────────────────────────────────────┘│
└─────────────────────────────────────────────────┘
Prerequisites
- AWS CLI configured with appropriate permissions
- A domain name with DNS you control
- SSH key pair in your target region
Part 1: Launch the Instance
Save this as launch-graviton-wp.sh and run from your local machine:
#!/bin/bash
set -euo pipefail
# Configuration - EDIT THESE
INSTANCE_TYPE="t4g.small" # t4g.small for small sites, t4g.medium for busier
KEY_NAME="your-key-name" # Your SSH key pair name
REGION="eu-west-1" # Your preferred region
INSTANCE_NAME="wordpress-graviton"
VOLUME_SIZE=30 # GB - adjust as needed
# Get latest Amazon Linux 2023 ARM64 AMI
AMI_ID=$(aws ec2 describe-images \
--owners amazon \
--filters "Name=name,Values=al2023-ami-2023*-arm64" \
"Name=state,Values=available" \
--query 'Images | sort_by(@, &CreationDate) | [-1].ImageId' \
--output text \
--region "$REGION")
echo "Using AMI: $AMI_ID"
# Create security group
SG_ID=$(aws ec2 create-security-group \
--group-name "wordpress-graviton-sg" \
--description "WordPress on Graviton security group" \
--region "$REGION" \
--query 'GroupId' \
--output text 2>/dev/null || \
aws ec2 describe-security-groups \
--group-names "wordpress-graviton-sg" \
--region "$REGION" \
--query 'SecurityGroups[0].GroupId' \
--output text)
# Configure security group rules
aws ec2 authorize-security-group-ingress --group-id "$SG_ID" --protocol tcp --port 22 --cidr 0.0.0.0/0 --region "$REGION" 2>/dev/null || true
aws ec2 authorize-security-group-ingress --group-id "$SG_ID" --protocol tcp --port 80 --cidr 0.0.0.0/0 --region "$REGION" 2>/dev/null || true
aws ec2 authorize-security-group-ingress --group-id "$SG_ID" --protocol tcp --port 443 --cidr 0.0.0.0/0 --region "$REGION" 2>/dev/null || true
echo "Security group configured: $SG_ID"
# Launch instance
INSTANCE_ID=$(aws ec2 run-instances \
--image-id "$AMI_ID" \
--instance-type "$INSTANCE_TYPE" \
--key-name "$KEY_NAME" \
--security-group-ids "$SG_ID" \
--block-device-mappings "DeviceName=/dev/xvda,Ebs={VolumeSize=$VOLUME_SIZE,VolumeType=gp3,Iops=3000,Throughput=125}" \
--credit-specification CpuCredits=unlimited \
--metadata-options "HttpTokens=required,HttpEndpoint=enabled" \
--tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=$INSTANCE_NAME}]" \
--region "$REGION" \
--query 'Instances[0].InstanceId' \
--output text)
echo "Launched instance: $INSTANCE_ID"
# Wait for instance to be running
aws ec2 wait instance-running --instance-ids "$INSTANCE_ID" --region "$REGION"
# Get public IP
PUBLIC_IP=$(aws ec2 describe-instances \
--instance-ids "$INSTANCE_ID" \
--region "$REGION" \
--query 'Reservations[0].Instances[0].PublicIpAddress' \
--output text)
echo ""
echo "============================================"
echo "Instance launched successfully!"
echo "Instance ID: $INSTANCE_ID"
echo "Public IP: $PUBLIC_IP"
echo ""
echo "Next steps:"
echo "1. Point your domain's A record to: $PUBLIC_IP"
echo "2. SSH in: ssh -i ~/.ssh/${KEY_NAME}.pem ec2-user@$PUBLIC_IP"
echo "3. Run the WordPress setup script"
echo "============================================"
Run it:
chmod +x launch-graviton-wp.sh
./launch-graviton-wp.sh
Part 2: Install WordPress Stack
SSH into your new instance and save this as setup-wordpress.sh:
#!/bin/bash
set -euo pipefail
# Configuration - EDIT THESE
DOMAIN="yourdomain.com"
WP_DB_NAME="wordpress"
WP_DB_USER="wp_user"
WP_DB_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)
MYSQL_ROOT_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)
WP_ADMIN_USER="admin"
WP_ADMIN_PASS=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9' | head -c 16)
WP_ADMIN_EMAIL="admin@${DOMAIN}"
# Store credentials
mkdir -p /root/.wordpress
cat > /root/.wordpress/credentials << EOF
MySQL Root Password: $MYSQL_ROOT_PASS
WordPress DB Name: $WP_DB_NAME
WordPress DB User: $WP_DB_USER
WordPress DB Pass: $WP_DB_PASS
WordPress Admin: $WP_ADMIN_USER
WordPress Admin Pass: $WP_ADMIN_PASS
EOF
chmod 600 /root/.wordpress/credentials
echo "==> Installing packages..."
dnf update -y
# Find latest available PHP version
PHP_VERSION=$(dnf list available php* 2>/dev/null | grep -oP 'php\d+\.\d+(?=\.x86_64|\.aarch64|-fpm)' | sort -V | tail -1)
if [ -z "$PHP_VERSION" ]; then
PHP_VERSION="php8.3" # Fallback
fi
echo " Using PHP: ${PHP_VERSION}"
# Find latest available MariaDB version
MARIADB_PKG=$(dnf list available mariadb*-server 2>/dev/null | grep -oP 'mariadb\d+-server' | sort -V | tail -1)
if [ -z "$MARIADB_PKG" ]; then
MARIADB_PKG="mariadb105-server" # Fallback
fi
echo " Using MariaDB: ${MARIADB_PKG}"
dnf install -y ${PHP_VERSION} ${PHP_VERSION}-fpm ${PHP_VERSION}-mysqlnd ${PHP_VERSION}-gd ${PHP_VERSION}-xml \
${PHP_VERSION}-mbstring ${PHP_VERSION}-opcache ${PHP_VERSION}-zip ${PHP_VERSION}-intl ${PHP_VERSION}-bcmath \
${PHP_VERSION}-imagick ${MARIADB_PKG} wget unzip
# Install Caddy
dnf install -y 'dnf-command(copr)'
dnf copr enable -y @caddy/caddy
dnf install -y caddy
echo "==> Configuring MariaDB..."
systemctl enable --now mariadb
mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '${MYSQL_ROOT_PASS}';"
mysql -u root -p"${MYSQL_ROOT_PASS}" << EOF
CREATE DATABASE ${WP_DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER '${WP_DB_USER}'@'localhost' IDENTIFIED BY '${WP_DB_PASS}';
GRANT ALL PRIVILEGES ON ${WP_DB_NAME}.* TO '${WP_DB_USER}'@'localhost';
FLUSH PRIVILEGES;
EOF
# MariaDB performance tuning
cat > /etc/my.cnf.d/wordpress-optimised.cnf << 'EOF'
[[mysqld]]
# InnoDB Settings
innodb_buffer_pool_size = 256M
innodb_log_file_size = 64M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
innodb_file_per_table = 1
# Query Cache (MariaDB still supports this)
query_cache_type = 1
query_cache_size = 32M
query_cache_limit = 2M
# Connection Settings
max_connections = 100
thread_cache_size = 8
# Table Settings
table_open_cache = 2000
table_definition_cache = 1000
# Logging
slow_query_log = 1
slow_query_log_file = /var/log/mariadb/slow.log
long_query_time = 2
EOF
systemctl restart mariadb
echo "==> Configuring PHP-FPM..."
cat > /etc/php.ini.d/99-wordpress-optimised.ini << 'EOF'
; Memory and execution
memory_limit = 256M
max_execution_time = 300
max_input_time = 300
post_max_size = 64M
upload_max_filesize = 64M
; OPcache - Critical for performance
opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 10000
opcache.validate_timestamps = 0
opcache.revalidate_freq = 0
opcache.save_comments = 1
opcache.enable_file_override = 1
; JIT Compilation (PHP 8+)
opcache.jit = 1255
opcache.jit_buffer_size = 64M
; Realpath cache
realpath_cache_size = 4096K
realpath_cache_ttl = 600
EOF
# PHP-FPM pool configuration
cat > /etc/php-fpm.d/www.conf << 'EOF'
[[www]]
user = caddy
group = caddy
listen = /run/php-fpm/www.sock
listen.owner = caddy
listen.group = caddy
listen.mode = 0660
pm = dynamic
pm.max_children = 25
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10
pm.max_requests = 500
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
; Security
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen
EOF
mkdir -p /var/log/php-fpm
systemctl enable --now php-fpm
echo "==> Installing WordPress..."
mkdir -p /var/www/wordpress
cd /var/www/wordpress
wget -q https://wordpress.org/latest.tar.gz
tar xzf latest.tar.gz --strip-components=1
rm latest.tar.gz
# Generate WordPress salts
SALTS=$(curl -s https://api.wordpress.org/secret-key/1.1/salt/)
cat > wp-config.php << EOF
<?php
define('DB_NAME', '${WP_DB_NAME}');
define('DB_USER', '${WP_DB_USER}');
define('DB_PASSWORD', '${WP_DB_PASS}');
define('DB_HOST', 'localhost');
define('DB_CHARSET', 'utf8mb4');
define('DB_COLLATE', '');
${SALTS}
\$table_prefix = 'wp_';
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', false);
define('WP_DEBUG_DISPLAY', false);
// Performance optimisations
define('WP_CACHE', true);
define('COMPRESS_CSS', true);
define('COMPRESS_SCRIPTS', true);
define('CONCATENATE_SCRIPTS', true);
define('ENFORCE_GZIP', true);
// Security hardening
define('DISALLOW_FILE_EDIT', true);
define('WP_AUTO_UPDATE_CORE', 'minor');
// Memory
define('WP_MEMORY_LIMIT', '256M');
define('WP_MAX_MEMORY_LIMIT', '512M');
if (!defined('ABSPATH')) {
define('ABSPATH', __DIR__ . '/');
}
require_once ABSPATH . 'wp-settings.php';
EOF
chown -R caddy:caddy /var/www/wordpress
find /var/www/wordpress -type d -exec chmod 755 {} \;
find /var/www/wordpress -type f -exec chmod 644 {} \;
echo "==> Configuring Caddy..."
cat > /etc/caddy/Caddyfile << EOF
${DOMAIN} {
root * /var/www/wordpress
# PHP processing
php_fastcgi unix//run/php-fpm/www.sock {
resolve_root_symlink
}
# Static file serving
file_server
# WordPress permalinks
@notStatic {
not path /wp-admin/* /wp-includes/* /wp-content/*
not file
}
rewrite @notStatic /index.php
# Security headers
header {
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
X-XSS-Protection "1; mode=block"
Referrer-Policy strict-origin-when-cross-origin
-Server
}
# Gzip compression
encode gzip zstd
# Cache static assets
@static {
path *.css *.js *.ico *.gif *.jpg *.jpeg *.png *.svg *.woff *.woff2
}
header @static Cache-Control "public, max-age=31536000, immutable"
# Block sensitive files
@blocked {
path /wp-config.php /readme.html /license.txt /.htaccess
path /wp-includes/*.php
}
respond @blocked 404
# Logging
log {
output file /var/log/caddy/access.log
format console
}
}
EOF
mkdir -p /var/log/caddy
chown caddy:caddy /var/log/caddy
systemctl enable --now caddy
echo "==> Installing WP-CLI..."
curl -sO 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
# Complete WordPress installation
cd /var/www/wordpress
sudo -u caddy wp core install \
--url="https://${DOMAIN}" \
--title="My WordPress Site" \
--admin_user="${WP_ADMIN_USER}" \
--admin_password="${WP_ADMIN_PASS}" \
--admin_email="${WP_ADMIN_EMAIL}" \
--skip-email
# Install and activate caching plugin
sudo -u caddy wp plugin install wp-super-cache --activate
echo "==> Setting up automated backups..."
mkdir -p /var/backups/wordpress
cat > /etc/cron.daily/wordpress-backup << 'EOF'
#!/bin/bash
BACKUP_DIR="/var/backups/wordpress"
DATE=$(date +%Y%m%d)
cd /var/www/wordpress
wp db export "${BACKUP_DIR}/db-${DATE}.sql" --allow-root
tar czf "${BACKUP_DIR}/files-${DATE}.tar.gz" wp-content/
find "${BACKUP_DIR}" -type f -mtime +7 -delete
EOF
chmod +x /etc/cron.daily/wordpress-backup
echo ""
echo "============================================"
echo "WordPress installation complete!"
echo ""
echo "Site URL: https://${DOMAIN}"
echo ""
echo "Admin credentials saved to: /root/.wordpress/credentials"
cat /root/.wordpress/credentials
echo ""
echo "============================================"
Run it:
chmod +x setup-wordpress.sh
sudo ./setup-wordpress.sh
Part 3: Migrate Your Existing Site
If you’re migrating from an existing WordPress installation, follow these steps.
What gets migrated:
- All posts, pages, and media
- All users and their roles
- All plugins (files + database settings)
- All themes (including customisations)
- All plugin/theme configurations (stored in
wp_optionstable) - Widgets, menus, and customizer settings
- WooCommerce products, orders, customers (if applicable)
- All custom database tables created by plugins
Step 3a: Export from Old Server
Run this on your existing WordPress server. Save as wp-export.sh:
#!/bin/bash
set -euo pipefail
# Configuration
WP_PATH="/var/www/html" # Adjust to your WordPress path
EXPORT_DIR="/tmp/wp-migration"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Detect WordPress path if not set correctly
if [ ! -f "${WP_PATH}/wp-config.php" ]; then
for path in "/var/www/wordpress" "/var/www/html/wordpress" "/home/*/public_html" "/var/www/*/public_html"; do
if [ -f "${path}/wp-config.php" ]; then
WP_PATH="$path"
break
fi
done
fi
if [ ! -f "${WP_PATH}/wp-config.php" ]; then
echo "ERROR: wp-config.php not found. Please set WP_PATH correctly."
exit 1
fi
echo "==> WordPress found at: ${WP_PATH}"
# Extract database credentials from wp-config.php
DB_NAME=$(grep "DB_NAME" "${WP_PATH}/wp-config.php" | cut -d "'" -f 4)
DB_USER=$(grep "DB_USER" "${WP_PATH}/wp-config.php" | cut -d "'" -f 4)
DB_PASS=$(grep "DB_PASSWORD" "${WP_PATH}/wp-config.php" | cut -d "'" -f 4)
DB_HOST=$(grep "DB_HOST" "${WP_PATH}/wp-config.php" | cut -d "'" -f 4)
echo "==> Database: ${DB_NAME}"
# Create export directory
mkdir -p "${EXPORT_DIR}"
cd "${EXPORT_DIR}"
echo "==> Exporting database..."
mysqldump -h "${DB_HOST}" -u "${DB_USER}" -p"${DB_PASS}" \
--single-transaction \
--quick \
--lock-tables=false \
--routines \
--triggers \
"${DB_NAME}" > database.sql
DB_SIZE=$(ls -lh database.sql | awk '{print $5}')
echo " Database exported: ${DB_SIZE}"
echo "==> Exporting wp-content..."
tar czf wp-content.tar.gz -C "${WP_PATH}" wp-content
CONTENT_SIZE=$(ls -lh wp-content.tar.gz | awk '{print $5}')
echo " wp-content exported: ${CONTENT_SIZE}"
echo "==> Exporting wp-config.php..."
cp "${WP_PATH}/wp-config.php" wp-config.php.bak
echo "==> Creating migration package..."
tar czf "wordpress-migration-${TIMESTAMP}.tar.gz" \
database.sql \
wp-content.tar.gz \
wp-config.php.bak
rm -f database.sql wp-content.tar.gz wp-config.php.bak
PACKAGE_SIZE=$(ls -lh "wordpress-migration-${TIMESTAMP}.tar.gz" | awk '{print $5}')
echo ""
echo "============================================"
echo "Export complete!"
echo ""
echo "Package: ${EXPORT_DIR}/wordpress-migration-${TIMESTAMP}.tar.gz"
echo "Size: ${PACKAGE_SIZE}"
echo ""
echo "Transfer to new server with:"
echo " scp ${EXPORT_DIR}/wordpress-migration-${TIMESTAMP}.tar.gz ec2-user@NEW_IP:/tmp/"
echo "============================================"
Step 3b: Transfer the Export
scp /tmp/wp-migration/wordpress-migration-*.tar.gz ec2-user@YOUR_NEW_IP:/tmp/
Step 3c: Import on New Server
Run this on your new Graviton instance. Save as wp-import.sh:
#!/bin/bash
set -euo pipefail
# Configuration - EDIT THESE
MIGRATION_FILE="${1:-/tmp/wordpress-migration-*.tar.gz}"
OLD_DOMAIN="oldsite.com" # Your old domain
NEW_DOMAIN="newsite.com" # Your new domain (can be same)
WP_PATH="/var/www/wordpress"
# Resolve migration file path
MIGRATION_FILE=$(ls -1 ${MIGRATION_FILE} 2>/dev/null | head -1)
if [ ! -f "${MIGRATION_FILE}" ]; then
echo "ERROR: Migration file not found: ${MIGRATION_FILE}"
echo "Usage: $0 /path/to/wordpress-migration-XXXXXX.tar.gz"
exit 1
fi
echo "==> Using migration file: ${MIGRATION_FILE}"
# Get database credentials from existing wp-config
if [ ! -f "${WP_PATH}/wp-config.php" ]; then
echo "ERROR: wp-config.php not found at ${WP_PATH}"
echo "Please run the WordPress setup script first"
exit 1
fi
DB_NAME=$(grep "DB_NAME" "${WP_PATH}/wp-config.php" | cut -d "'" -f 4)
DB_USER=$(grep "DB_USER" "${WP_PATH}/wp-config.php" | cut -d "'" -f 4)
DB_PASS=$(grep "DB_PASSWORD" "${WP_PATH}/wp-config.php" | cut -d "'" -f 4)
MYSQL_ROOT_PASS=$(cat /root/.wordpress/credentials | grep "MySQL Root" | awk '{print $4}')
echo "==> Extracting migration package..."
TEMP_DIR=$(mktemp -d)
cd "${TEMP_DIR}"
tar xzf "${MIGRATION_FILE}"
echo "==> Backing up current installation..."
BACKUP_DIR="/var/backups/wordpress/pre-migration-$(date +%Y%m%d_%H%M%S)"
mkdir -p "${BACKUP_DIR}"
cp -r "${WP_PATH}/wp-content" "${BACKUP_DIR}/" 2>/dev/null || true
mysqldump -u root -p"${MYSQL_ROOT_PASS}" "${DB_NAME}" > "${BACKUP_DIR}/database.sql" 2>/dev/null || true
echo "==> Importing database..."
mysql -u root -p"${MYSQL_ROOT_PASS}" << EOF
DROP DATABASE IF EXISTS ${DB_NAME};
CREATE DATABASE ${DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'localhost';
FLUSH PRIVILEGES;
EOF
mysql -u root -p"${MYSQL_ROOT_PASS}" "${DB_NAME}" < database.sql
echo "==> Importing wp-content..."
rm -rf "${WP_PATH}/wp-content"
tar xzf wp-content.tar.gz -C "${WP_PATH}"
chown -R caddy:caddy "${WP_PATH}/wp-content"
find "${WP_PATH}/wp-content" -type d -exec chmod 755 {} \;
find "${WP_PATH}/wp-content" -type f -exec chmod 644 {} \;
echo "==> Updating URLs in database..."
cd "${WP_PATH}"
OLD_URL_HTTP="http://${OLD_DOMAIN}"
OLD_URL_HTTPS="https://${OLD_DOMAIN}"
NEW_URL="https://${NEW_DOMAIN}"
# Install WP-CLI if not present
if ! command -v wp &> /dev/null; then
curl -sO 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
fi
echo " Replacing ${OLD_URL_HTTPS} with ${NEW_URL}..."
sudo -u caddy wp search-replace "${OLD_URL_HTTPS}" "${NEW_URL}" --all-tables --precise --skip-columns=guid 2>/dev/null || true
echo " Replacing ${OLD_URL_HTTP} with ${NEW_URL}..."
sudo -u caddy wp search-replace "${OLD_URL_HTTP}" "${NEW_URL}" --all-tables --precise --skip-columns=guid 2>/dev/null || true
echo " Replacing //${OLD_DOMAIN} with //${NEW_DOMAIN}..."
sudo -u caddy wp search-replace "//${OLD_DOMAIN}" "//${NEW_DOMAIN}" --all-tables --precise --skip-columns=guid 2>/dev/null || true
echo "==> Flushing caches and rewrite rules..."
sudo -u caddy wp cache flush
sudo -u caddy wp rewrite flush
echo "==> Reactivating plugins..."
# Some plugins may deactivate during migration - reactivate all
sudo -u caddy wp plugin activate --all 2>/dev/null || true
echo "==> Verifying import..."
POST_COUNT=$(sudo -u caddy wp post list --post_type=post --format=count)
PAGE_COUNT=$(sudo -u caddy wp post list --post_type=page --format=count)
USER_COUNT=$(sudo -u caddy wp user list --format=count)
PLUGIN_COUNT=$(sudo -u caddy wp plugin list --format=count)
echo ""
echo "============================================"
echo "Migration complete!"
echo ""
echo "Imported content:"
echo " - Posts: ${POST_COUNT}"
echo " - Pages: ${PAGE_COUNT}"
echo " - Users: ${USER_COUNT}"
echo " - Plugins: ${PLUGIN_COUNT}"
echo ""
echo "Site URL: https://${NEW_DOMAIN}"
echo ""
echo "Pre-migration backup: ${BACKUP_DIR}"
echo "============================================"
rm -rf "${TEMP_DIR}"
Run it:
chmod +x wp-import.sh
sudo ./wp-import.sh /tmp/wordpress-migration-*.tar.gz
Step 3d: Verify Migration
#!/bin/bash
set -euo pipefail
WP_PATH="/var/www/wordpress"
cd "${WP_PATH}"
echo "==> WordPress Verification Report"
echo "=================================="
echo ""
echo "WordPress Version:"
sudo -u caddy wp core version
echo ""
echo "Site URL Configuration:"
sudo -u caddy wp option get siteurl
sudo -u caddy wp option get home
echo ""
echo "Database Status:"
sudo -u caddy wp db check
echo ""
echo "Content Summary:"
echo " Posts: $(sudo -u caddy wp post list --post_type=post --format=count)"
echo " Pages: $(sudo -u caddy wp post list --post_type=page --format=count)"
echo " Media: $(sudo -u caddy wp post list --post_type=attachment --format=count)"
echo " Users: $(sudo -u caddy wp user list --format=count)"
echo ""
echo "Plugin Status:"
sudo -u caddy wp plugin list --format=table
echo ""
echo "Uploads Directory:"
UPLOAD_COUNT=$(find "${WP_PATH}/wp-content/uploads" -type f 2>/dev/null | wc -l)
UPLOAD_SIZE=$(du -sh "${WP_PATH}/wp-content/uploads" 2>/dev/null | cut -f1)
echo " Files: ${UPLOAD_COUNT}"
echo " Size: ${UPLOAD_SIZE}"
echo ""
echo "Service Status:"
echo " PHP-FPM: $(systemctl is-active php-fpm)"
echo " MariaDB: $(systemctl is-active mariadb)"
echo " Caddy: $(systemctl is-active caddy)"
echo ""
echo "Page Load Test:"
DOMAIN=$(sudo -u caddy wp option get siteurl | sed 's|https://||' | sed 's|/.*||')
curl -w " Total time: %{time_total}s\n HTTP code: %{http_code}\n" -o /dev/null -s "https://${DOMAIN}/"
Rollback if Needed
If something goes wrong:
#!/bin/bash
set -euo pipefail
BACKUP_DIR=$(ls -1d /var/backups/wordpress/pre-migration-* 2>/dev/null | tail -1)
if [ -z "${BACKUP_DIR}" ]; then
echo "ERROR: No backup found"
exit 1
fi
echo "==> Rolling back to: ${BACKUP_DIR}"
WP_PATH="/var/www/wordpress"
MYSQL_ROOT_PASS=$(cat /root/.wordpress/credentials | grep "MySQL Root" | awk '{print $4}')
DB_NAME=$(grep "DB_NAME" "${WP_PATH}/wp-config.php" | cut -d "'" -f 4)
mysql -u root -p"${MYSQL_ROOT_PASS}" "${DB_NAME}" < "${BACKUP_DIR}/database.sql"
rm -rf "${WP_PATH}/wp-content"
cp -r "${BACKUP_DIR}/wp-content" "${WP_PATH}/"
chown -R caddy:caddy "${WP_PATH}/wp-content"
cd "${WP_PATH}"
sudo -u caddy wp cache flush
sudo -u caddy wp rewrite flush
echo "Rollback complete!"
Part 4: Post-Installation Optimisations
After setup (or migration), run these additional optimisations:
#!/bin/bash
cd /var/www/wordpress
# Remove default content
sudo -u caddy wp post delete 1 2 --force 2>/dev/null || true
sudo -u caddy wp theme delete twentytwentytwo twentytwentythree 2>/dev/null || true
# Update everything
sudo -u caddy wp core update
sudo -u caddy wp plugin update --all
sudo -u caddy wp theme update --all
# Configure WP Super Cache
sudo -u caddy wp super-cache enable 2>/dev/null || true
# Set optimal permalink structure
sudo -u caddy wp rewrite structure '/%postname%/'
sudo -u caddy wp rewrite flush
echo "Optimisations complete!"
Performance Verification
Check your stack is running optimally:
# Verify PHP OPcache status
php -i | grep -i opcache
# Check PHP-FPM status
systemctl status php-fpm
# Test page load time
curl -w "@-" -o /dev/null -s "https://yourdomain.com" << 'EOF'
time_namelookup: %{time_namelookup}s
time_connect: %{time_connect}s
time_appconnect: %{time_appconnect}s
time_pretransfer: %{time_pretransfer}s
time_redirect: %{time_redirect}s
time_starttransfer: %{time_starttransfer}s
----------
time_total: %{time_total}s
EOF
Cost Comparison
| Instance | vCPU | RAM | Monthly Cost | Use Case |
|---|---|---|---|---|
| t4g.micro | 2 | 1GB | ~$6 | Dev/testing |
| t4g.small | 2 | 2GB | ~$12 | Small blogs |
| t4g.medium | 2 | 4GB | ~$24 | Medium traffic |
| t4g.large | 2 | 8GB | ~$48 | High traffic |
| c7g.medium | 1 | 2GB | ~$25 | CPU-intensive |
All prices are approximate for eu-west-1 with on-demand pricing. Reserved instances or Savings Plans reduce costs by 30-60%.
Troubleshooting
502 Bad Gateway: PHP-FPM socket permissions issue
systemctl restart php-fpm
ls -la /run/php-fpm/www.sock
Database connection error: Check MariaDB is running
systemctl status mariadb
mysql -u wp_user -p wordpress
SSL certificate not working: Ensure DNS is pointing to instance IP
dig +short yourdomain.com
curl -I https://yourdomain.com
OPcache not working: Verify with phpinfo
php -r "phpinfo();" | grep -i opcache.enable
Quick Reference
# 1. Launch instance (local machine)
./launch-graviton-wp.sh
# 2. SSH in and setup WordPress
ssh -i ~/.ssh/key.pem ec2-user@IP
sudo ./setup-wordpress.sh
# 3. If migrating - on old server
./wp-export.sh
scp /tmp/wp-migration/wordpress-migration-*.tar.gz ec2-user@NEW_IP:/tmp/
# 4. If migrating - on new server
sudo ./wp-import.sh /tmp/wordpress-migration-*.tar.gz
This setup delivers a production-ready WordPress installation that’ll handle significant traffic while keeping your AWS bill minimal. The combination of Graviton’s price-performance, Caddy’s efficiency, and properly-tuned PHP creates a stack that punches well above its weight class.