Prerequisites
An Ubuntu 24.04 VPS with at least 2 vCPUs and 2 GB of RAM, a domain name whose A record points to the server's IP, and root SSH access (or sudo). Update the system first:
apt update && apt upgrade -yStep 1 — Install Nginx and MariaDB
apt install -y nginx mariadb-server
systemctl enable --now nginx mariadb
mysql_secure_installationAnswer "yes" to everything: remove anonymous users, disallow remote root login, drop the test database. Pick a strong MariaDB root password.
Step 2 — Install PHP 8.3-FPM
Ubuntu 24.04 ships PHP 8.3 out of the box — no third-party PPA needed:
apt install -y php8.3-fpm php8.3-mysql php8.3-curl php8.3-gd php8.3-mbstring \
php8.3-xml php8.3-zip php8.3-intl php8.3-imagick php8.3-bcmath
systemctl enable --now php8.3-fpmStep 3 — Tune php.ini for WordPress
Edit /etc/php/8.3/fpm/php.ini and adjust these values:
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300Apply with systemctl reload php8.3-fpm. The upload cap must stay consistent with Nginx's client_max_body_size, set in step 6.
Step 4 — Create the database
mariadbCREATE DATABASE wordpress DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp_user'@'localhost' IDENTIFIED BY 'AVeryStrongPassword!';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wp_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;Never use the MariaDB root account in wp-config.php: a dedicated user limits the blast radius of a credential leak.
Step 5 — Download WordPress
mkdir -p /var/www/example.com
cd /tmp
wget https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz
cp -r wordpress/* /var/www/example.com/
chown -R www-data:www-data /var/www/example.com
chmod -R u=rwX,go=rX /var/www/example.comGenerate the configuration file:
cd /var/www/example.com
cp wp-config-sample.php wp-config.php
curl -s https://api.wordpress.org/secret-key/1.1/salt/ > /tmp/salts.txtEdit wp-config.php: fill in DB_NAME, DB_USER, DB_PASSWORD, replace the eight security keys block with the contents of /tmp/salts.txt, then add:
define('FS_METHOD', 'direct');
define('DISABLE_WP_CRON', true);FS_METHOD prevents the FTP credential prompt on every plugin update. DISABLE_WP_CRON stops scheduled tasks from firing on every page view — we replace it with a real system cron in step 8.
Step 6 — Nginx server block
Create /etc/nginx/sites-available/example.com:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.php index.html;
client_max_body_size 64M;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location ~ /\.ht {
deny all;
}
}Enable the site and validate the configuration:
ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginxStep 7 — HTTPS with Let's Encrypt
apt install -y certbot python3-certbot-nginx
certbot --nginx -d example.com -d www.example.comCertbot rewrites the server block for HTTPS and installs a systemd timer that renews the certificate automatically — check it with systemctl list-timers certbot.
Step 8 — System cron for WP-Cron
By default WordPress triggers its cron on every page load: unpredictable, and expensive under traffic spikes. With DISABLE_WP_CRON active, schedule execution every five minutes:
crontab -u www-data -e*/5 * * * * curl -s https://example.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1Step 9 — Finish the installation
Open https://example.com, pick the language and create the administrator account — never use "admin" as the username. Under Settings → Permalinks, select "Post name": the server block's try_files already handles rewrites, no .htaccess needed.
Verification
systemctl status nginx mariadb php8.3-fpm— all three services active.curl -I https://example.com— a 200 response over HTTPS.- WordPress "Site Health" tool: no critical warnings.
Troubleshooting
- 502 Bad Gateway: PHP-FPM stopped or wrong socket — compare
ls /run/php/with thefastcgi_passdirective, then readjournalctl -u php8.3-fpm. - "Error establishing a database connection": test the credentials manually with
mariadb -u wp_user -p wordpress. - Uploads rejected:
upload_max_filesize,post_max_sizeandclient_max_body_sizemust be consistent on both sides. - White screen: set
WP_DEBUGtotruein wp-config.php and checkwp-content/debug.log.
Going further
Install WP-CLI to manage the site from the command line:
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/wpOn a high-traffic site, add an object cache: the php8.3-redis package, a local Redis server, and the "Redis Object Cache" plugin. Repetitive queries will stop hitting MariaDB.
Keep the whole stack patched with apt update && apt upgrade -y — PHP and MariaDB security fixes land in Ubuntu's repositories throughout the 24.04 lifecycle. A weekly wp plugin update --all && wp core update through WP-CLI closes the most exploited attack surface of any WordPress site.