WordPress on a MicroVM
Run the WordPress CMS in a MicroVM with Nginx, PHP-FPM, and MySQL. A complete setup for a production-ready WordPress.
Architecture
Client
|
[MicroVM]
|
[Nginx :80] -> [PHP-FPM :9000] -> [WordPress Files]
|
[MySQL :3306]
Preparing the Image
Install All Packages
sudo mount -o loop rootfs.img /mnt/microvm
sudo chroot /mnt/microvm bash -c '
apt update && apt install -y \
nginx php8.2-fpm php8.2-mysql php8.2-gd php8.2-curl \
php8.2-mbstring php8.2-xml php8.2-zip php8.2-intl \
php8.2-imagick php8.2-redis \
mysql-server \
wget unzip
# Download WordPress
cd /var/www
wget -q https://wordpress.org/latest.tar.gz
tar xzf latest.tar.gz
mv wordpress html
chown -R www-data:www-data html
'
sudo umount /mnt/microvm
Install via Alpine
sudo chroot /mnt/microvm bash -c '
apk add nginx php82-fpm php82-mysqlnd php82-gd php82-curl \
php82-mbstring php82-xml php82-zip php82-intl \
mysql-client
mkdir -p /var/www/html
cd /tmp
wget https://wordpress.org/latest.tar.gz
tar xzf latest.tar.gz -C /var/www/html --strip-components=1
chown -R nginx:nginx /var/www/html
'
Configuring Nginx
cat > /etc/nginx/sites-available/wordpress << 'WPCONF'
server {
listen 80;
server_name yourdomain.com;
root /var/www/html;
index index.php index.html;
client_max_body_size 64M;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 300;
}
# Cache static files
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# Block sensitive files
location ~ /\.(ht|git|svn) {
deny all;
}
location = /xmlrpc.php {
deny all;
}
# WP-Cron
location = /wp-cron.php {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
WPCONF
ln -s /etc/nginx/sites-available/wordpress /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
Configuring PHP-FPM
# Pool configuration for WordPress
cat > /etc/php/8.2/fpm/pool.d/www.conf << 'PHPPOOL'
[www]
user = www-data
group = www-data
listen = /run/php/php8.2-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 10
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 500
; Memory limit
php_admin_value[memory_limit] = 256M
php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 64M
php_admin_value[max_execution_time] = 300
php_admin_value[max_input_time] = 300
; OPcache
php_admin_value[opcache.enable] = 1
php_admin_value[opcache.memory_consumption] = 128
php_admin_value[opcache.interned_strings_buffer] = 8
php_admin_value[opcache.max_accelerated_files] = 10000
php_admin_value[opcache.revalidate_freq] = 2
php_admin_value[opcache.save_comments] = 1
PHPPOOL
Configuring MySQL
cat > /etc/mysql/conf.d/wordpress.cnf << 'MYSQLCONF'
[mysqld]
innodb_buffer_pool_size = 128M
innodb_log_file_size = 32M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
max_connections = 50
skip-name-resolve
MYSQLCONF
Running the MicroVM
qemu-system-x86_64 \
-M microvm -enable-kvm -cpu host -m 1g -smp 4 \
-kernel vmlinuz \
-append "console=ttyS0 root=/dev/vda rw" \
-drive id=rootfs,file=wordpress-rootfs.img,format=raw,if=none,cache=none,aio=linux \
-device virtio-blk-device,drive=rootfs \
-netdev user,id=net0,hostfwd=tcp::8080-:80,hostfwd=tcp::2222-:22 \
-device virtio-net-device,netdev=net0 \
-nographic -serial stdio
Setting Up the Guest
# Start services
service mysql start
service php8.2-fpm start
service nginx start
# Create the WordPress database
mysql -u root << 'SQL'
CREATE DATABASE wordpress;
CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'secure_password';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost';
FLUSH PRIVILEGES;
SQL
# Configure wp-config.php
cd /var/www/html
cp wp-config-sample.php wp-config.php
# Generate salts
SALTS=$(curl -s https://api.wordpress.org/secret-key/1.1/salt/)
# Edit wp-config.php
sed -i "s/database_name_here/wordpress/" wp-config.php
sed -i "s/username_here/wpuser/" wp-config.php
sed -i "s/password_here/secure_password/" wp-config.php
# Add the salts
# (remove the old placeholder salts, add the new ones)
Complete wp-config.php
<?php
define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'wpuser' );
define( 'DB_PASSWORD', 'secure_password' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
define( 'DB_COLLATE', '' );
define( 'AUTH_KEY', 'put your unique phrase here' );
define( 'SECURE_AUTH_KEY', 'put your unique phrase here' );
define( 'LOGGED_IN_KEY', 'put your unique phrase here' );
define( 'NONCE_KEY', 'put your unique phrase here' );
define( 'AUTH_SALT', 'put your unique phrase here' );
define( 'SECURE_AUTH_SALT', 'put your unique phrase here' );
define( 'LOGGED_IN_SALT', 'put your unique phrase here' );
define( 'NONCE_SALT', 'put your unique phrase here' );
$table_prefix = 'wp_';
define( 'WP_DEBUG', false );
define( 'WP_MEMORY_LIMIT', '256M' );
define( 'WP_MAX_MEMORY_LIMIT', '512M' );
define( 'WP_CRON_LOCK_TIMEOUT', 120 );
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', __DIR__ . '/' );
}
require_once ABSPATH . 'wp-settings.php';
OPcache Optimization
; /etc/php/8.2/fpm/conf.d/10-opcache.ini
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2
opcache.save_comments=1
opcache.fast_shutdown=1
opcache.enable_cli=0
Cron Job
# Disable WP-Cron via HTTP (use the system cron)
echo "DISABLE_WP_CRON = true" >> /var/www/html/wp-config.php
# Add the system cron
echo "*/5 * * * * www-data php /var/www/html/wp-cron.php" > /etc/cron.d/wordpress
Backup
# Backup files
tar czf /data/wordpress-files-$(date +%F).tar.gz /var/www/html
# Backup database
mysqldump -u wpuser -p wordpress > /data/wordpress-db-$(date +%F).sql
# or combined
mysqldump -u wpuser -p wordpress | gzip > /data/wordpress-full-$(date +%F).sql.gz
Troubleshooting
White screen of death
# Enable debug
# in wp-config.php:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
# Check the log
tail -f /var/www/html/wp-content/debug.log
PHP memory limit
// wp-config.php
define( 'WP_MEMORY_LIMIT', '512M' );
Permission issues
chown -R www-data:www-data /var/www/html
find /var/www/html -type d -exec chmod 755 {} \;
find /var/www/html -type f -exec chmod 644 {} \;
chmod 400 /var/www/html/wp-config.php