WordPress runs well on Ubuntu 24.04 with Nginx, PHP-FPM, and MariaDB when you want full control over the web server, PHP runtime, database, files, TLS, and backups. In this tutorial, you will install WordPress on a Raff Linux VM, configure the Nginx WordPress server block, keep MariaDB local to the server, protect wp-config.php, enable HTTPS with Let's Encrypt, and verify the finished site end to end.
Ubuntu 24.04's PHP 8.3 baseline matches WordPress's current recommended PHP requirement, and the MariaDB version available on Ubuntu 24.04 meets WordPress's modern database baseline. This guide uses a single-server WordPress VPS design: Nginx, PHP-FPM, MariaDB, and WordPress all run on the same VM. For a small site, start with at least 2 GB RAM; use more memory when plugins, backups, imports, WooCommerce, or traffic increase PHP and database load.
You need a domain whose DNS can point to the VM, SSH access with a non-root sudo user, and a fresh or maintained Ubuntu 24.04 server. Keep the domain placeholder example.com until you replace it with your real hostname.
Step 1 — Confirm DNS and Update Ubuntu
Point the domain's A record to the public IPv4 address of your Raff Linux VM. Only add an AAAA record when IPv6 is configured and reachable on the same server.
From your workstation, check the hostnames you plan to include in the certificate:
dig +short A example.com dig +short A www.example.com dig +short AAAA example.com dig +short AAAA www.example.com
If you do not use www.example.com, do not add it to the Nginx server_name or Certbot command later. Only request names that actually resolve to this VM.
Then connect over SSH and update Ubuntu:
sudo apt update sudo apt upgrade -y
If the upgrade reports that a reboot is required, reboot before continuing and reconnect over SSH:
sudo reboot
Verify: every hostname you plan to serve should resolve to the intended VM address, and sudo apt update should complete without repository errors.
Step 2 — Install Nginx, MariaDB, PHP-FPM, and WordPress Extensions
Install Nginx, MariaDB, PHP-FPM, and the PHP extensions commonly needed by WordPress:
sudo apt install -y \ nginx \ mariadb-server \ php-fpm \ php-mysql \ php-curl \ php-gd \ php-intl \ php-mbstring \ php-soap \ php-xml \ php-zip \ php-imagick \ curl \ tar \ openssl
Enable the core services:
sudo systemctl enable --now nginx mariadb php8.3-fpm
Check the installed versions:
nginx -v php -v mariadb --version
WordPress currently recommends PHP 8.3 or newer, MariaDB 10.11 or newer or MySQL 8.0 or newer, plus HTTPS. Ubuntu 24.04's standard PHP-FPM service is php8.3-fpm; if you deliberately installed another PHP version, use that service and socket consistently in later steps.
Verify: Run:
systemctl is-active nginx mariadb php8.3-fpm
All three services should return active.
Step 3 — Secure MariaDB Before Creating WordPress Data
Run MariaDB's security helper:
sudo mariadb-secure-installation
For a local single-server WordPress deployment, keep local administrative access protected, remove anonymous users, remove the test database, and disallow unnecessary remote root access. WordPress will use its own database user rather than the MariaDB root account.
Check that MariaDB is listening locally:
sudo ss -ltnp | grep 3306 || true
Do not open TCP port 3306 in UFW or another public firewall for this architecture.
Verify: sudo mariadb -e "SELECT VERSION();" should return the database version, and there should be no public firewall rule exposing port 3306.
Step 4 — Create a Dedicated WordPress Database and User
Generate a strong database password and save it in your password manager:
openssl rand -hex 24
Open the MariaDB shell:
sudo mariadb
Create the database and a local-only user. Replace use_a_unique_password with the password you generated:
CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'use_a_unique_password'; GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost'; FLUSH PRIVILEGES; EXIT;
Test the WordPress account:
mariadb -u wpuser -p wordpress
Then run:
SELECT DATABASE(), CURRENT_USER(); EXIT;
Verify: The query should show wordpress as the active database and wpuser@localhost as the current user.
Step 5 — Download WordPress from the Official Source
Download the current WordPress archive from WordPress.org:
cd /tmp curl -fLO https://wordpress.org/latest.tar.gz
Extract it:
tar -xzf latest.tar.gz
Create the site directory and copy the WordPress files:
sudo mkdir -p /var/www/example.com/public sudo cp -a /tmp/wordpress/. /var/www/example.com/public/
Confirm the expected core files exist:
ls -l /var/www/example.com/public/wp-admin/index.php ls -l /var/www/example.com/public/wp-includes/version.php
You can inspect the downloaded WordPress version with:
grep -m1 '\$wp_version =' /var/www/example.com/public/wp-includes/version.php
Verify: wp-admin/index.php and wp-includes/version.php should both exist, and the version file should contain a WordPress version value.
Step 6 — Configure wp-config.php and Authentication Salts
Create wp-config.php from the sample:
sudo cp /var/www/example.com/public/wp-config-sample.php \ /var/www/example.com/public/wp-config.php
Open the file:
sudo nano /var/www/example.com/public/wp-config.php
Set the database values created in Step 4:
define( 'DB_NAME', 'wordpress' ); define( 'DB_USER', 'wpuser' ); define( 'DB_PASSWORD', 'use_a_unique_password' ); define( 'DB_HOST', 'localhost' ); define( 'DB_CHARSET', 'utf8mb4' );
Generate fresh WordPress authentication keys and salts from the official API:
curl -s https://api.wordpress.org/secret-key/1.1/salt/
Replace the placeholder authentication key and salt definitions in wp-config.php with the generated values.
Disable the built-in plugin and theme file editors by adding this above the final stop-editing comment:
define( 'DISALLOW_FILE_EDIT', true );
This removes a common code-editing path from the WordPress dashboard if an administrator account is compromised.
Verify: Confirm the database constants exist without printing the password to your terminal history again:
sudo grep -E "DB_NAME|DB_USER|DB_HOST|DISALLOW_FILE_EDIT" \ /var/www/example.com/public/wp-config.php
You should see the expected database name, user, host, and DISALLOW_FILE_EDIT setting.
Step 7 — Apply Safer WordPress Ownership and File Permissions
WordPress's hardening guidance recommends keeping application code writable by the administrative user and granting the web server write access only where the site actually needs it. Use your SSH user as the owner and www-data as the group:
sudo chown -R "$USER":www-data /var/www/example.com
Keep the WordPress tree read-only to the web-server group by default:
sudo find /var/www/example.com -type d -exec chmod 750 {} \; sudo find /var/www/example.com -type f -exec chmod 640 {} \;
Create the uploads directory and grant PHP-FPM group write access only there:
sudo mkdir -p /var/www/example.com/public/wp-content/uploads sudo chown -R "$USER":www-data \ /var/www/example.com/public/wp-content/uploads sudo find /var/www/example.com/public/wp-content/uploads \ -type d -exec chmod 770 {} \; sudo find /var/www/example.com/public/wp-content/uploads \ -type f -exec chmod 660 {} \;
Keep plugin and theme code non-writable by the web-server group unless a specific, documented workflow requires otherwise. If a plugin needs a cache or temporary directory, grant write access to that directory only instead of recursively opening all of wp-content.
Keep wp-config.php restricted:
sudo chmod 640 /var/www/example.com/public/wp-config.php
Do not use chmod 777 on WordPress files or directories. With this model, dashboard-based plugin, theme, or core updates may require your administrative deployment workflow rather than broad PHP-FPM write access to application code.
Verify: Run:
stat -c '%U %G %a %n' \ /var/www/example.com/public/wp-config.php \ /var/www/example.com/public/wp-content \ /var/www/example.com/public/wp-content/uploads
The WordPress code tree should remain non-writable by the web-server group, while uploads is explicitly writable by www-data.
Step 8 — Configure Nginx for WordPress and Block PHP in Uploads
Create the Nginx server block:
sudo nano /etc/nginx/sites-available/example.com
Add:
server { listen 80; listen [::]:80; server_name example.com www.example.com; root /var/www/example.com/public; index index.php index.html; client_max_body_size 64M; access_log /var/log/nginx/example.com.access.log; error_log /var/log/nginx/example.com.error.log; location / { try_files $uri $uri/ /index.php?$args; } location = /wp-config.php { deny all; } location ~* /(?:uploads|files)/.*\.php$ { deny all; } location ~ /\.(?!well-known).* { deny all; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.3-fpm.sock; } location = /xmlrpc.php { deny all; } location ~* \.(css|gif|ico|jpeg|jpg|js|png|svg|webp|woff|woff2)$ { expires 7d; access_log off; } }
The try_files rule supports WordPress pretty permalinks. The uploads restriction prevents a .php file placed under common upload paths from being executed by PHP-FPM. The xmlrpc.php block is optional: remove it if you use Jetpack, the WordPress mobile app, remote publishing, or another XML-RPC-dependent integration.
Enable the site and remove the default site symlink:
sudo ln -s /etc/nginx/sites-available/example.com \ /etc/nginx/sites-enabled/example.com sudo rm -f /etc/nginx/sites-enabled/default
Test and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Verify: sudo nginx -t should report that the configuration syntax is successful, and curl -I http://127.0.0.1 -H 'Host: example.com' should return an HTTP response from the new server block.
Step 9 — Configure UFW Without Locking Out SSH
Check UFW first:
sudo ufw status
If UFW is active, allow the existing SSH path before changing web rules:
sudo ufw allow OpenSSH sudo ufw allow 'Nginx Full' sudo ufw status
Do not open MariaDB port 3306 publicly for this single-VM WordPress setup.
If UFW is currently inactive, review your SSH access and firewall policy before enabling it. Do not blindly enable a firewall on a remote server without confirming that your management path is allowed.
Verify: When UFW is active, the status should allow SSH plus HTTP/HTTPS and should not contain a public MariaDB rule.
Step 10 — Enable HTTPS with Let's Encrypt
Install Certbot and its Nginx plugin:
sudo apt install -y certbot python3-certbot-nginx
Request a certificate and configure an HTTP-to-HTTPS redirect for the hostnames that resolve to this VM:
sudo certbot --nginx --redirect \ -d example.com \ -d www.example.com
If you do not use www.example.com, request only -d example.com. Certbot validation must succeed for every hostname included in the request.
Check the certificate and renewal path:
sudo certbot certificates sudo certbot renew --dry-run
For deeper certificate troubleshooting, use How to Secure Nginx with Let's Encrypt on Ubuntu 24.04.
Verify: Open https://example.com or run:
curl -I https://example.com
The connection should complete over HTTPS, and an HTTP request should redirect to HTTPS after Certbot configures the site.
Step 11 — Complete the WordPress Browser Installation
Open:
https://example.com
Choose the site language and enter:
- Site title
- Administrator username
- Strong administrator password
- Administrator email address
- Search-engine visibility preference
Avoid predictable administrator usernames such as admin. Complete the installer, then sign in at:
https://example.com/wp-admin/
After login, immediately review Dashboard → Updates and apply required WordPress, theme, and plugin updates using an update method compatible with the file-permission model from Step 7.
Verify: You should be able to sign in to /wp-admin/, open the site homepage in another browser window, and load a WordPress page over HTTPS without a certificate warning.
::cta Deploy Your Server Deploy a Raff Linux VM and use this tutorial to build a self-managed WordPress VPS with Nginx, PHP-FPM, MariaDB, and HTTPS. :::
Step 12 — Verify WordPress, Nginx, PHP-FPM, and the Uploads Restriction
Run the service checks:
systemctl is-active nginx mariadb php8.3-fpm
Check the public HTTP and HTTPS behavior:
curl -I http://example.com curl -I https://example.com curl -I https://example.com/wp-login.php
The HTTP request should redirect to HTTPS. The HTTPS homepage and login page should return a valid application response.
Now test the Nginx uploads restriction with a temporary PHP probe:
sudo mkdir -p /var/www/example.com/public/wp-content/uploads printf '%s\n' '<?php echo "this-should-not-run"; ?>' \ | sudo tee /var/www/example.com/public/wp-content/uploads/raff-php-test.php >/dev/null curl -i https://example.com/wp-content/uploads/raff-php-test.php sudo rm -f /var/www/example.com/public/wp-content/uploads/raff-php-test.php
You want Nginx to deny the request rather than execute the PHP file.
Check recent service errors:
sudo tail -n 50 /var/log/nginx/example.com.error.log sudo journalctl -u php8.3-fpm -n 50 --no-pager sudo journalctl -u mariadb -n 50 --no-pager
Verify: All three services should be active, HTTP should redirect to HTTPS, the WordPress homepage and login should load, and the uploads PHP probe should be denied.
Step 13 — Configure Backups and Production Maintenance
A working WordPress installation is not complete until you can restore both the database and site files. At minimum, back up:
- the MariaDB
wordpressdatabase /var/www/example.com/public- the Nginx site configuration
- any external DNS or application configuration needed for recovery
Create a manual database backup example:
mkdir -p ~/backups mariadb-dump -u wpuser -p wordpress | gzip > ~/backups/wordpress.sql.gz
Create a file backup example:
tar -czf ~/backups/wordpress-files.tar.gz \ /var/www/example.com/public
Store at least one backup away from the VM. Raff Data Protection can protect the server layer, while Object Storage can be used as an off-server destination when your backup workflow supports S3-compatible storage. A backup should not be considered reliable until you have tested restoring it.
For ongoing maintenance:
- keep Ubuntu, PHP, WordPress core, themes, and plugins patched
- remove unused themes and plugins
- use multi-factor authentication for administrator accounts
- monitor disk usage before large imports or backup jobs
- review Nginx, PHP-FPM, and MariaDB logs when application behavior changes
- add caching only after measuring the actual bottleneck
Verify: Confirm both backup files exist and are non-empty:
ls -lh ~/backups/wordpress.sql.gz ~/backups/wordpress-files.tar.gz gzip -t ~/backups/wordpress.sql.gz
Then prove the database dump can be restored into a disposable database:
sudo mariadb -e \ "DROP DATABASE IF EXISTS wordpress_restore_test; CREATE DATABASE wordpress_restore_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" gunzip -c ~/backups/wordpress.sql.gz \ | sudo mariadb wordpress_restore_test sudo mariadb -e \ "SELECT COUNT(*) AS table_count FROM information_schema.tables WHERE table_schema='wordpress_restore_test';"
The table count should be greater than zero for a populated WordPress database. Remove only the disposable restore target after validation:
sudo mariadb -e "DROP DATABASE wordpress_restore_test;"
For a production recovery rehearsal, also restore the WordPress files into an isolated path and verify the site against the restored database without touching production.
Step 14 — Clean Up or Roll Back the Installation
If the installation is working and you are keeping it, remove the downloaded archive and extracted temporary source:
rm -rf /tmp/wordpress /tmp/latest.tar.gz
If you are abandoning this test installation, the following rollback is destructive. Back up anything you need first.
Disable the Nginx site:
sudo rm -f /etc/nginx/sites-enabled/example.com sudo nginx -t sudo systemctl reload nginx
Remove the site files only when you are certain they are no longer needed:
sudo rm -rf /var/www/example.com
Drop the WordPress database and user:
sudo mariadb
Then run:
DROP DATABASE wordpress; DROP USER 'wpuser'@'localhost'; EXIT;
If you also want to remove the Let's Encrypt certificate for this hostname, review the installed certificates first:
sudo certbot certificates
Then delete only the certificate name that belongs to this retired site:
sudo certbot delete --cert-name example.com
Verify: After a full rollback, the Nginx site symlink should be absent, the site directory should be removed, and SHOW DATABASES LIKE 'wordpress'; should return no WordPress database.
Conclusion
You now have WordPress running on Ubuntu 24.04 with Nginx, PHP 8.3, MariaDB, restricted configuration permissions, HTTPS, and an Nginx rule that blocks PHP execution from the uploads path. The installation also has a dedicated local database account, an explicit end-to-end verification flow, and a backup and rollback path.
This single-VM WordPress pattern is a fit when you need control over PHP, MariaDB, and server configuration. Compare current Raff pricing with the memory, CPU, and storage your plugins, backups, imports, and traffic require; 2 GB RAM is an initial sizing reference, not a guaranteed production requirement. To choose between a self-managed VPS and shared or managed hosting, read WordPress VPS Hosting: When It Beats Shared and Managed Hosting. Keep WordPress files and the database backed up separately from the VM, and test restores as described in Step 13.