block by mrchrisadams aa812e59736e15f68f89558902f72f2b

WordPress + FrankenPHP + SQLite with systemd socket activation (start on demand, stop when idle)

WordPress with SQLite on FrankenPHP

A lightweight WordPress setup using SQLite instead of MySQL/MariaDB, powered by FrankenPHP. No database server required.

Quick Start

For a fresh Ubuntu 24.04 server:

curl -fsSL https://raw.githubusercontent.com/YOUR_REPO/setup.sh | sudo bash -s -- yourdomain.com 8000

Or clone and run:

git clone https://github.com/YOUR_REPO/wordpress-frankenphp-sqlite
cd wordpress-frankenphp-sqlite
sudo ./setup.sh yourdomain.com 8000

What You Get

Directory Structure

/var/www/yourdomain.com/
├── wp-config.php          # WordPress config (outside document root)
├── database/              # SQLite database (not web-accessible)
│   └── wordpress.db
└── public/                # Document root
    ├── wp-admin/
    ├── wp-content/
    ├── wp-includes/
    └── index.php

Manual Installation

Follow these steps if you prefer to install manually or need to customize the setup.

Prerequisites

Step 1: Install FrankenPHP

Add the repository and install FrankenPHP:

# Add the static-php repository (provides FrankenPHP)
curl -fsSL https://deb.henderkes.com/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/static-php.gpg
echo "deb [signed-by=/usr/share/keyrings/static-php.gpg] https://deb.henderkes.com/ stable main" | \
    sudo tee /etc/apt/sources.list.d/static-php.list

# Install FrankenPHP
sudo apt-get update
sudo apt-get install -y frankenphp

Step 2: Install Required PHP Extensions

FrankenPHP uses a modular static PHP build. Install PDO and SQLite support:

sudo apt-get install -y php-zts-pdo php-zts-pdo-sqlite php-zts-sqlite3

Verify the extensions are loaded:

php -m | grep -i pdo
# Should output:
# PDO
# pdo_sqlite

Step 3: Install WP-CLI

WP-CLI is the command-line interface for WordPress:

curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp

# Verify installation
wp --info

Step 4: Create Directory Structure

Set up the site directory following the Filesystem Hierarchy Standard:

export SITE_NAME="yourdomain.com"

sudo mkdir -p /var/www/${SITE_NAME}/public
sudo mkdir -p /var/www/${SITE_NAME}/database
sudo chown -R frankenphp:frankenphp /var/www/${SITE_NAME}/

Step 5: Download WordPress

Use WP-CLI to download WordPress:

sudo -u frankenphp wp core download --path=/var/www/${SITE_NAME}/public/

Step 6: Install SQLite Database Integration Plugin

Download and install the official SQLite plugin:

# Note: We can't use `wp plugin install` here because WordPress tries to
# connect to MySQL before the SQLite plugin is active
curl -sLo /tmp/sqlite-database-integration.zip \
    https://downloads.wordpress.org/plugin/sqlite-database-integration.zip
sudo unzip -q -o /tmp/sqlite-database-integration.zip \
    -d /var/www/${SITE_NAME}/public/wp-content/plugins/
sudo chown -R frankenphp:frankenphp \
    /var/www/${SITE_NAME}/public/wp-content/plugins/sqlite-database-integration
rm -f /tmp/sqlite-database-integration.zip

Copy the database drop-in file:

sudo cp /var/www/${SITE_NAME}/public/wp-content/plugins/sqlite-database-integration/db.copy \
       /var/www/${SITE_NAME}/public/wp-content/db.php
sudo chown frankenphp:frankenphp /var/www/${SITE_NAME}/public/wp-content/db.php

Step 7: Create wp-config.php

Generate the configuration file using WP-CLI:

export SITE_URL="https://yourdomain.com:8000"  # Adjust for your setup

# Create initial wp-config.php
sudo -u frankenphp wp config create \
    --path=/var/www/${SITE_NAME}/public/ \
    --dbname=wordpress \
    --dbuser='' \
    --dbpass='' \
    --dbhost='' \
    --skip-check

# Add SQLite configuration
sudo -u frankenphp wp config set DB_ENGINE sqlite --path=/var/www/${SITE_NAME}/public/
sudo -u frankenphp wp config set DB_DIR "/var/www/${SITE_NAME}/database/" --path=/var/www/${SITE_NAME}/public/
sudo -u frankenphp wp config set DB_FILE wordpress.db --path=/var/www/${SITE_NAME}/public/

# Set site URLs
sudo -u frankenphp wp config set WP_HOME "${SITE_URL}" --path=/var/www/${SITE_NAME}/public/
sudo -u frankenphp wp config set WP_SITEURL "${SITE_URL}" --path=/var/www/${SITE_NAME}/public/

Move wp-config.php Outside Document Root

For security, move the config file one level up:

sudo mv /var/www/${SITE_NAME}/public/wp-config.php /var/www/${SITE_NAME}/wp-config.php

Update ABSPATH since wp-config.php is now one level above WordPress:

sudo sed -i "s|define( 'ABSPATH', __DIR__ . '/' );|define( 'ABSPATH', __DIR__ . '/public/' );|" \
    /var/www/${SITE_NAME}/wp-config.php

Add reverse proxy HTTPS handling:

sudo tee -a /var/www/${SITE_NAME}/wp-config.php << 'EOF'

// Handle reverse proxy HTTPS
if ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) {
    $_SERVER['HTTPS'] = 'on';
}
EOF

Key points about wp-config.php placement:

Step 8: Set Permissions

# Set ownership to FrankenPHP user
sudo chown -R frankenphp:frankenphp /var/www/${SITE_NAME}/

# Secure the database directory (only frankenphp and root can access)
sudo chmod 750 /var/www/${SITE_NAME}/database

# Ensure wp-content is writable for uploads, plugins, themes
sudo chmod -R 755 /var/www/${SITE_NAME}/public/wp-content

Step 9: Configure FrankenPHP

Edit /etc/frankenphp/Caddyfile:

export PORT=8000

sudo tee /etc/frankenphp/Caddyfile << EOF
{
    frankenphp
}

:${PORT} {
    root /var/www/${SITE_NAME}/public/
    encode zstd br gzip
    php_server
}
EOF

Step 10: Configure Systemd Override

FrankenPHP’s systemd service uses ProtectSystem=full which makes /var read-only by default. We need to allow writes to specific directories:

sudo mkdir -p /etc/systemd/system/frankenphp.service.d/
sudo tee /etc/systemd/system/frankenphp.service.d/override.conf << EOF
[Service]
ReadWritePaths=/var/www/${SITE_NAME}/public/wp-content
ReadWritePaths=/var/www/${SITE_NAME}/database
EOF

sudo systemctl daemon-reload

Step 11: Start FrankenPHP

sudo systemctl enable frankenphp
sudo systemctl restart frankenphp

Verify it’s running:

sudo systemctl status frankenphp
curl -I http://localhost:8000/

Step 12: Complete WordPress Installation

Option A: Via Browser

Visit your site in a browser:

http://yourdomain.com:8000/

You’ll see the WordPress installation wizard. Fill in your site details.

Option B: Via WP-CLI (Headless)

Complete the installation entirely from the command line:

sudo -u frankenphp wp core install \
    --path=/var/www/${SITE_NAME}/public/ \
    --url="${SITE_URL}" \
    --title="My Site" \
    --admin_user=admin \
    --admin_password="YourSecurePassword123!" \
    --admin_email=admin@example.com

WP-CLI Reference

Common WP-CLI commands for managing your site:

# Always run as frankenphp user with the correct path
export WP="sudo -u frankenphp wp --path=/var/www/yourdomain.com/public/"

# Update WordPress core
$WP core update

# Update all plugins
$WP plugin update --all

# Update all themes
$WP theme update --all

# Install and activate a plugin
$WP plugin install jetpack --activate

# Install and activate a theme
$WP theme install flavor --activate

# Create a new user
$WP user create editor editor@example.com --role=editor

# Search and replace (useful when migrating)
$WP search-replace 'old-domain.com' 'new-domain.com'

# Export database (creates SQL-like output even for SQLite)
$WP db export backup.sql

# Clear caches
$WP cache flush

Troubleshooting

“PHP PDO Extension is not loaded”

Install the missing extensions:

sudo apt-get install -y php-zts-pdo php-zts-pdo-sqlite php-zts-sqlite3
sudo systemctl restart frankenphp

“Unable to create a file in the directory”

The systemd service is blocking writes. Check the override:

cat /etc/systemd/system/frankenphp.service.d/override.conf

Ensure ReadWritePaths includes both wp-content and database directories.

Database Locked Errors

Ensure the frankenphp user owns the database:

sudo chown frankenphp:frankenphp /var/www/yourdomain.com/database/wordpress.db

WP-CLI Errors

Always run WP-CLI as the frankenphp user:

sudo -u frankenphp wp --path=/var/www/yourdomain.com/public/ <command>

Check Logs

sudo journalctl -u frankenphp -f

Security Notes

  1. Database location: The SQLite database is stored outside the document root in /var/www/site/database/, making it inaccessible via web requests.

  2. wp-config.php location: Also outside the document root, protecting your authentication keys and database credentials.

  3. Directory permissions: The database directory has mode 750, readable only by the frankenphp user and root.

  4. HTTPS: If you’re behind a reverse proxy that terminates SSL, the config includes handling for X-Forwarded-Proto headers.

Backups

To backup your entire site:

tar czf backup-$(date +%Y%m%d).tar.gz /var/www/yourdomain.com/

The SQLite database is just a single file, making backups simple:

cp /var/www/yourdomain.com/database/wordpress.db wordpress-backup-$(date +%Y%m%d).db

Or use WP-CLI:

sudo -u frankenphp wp db export backup.sql --path=/var/www/yourdomain.com/public/

Resource Usage

This setup is extremely lightweight:

Socket Activation (Optional)

For even lower resource usage, you can configure systemd to:

  1. Start FrankenPHP only when traffic arrives
  2. Stop it automatically when idle

This is ideal for low-traffic sites or development environments.

See SOCKET_ACTIVATION.md for detailed setup, or use the quick install below.

Quick Install

# Stop the always-on service
sudo systemctl stop frankenphp
sudo systemctl disable frankenphp

# Install socket activation units
sudo cp frankenphp.socket /etc/systemd/system/
sudo cp frankenphp-socket.service /etc/systemd/system/frankenphp.service
sudo cp frankenphp-idle.service /etc/systemd/system/
sudo cp frankenphp-idle.timer /etc/systemd/system/

# Update Caddyfile for socket activation
sudo cp Caddyfile.socket-activated /etc/frankenphp/Caddyfile
# Edit /etc/frankenphp/Caddyfile to set your site root

# Enable socket and idle timer
sudo systemctl daemon-reload
sudo systemctl enable --now frankenphp.socket
sudo systemctl enable --now frankenphp-idle.timer

How It Works

Request → Socket (always listening) → Starts Service → Handles Request
                                              ↓
                              Timer checks every N minutes
                                              ↓
                              No connections? Stop service.
                                              ↓
                              Next request? Socket restarts it.

Verify

# Socket listening, service stopped
ss -tlnp | grep 8000                          # Port open
sudo systemctl is-active frankenphp.service   # inactive

# Make a request
curl http://localhost:8000/

# Service now running
sudo systemctl is-active frankenphp.service   # active

# Wait for idle timer (default: 1 min for testing, 5 min for production)
# Service will stop automatically

Tuning Idle Timeout

Edit /etc/systemd/system/frankenphp-idle.timer:

[Timer]
OnBootSec=5min
OnUnitActiveSec=5min  # Check every 5 minutes

Then reload: sudo systemctl daemon-reload && sudo systemctl restart frankenphp-idle.timer

License

MIT

Caddyfile.socket-activated

SOCKET_ACTIVATION.md

frankenphp-idle.service

frankenphp-idle.timer

frankenphp-socket.service

frankenphp.socket