Skip to content

Setting up a WordPress Subdomain

This guide sets up a fresh WordPress site on its own subdomain (example: blek.franzvoid.is/var/www/blek.franzvoid.is/), served by Apache with PHP-FPM and MariaDB, secured with Let's Encrypt. It leaves WordPress at its browser install wizard so you can create the admin account yourself.

For the general subdomain/vhost mechanics, see Setup Subdomains. This page is the WordPress-specific end-to-end.

0. Prerequisites

  • Apache already installed and running (see Setup Apache).
  • DNS: franzvoid.is has a wildcard *.franzvoid.is record (at Dynu) pointing at the server, so any new *.franzvoid.is subdomain resolves automatically — no A record to add. For a different domain, create an A record first and confirm it resolves: dig +short A <subdomain>.
  • sudo access.

Throughout, replace <subdomain> with your site (e.g. blek.franzvoid.is) and <docroot> with /var/www/<subdomain>.

1. Install PHP-FPM, extensions, and MariaDB

This server runs the Apache event MPM, so PHP must run through PHP-FPMmod_php is not an option.

sudo apt update
sudo apt install -y \
  php8.2-fpm php8.2-mysql php8.2-gd php8.2-curl php8.2-xml \
  php8.2-mbstring php8.2-zip php8.2-intl php8.2-bcmath php8.2-imagick \
  mariadb-server

Confirm both services are up:

php -v
sudo systemctl is-active mariadb php8.2-fpm

2. Wire PHP-FPM into Apache

sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.2-fpm        # routes *.php to /run/php/php8.2-fpm.sock for all vhosts
sudo systemctl reload apache2

3. Create the database

MariaDB root uses unix_socket auth, so sudo mariadb logs in without a password. Pick a strong password (openssl rand -base64 24 is handy) and substitute it below:

sudo mariadb <<'SQL'
CREATE DATABASE blek_wp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'blek_wp'@'localhost' IDENTIFIED BY 'CHANGE-ME-STRONG-PASSWORD';
GRANT ALL PRIVILEGES ON blek_wp.* TO 'blek_wp'@'localhost';
FLUSH PRIVILEGES;
SQL

Keep the DB name, user, and password — they go into wp-config.php next.

4. Download WordPress

cd /tmp
curl -fsSL -o wp.tar.gz https://downloads.wordpress.org/release/wordpress-7.0.1.tar.gz
tar xzf wp.tar.gz
grep "wp_version =" wordpress/wp-includes/version.php    # sanity-check the version
sudo mkdir -p /var/www/<subdomain>
sudo cp -a wordpress/. /var/www/<subdomain>/

For the latest version instead of a pinned one, use https://wordpress.org/latest.tar.gz.

5. Create wp-config.php

Generate fresh security salts and write the config. Run this from a working dir, then copy the result into place:

DBPASS='CHANGE-ME-STRONG-PASSWORD'
SALTS="$(curl -fsSL https://api.wordpress.org/secret-key/1.1/salt/)"

cat > /tmp/wp-config.php <<EOF
<?php
// ** Database settings ** //
define( 'DB_NAME', 'blek_wp' );
define( 'DB_USER', 'blek_wp' );
define( 'DB_PASSWORD', '${DBPASS}' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
define( 'DB_COLLATE', '' );

// ** Authentication unique keys and salts ** //
${SALTS}

\$table_prefix = 'wp_';

define( 'WP_DEBUG', false );

if ( ! defined( 'ABSPATH' ) ) {
    define( 'ABSPATH', __DIR__ . '/' );
}

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

php -l /tmp/wp-config.php                                  # syntax check
sudo cp /tmp/wp-config.php /var/www/<subdomain>/wp-config.php

6. Set ownership and permissions

Apache and PHP-FPM run as www-data, so the whole tree must be owned by it:

sudo chown -R www-data:www-data /var/www/<subdomain>
sudo find /var/www/<subdomain> -type d -exec chmod 755 {} \;
sudo find /var/www/<subdomain> -type f -exec chmod 644 {} \;
sudo chmod 640 /var/www/<subdomain>/wp-config.php         # keep DB creds tighter

7. Create the Apache virtual host

Create only the :80 vhost — certbot generates the HTTPS vhost and redirect in the next step. AllowOverride All lets WordPress's .htaccess (permalinks) work.

/etc/apache2/sites-available/<subdomain>.conf:

<VirtualHost *:80>
    ServerName <subdomain>
    DocumentRoot /var/www/<subdomain>

    DirectoryIndex index.php index.html

    <Directory /var/www/<subdomain>>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/<subdomain>_error.log
    CustomLog ${APACHE_LOG_DIR}/<subdomain>_access.log combined
</VirtualHost>

Enable it and reload:

sudo a2ensite <subdomain>.conf
sudo apache2ctl configtest
sudo systemctl reload apache2

Sanity-check over HTTP before TLS — this should redirect to /wp-admin/install.php:

curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" http://<subdomain>/

8. Enable HTTPS with Certbot

Certbot writes <subdomain>-le-ssl.conf (SSL vhost) and adds the HTTP→HTTPS redirect:

sudo certbot --apache -d <subdomain> --non-interactive --agree-tos -m <email> --redirect

Verify:

curl -s -o /dev/null -w "%{http_code}\n" http://<subdomain>/         # expect 301
curl -s -L https://<subdomain>/ | grep -o '<title>[^<]*</title>'     # expect "WordPress › Installation"

9. Finish the install in the browser

Because wp-config.php already contains the DB settings, WordPress skips the database step. Open https://<subdomain>/ and the wizard goes straight to picking the site title, admin username, and password. Set those and log in — the site is then yours to populate.

Notes

  • Harden the REST API user endpoints afterwards: see Disable wp-json user enumeration.
  • The Let's Encrypt cert auto-renews via certbot's scheduled task; nothing else to do.