WordPress is often associated with managed hosting, but a small VPS is enough for a single site if you are comfortable administering Linux. This guide deploys WordPress directly on Ubuntu 26.04 LTS with Nginx, PHP-FPM and MariaDB.

This is intentionally a single-application deployment. There is no Docker, Kubernetes or hosting control panel involved. You get a small and understandable stack that you can inspect, update and back up yourself.

What are we deploying?

At the time of writing, the latest stable release available from WordPress.org is WordPress 7.0.4.

ComponentVersion / choice
Operating systemUbuntu 26.04 LTS "Resolute Raccoon"
Web serverNginx 1.28.x
Application runtimePHP 8.5 with PHP-FPM
DatabaseMariaDB 11.8.x
CMSWordPress 7.0.4
HTTPSLet's Encrypt with Certbot

WordPress currently recommends PHP 8.3 or newer, MariaDB 10.11 or newer (or MySQL 8.0 or newer), and HTTPS. Ubuntu 26.04's default packages meet those requirements.

Before you start

You need:

  • A fresh Ubuntu 26.04 LTS VPS with at least 1 GB of RAM for a small site.
  • A non-root user with sudo access.
  • A domain pointing to the VPS public IP.
  • SSH access to the server.
  • A strong, unique password for the WordPress database user.

Replace example.com and all placeholder passwords in the commands below with your own values.

Step 1: Update Ubuntu

Connect over SSH and update the base system:

sudo apt update && sudo apt upgrade -ybash

If the kernel was upgraded, reboot before continuing:

sudo rebootbash

Step 2: Install the stack

Install Nginx, MariaDB, PHP-FPM and the extensions WordPress commonly needs:

sudo apt install -y nginx mariadb-server mariadb-client \
  php-fpm php-cli php-mysql php-curl php-gd php-intl \
  php-mbstring php-xml php-zip php-imagick unzip curlbash

Check the installed versions and services:

nginx -v
php -v
mariadb -V
systemctl --no-pager --type=service --state=running | grep -E 'nginx|mariadb|php.*fpm'bash

Ubuntu 26.04 uses the versioned PHP-FPM socket below:

/run/php/php8.5-fpm.socktext

Check the actual socket if you installed a different PHP branch:

ls /run/php/bash

Step 3: Secure MariaDB and create the database

Run the security helper:

sudo mariadb-secure-installationbash

On a fresh Ubuntu installation, MariaDB's root account normally uses Unix socket authentication. You can therefore open the administrative shell with:

sudo mariadbbash

Create a dedicated database and user. Do not use the MariaDB root account from WordPress:

CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wordpress'@'localhost' IDENTIFIED BY 'replace-with-a-long-random-password';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'localhost';
FLUSH PRIVILEGES;
EXIT;sql

The database remains bound to localhost. A single-site VPS has no reason to expose MariaDB on the public internet.

Step 4: Create the web root

Use a dedicated directory for this site:

sudo mkdir -p /var/www/example.com/public
sudo chown -R www-data:www-data /var/www/example.combash

The Nginx and PHP-FPM processes will serve the site as www-data.

Step 5: Download WordPress

Download the current stable archive from the official WordPress endpoint:

cd /tmp
curl -fLO https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz
sudo cp -a wordpress/. /var/www/example.com/public/
sudo chown -R www-data:www-data /var/www/example.combash

Verify that the expected files are present:

sudo ls -la /var/www/example.com/publicbash

The latest.tar.gz URL follows the current stable WordPress release. If you need reproducible deployments, download a versioned archive and record its checksum instead of relying on latest.

Step 6: Configure wp-config.php

Create the configuration file from the example:

cd /var/www/example.com/public
sudo -u www-data cp wp-config-sample.php wp-config.php
sudo -u www-data nano wp-config.phpbash

Set the database values:

define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'wordpress' );
define( 'DB_PASSWORD', 'replace-with-a-long-random-password' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
define( 'DB_COLLATE', '' );php

Generate fresh salts from the official WordPress API:

curl -fsSL https://api.wordpress.org/secret-key/1.1/salt/bash

Replace the placeholder salt definitions in wp-config.php with the output. Never commit this file or publish its contents.

define('AUTH_KEY',         'the-random-value-given-by-the-api');
define('SECURE_AUTH_KEY',  'the-random-value-given-by-the-api');
define('LOGGED_IN_KEY',    'the-random-value-given-by-the-api');
define('NONCE_KEY',        'the-random-value-given-by-the-api');
define('AUTH_SALT',        'the-random-value-given-by-the-api');
define('SECURE_AUTH_SALT', 'the-random-value-given-by-the-api');
define('LOGGED_IN_SALT',   'the-random-value-given-by-the-api');
define('NONCE_SALT',       'the-random-value-given-by-the-api');php

Add these settings before the final comment in wp-config.php:

define( 'DISALLOW_FILE_EDIT', true );
define( 'WP_AUTO_UPDATE_CORE', 'minor' );php

DISALLOW_FILE_EDIT removes the theme and plugin editor from the administration panel. Core minor updates remain enabled while major updates can be tested before applying them.

Lock down the configuration file itself:

sudo chmod 640 /var/www/example.com/public/wp-config.phpbash

Step 7: Configure the Nginx server block

Create a dedicated virtual host:

sudo nano /etc/nginx/sites-available/example.combash

Use this configuration. The try_files fallback is what makes WordPress pretty permalinks work:

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;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.5-fpm.sock;
    }

    # PHP should never execute from the media upload directory.
    location ~* /wp-content/uploads/.*\.php$ {
        deny all;
    }

    # Do not expose hidden files, except ACME challenge files.
    location ~ /\.(?!well-known).* {
        deny all;
    }
}nginx

Enable the site and remove the default Nginx page:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginxbash

Check the response before continuing:

curl -I http://example.combash

You should receive an HTTP response from Nginx. The WordPress installer will be available once DNS resolves to the VPS.

Step 8: Adjust PHP limits

The Nginx upload limit is 64M, so PHP should allow at least the same value. Edit the FPM configuration:

sudo nano /etc/php/8.5/fpm/php.inibash

Set or update these values:

memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 120ini

Restart PHP-FPM after saving:

sudo systemctl restart php8.5-fpmbash

Step 9: Configure the firewall

Allow SSH and web traffic before enabling UFW:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status verbosebash

Do not open port 3306. MariaDB should remain reachable only from localhost.

Step 10: Enable HTTPS

Install Certbot and its Nginx integration:

sudo apt install -y certbot python3-certbot-nginxbash

Request a certificate for both hostnames:

sudo certbot --nginx -d example.com -d www.example.combash

Choose the HTTP-to-HTTPS redirect when prompted. Verify automatic renewal:

sudo certbot renew --dry-runbash

HTTPS is not an optional finishing touch for WordPress. It protects login credentials, administration sessions and site visitors.

Step 11: Finish the WordPress installation

Open the HTTPS URL in a browser:

https://example.comtext

Complete the installer with:

  • Site title.
  • Administrator username that is not admin.
  • A unique, long administrator password.
  • An administrator email address that you monitor.
  • Search engine visibility according to whether the site is ready for publication.

After logging in, visit Settings → Permalinks and save the preferred structure. The Nginx try_files rule already supports the resulting URLs.

Verify the deployment

Check the important services:

sudo systemctl is-active nginx
sudo systemctl is-active mariadb
sudo systemctl is-active php8.5-fpmbash

Check the public response and HTTPS redirect:

curl -I http://example.com
curl -I https://example.combash

Then verify from the WordPress dashboard that:

  • The site URL uses https://.
  • Media uploads work within the configured limit.
  • Pretty permalinks work.
  • The PHP version and database connection are healthy.
  • No unexpected plugin or theme editor is available.

Backups and maintenance

A VPS is not a backup. At minimum, back up the database and wp-content to storage outside the server:

sudo mariadb-dump wordpress | gzip > wordpress-$(date +%F).sql.gz
sudo tar -czf wp-content-$(date +%F).tar.gz \
  -C /var/www/example.com/public wp-contentbash

Test that you can restore both backups. Keep multiple generations and never store the only copy on the VPS.

For ongoing maintenance:

  • Apply Ubuntu security updates regularly.
  • Keep WordPress, themes and plugins updated.
  • Remove unused plugins and themes.
  • Monitor disk space, PHP-FPM logs and Nginx logs.
  • Review administrator accounts and application passwords.
  • Test backups and certificate renewal periodically.

Troubleshooting

Nginx returns 502 Bad Gateway

PHP-FPM is probably stopped or the socket path does not match the installed PHP version:

sudo systemctl status php8.5-fpm
ls -l /run/php/
sudo nginx -tbash

Permalinks return 404

Confirm that the server block contains:

try_files $uri $uri/ /index.php?$args;nginx

Then save the permalink settings again in WordPress and reload Nginx if the configuration changed.

Uploads fail with 413 Request Entity Too Large

Increase client_max_body_size in Nginx and make sure post_max_size is at least as large as upload_max_filesize in PHP. Restart PHP-FPM and reload Nginx after changing them.

WordPress asks for FTP credentials

Check that the WordPress files are owned by the account running PHP-FPM:

sudo chown -R www-data:www-data /var/www/example.combash

Do not solve this by making the whole web root world-writable.

References

AI-generated image disclosure

The cover image (/images/posts/wordpress-nginx-header.webp) was generated with the assistance of an AI model (deepseek-v4-flash). In accordance with Regulation (EU) 2024/1689 (EU Artificial Intelligence Act, Article 50), this content is disclosed as AI-generated. It is an original illustration created for this article without third-party images, logos or external assets. WordPress is a trademark of the WordPress Foundation; the name appears descriptively to identify the software discussed in this article. No affiliation or endorsement is implied.