Guides

WordPress on a VPS: install it on nginx, PHP-FPM and MariaDB step by step

Set up a WordPress VPS on Ubuntu 24.04 or Debian 13: nginx, PHP-FPM, MariaDB, WP-CLI, HTTPS with certbot, permissions, fail2ban and backups, step by step.

Published ~10 min read
On this page

Short answer: a WordPress VPS needs a web server, PHP-FPM with the extensions WordPress uses, and MariaDB or MySQL. On Ubuntu 24.04 or Debian 13, install nginx, PHP-FPM and MariaDB, create a database and user, install WordPress with WP-CLI, point an nginx server block at it and add HTTPS with certbot.

The requirements come from wordpress.org, the commands from the WP-CLI handbook, the WordPress server docs and the Debian and Ubuntu packaging, all read on 21 October 2026. The fail2ban filter further down was checked with fail2ban-regex 1.0.2 on Debian 12 against sample nginx log lines. The rest was not run for this guide, so replace example.com, the names and the PHP version with your own as you go.

What does WordPress need on a VPS?

The WordPress requirements page recommends PHP 8.3 or newer, MariaDB 10.11 or newer or MySQL 8.0 or newer, HTTPS, and Apache or nginx. Older setups down to PHP 7.4 and MySQL 5.5.5 still run it, but the page warns that those versions are past their end of life. As of 21 October 2026 the current release is WordPress 7.1.3, per the WordPress.org version API. What the usual VPS images ship:

DistributionPHPMariaDBMeets the recommendation
Ubuntu 24.04 LTS8.310.11Yes
Debian 138.411.8Yes
Debian 128.210.11PHP is one minor version below
Ubuntu 22.04 LTS8.110.6No: pick a newer release

WordPress itself names no RAM figure. The memory goes to MariaDB’s buffer pool and to the PHP-FPM workers, each of which can grow up to PHP’s memory_limit. How to size that is in PHP memory_limit. Start from a server that is already set up with a sudo user and SSH keys.

Step 1: install nginx, PHP-FPM and MariaDB

Ubuntu 24.04 · Debian 13
sudo apt update
sudo apt install -y nginx mariadb-server php-fpm php-mysql php-curl php-xml \
  php-mbstring php-zip php-intl php-imagick php-igbinary
ls /run/php/            # the socket, e.g. php8.3-fpm.sock on Ubuntu 24.04

These packages cover the extensions the WordPress hosting handbook lists as required (mysqli, json, hash) and highly recommended: php-xml brings dom and xml, while exif, fileinfo and openssl are already part of the base PHP packages on Debian and Ubuntu. Check what PHP-FPM actually loads:

Ubuntu 24.04 · use your PHP version
sudo php-fpm8.3 -m | grep -iE 'curl|dom|exif|fileinfo|igbinary|imagick|intl|mbstring|mysqli|opcache|zip'

Then secure MariaDB as described in installing MariaDB or MySQL on Ubuntu and Debian. On both systems the MariaDB root account logs in through the Unix socket, so sudo mariadb needs no password.

Step 2: create the database and its user

any Linux
openssl rand -base64 24            # a password for the database user
sudo mariadb
MariaDB
CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp_example'@'localhost' IDENTIFIED BY 'PASTE-THE-PASSWORD-HERE';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wp_example'@'localhost';
EXIT;

One database user per site, limited to its own database and to localhost, means a compromised site cannot read another site’s tables, and the database port can stay closed to the internet.

Step 3: install WordPress with WP-CLI

WP-CLI is one PHP file. The handbook installs it like this:

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

Now fetch WordPress, write wp-config.php and run the installation, all as www-data, the user PHP-FPM runs as on Debian and Ubuntu:

Ubuntu · Debian
sudo mkdir -p /var/www/example.com
sudo chown www-data:www-data /var/www/example.com
cd /var/www/example.com

sudo -u www-data wp core download
sudo -u www-data wp config create --dbname=wordpress --dbuser=wp_example --prompt=dbpass
sudo -u www-data wp core install --url=https://example.com --title="Example" \
  --admin_user=yourname --admin_email=you@example.com

--prompt=dbpass asks for the database password instead of leaving it in your shell history. Without --admin_password, WP-CLI generates the admin password and prints it once, so save it. Pick an admin name other than admin. The URL is already https:// because the certificate comes in step 5, which saves changing it afterwards.

Step 4: the nginx server block

/etc/nginx/sites-available/example.com
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/example.com;
    index index.php;

    # largest upload nginx accepts; match it in PHP below
    client_max_body_size 64m;

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

    # hidden files such as .htaccess and .git, but not the ACME challenge path
    location ~ /\.(?!well-known) {
        deny all;
    }

    # never run PHP from the uploads folder
    location ~* /(?:uploads|files)/.*\.php$ {
        deny all;
    }

    # only if nothing uses XML-RPC (Jetpack and the WordPress apps do)
    location = /xmlrpc.php {
        deny all;
    }

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

    location ~* \.(?:css|js|jpe?g|png|gif|ico|svg|webp|woff2?)$ {
        expires 30d;
        access_log off;
    }
}

try_files $uri $uri/ /index.php?$args is the line from the WordPress nginx docs that makes pretty permalinks work: anything that is not a real file goes to index.php. Order matters for the regex blocks, because nginx uses the first regex that matches: the uploads rule has to come before the PHP block or it never applies. snippets/fastcgi-php.conf ships with nginx on Debian and Ubuntu. Enable the site:

Ubuntu · Debian
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default        # the stock welcome page, optional
sudo nginx -t && sudo systemctl reload nginx

Step 5: HTTPS with certbot

The domain must already resolve to the server and ports 80 and 443 must be open, for example with sudo ufw allow 'Nginx Full'. Then:

Ubuntu · Debian
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
sudo certbot renew --dry-run           # confirm that renewal will work

Certbot adds the HTTPS listener, the certificate paths and the redirect from HTTP to HTTPS to the server block, and its systemd timer renews the certificate. The certificate section of the nginx SSL guide explains the details. Behind Cloudflare, use the SSL mode Full (strict); Flexible makes WordPress redirect in a loop, as ERR_TOO_MANY_REDIRECTS shows.

File ownership and permissions

Ubuntu · Debian
sudo chown -R www-data:www-data /var/www/example.com
sudo find /var/www/example.com -type d -exec chmod 755 {} +
sudo find /var/www/example.com -type f -exec chmod 644 {} +
sudo chmod 640 /var/www/example.com/wp-config.php

Directories 755 and files 644 are WordPress’s own recommendation, with a tighter mode for wp-config.php because it holds the database password. Making www-data the owner lets WordPress install updates and plugins from wp-admin. WordPress’s file permissions docs prefer files owned by a user account instead of the web server process, at the cost of those one-click updates. Whatever you choose, never use 777.

With several sites on one VPS, give each its own PHP-FPM pool and system user, so one hacked site cannot write into another:

/etc/php/8.3/fpm/pool.d/example.conf
[example]
user = wp_example
group = wp_example
listen = /run/php/example.sock
listen.owner = www-data
listen.group = www-data
pm = ondemand
pm.max_children = 10

Create the user with sudo useradd --system --user-group --shell /usr/sbin/nologin wp_example, make it the owner of that site’s files, point the site’s fastcgi_pass at unix:/run/php/example.sock and restart PHP-FPM.

OPcache, upload limits and an object cache

OPcache keeps compiled PHP in memory. On Debian 13 the php8.4-fpm package depends on php8.4-opcache, so it is installed with PHP-FPM; the php-fpm8.3 -m check in step 1 shows whether it is loaded on your system. Put your PHP settings in one file of your own so package updates leave them alone:

/etc/php/8.3/fpm/conf.d/99-wordpress.ini
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
opcache.memory_consumption = 192
opcache.max_accelerated_files = 20000
Ubuntu · Debian
sudo systemctl restart php8.3-fpm

The upload values match client_max_body_size in nginx; a mismatch is behind most 413 Request Entity Too Large errors. An object cache is the next step once the site gets busy: WordPress keeps database results only for the length of one request, and a persistent object cache (Redis or Memcached plus a drop-in plugin) keeps them between requests. It helps logged-in users, shops and wp-admin most; for anonymous visitors a page cache does more.

Updates and security

Keep WordPress, plugins and the OS current

Ubuntu · Debian
cd /var/www/example.com
sudo -u www-data wp core update
sudo -u www-data wp plugin update --all
sudo -u www-data wp theme update --all
sudo -u www-data wp core verify-checksums      # core files changed by someone?

WordPress installs minor core releases on its own by default. Every plugin is code that can carry a vulnerability, so delete the ones you do not use rather than only deactivating them. Turn off the theme and plugin file editor in wp-admin with sudo -u www-data wp config set DISALLOW_FILE_EDIT true --raw, and let unattended-upgrades install OS security updates, as set up in the initial server setup guide.

Ban password guessing on wp-login.php

By default a failed WordPress login answers the POST with the login form again, status 200, and a successful one redirects with 302. This filter counts only the failures, plus every POST to xmlrpc.php:

/etc/fail2ban/filter.d/wordpress-login.conf
[Definition]
failregex = ^<HOST> \S+ \S+ \[\] "POST /wp-login\.php[^"]*" 200
            ^<HOST> \S+ \S+ \[\] "POST /xmlrpc\.php
ignoreregex =
/etc/fail2ban/jail.d/wordpress.local
[wordpress-login]
enabled  = true
port     = http,https
filter   = wordpress-login
logpath  = /var/log/nginx/access.log
maxretry = 5
findtime = 10m
bantime  = 1h

The empty \[\] stands where the date was: fail2ban cuts the timestamp out of the line before it applies the regex. On Debian 12, against six sample lines in nginx’s default log format, fail2ban-regex 1.0.2 matched the two failed logins and the XML-RPC POST and skipped the successful login and the plain GET requests. Test it on your real log with sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-login.conf, then sudo fail2ban-client reload. Behind Cloudflare the log shows Cloudflare’s addresses, so configure nginx’s real IP module first or you ban the CDN. More jails are in the fail2ban guide.

Backups

Ubuntu · Debian · as root (sudo -i)
mkdir -p /root/wp-backups
cd /var/www/example.com
sudo -u www-data wp db export - | gzip > /root/wp-backups/db-$(date +%F).sql.gz
tar -czf /root/wp-backups/files-$(date +%F).tar.gz -C /var/www example.com

That pair is a complete copy: the database plus every file, wp-config.php and uploads included. Copy it off the server, for example with rsync, because a backup on the same disk dies with the disk. On a VMHeaven VPS the customer panel also takes backups of the whole server, by hand or on a daily, weekly or monthly schedule; take one before big updates. Restore a backup into a test directory once, so you know it works before you need it.

When the site breaks

  • “There has been a critical error on this website”: a PHP fatal error, usually a plugin or theme. The WordPress critical error guide finds the line in the log.
  • 502 Bad Gateway: PHP-FPM is down or the socket path in fastcgi_pass is wrong. ls /run/php/ and systemctl status php8.3-fpm show which.
  • “Error establishing a database connection”: MariaDB is stopped, or the name, user or password in wp-config.php differs from step 2.
  • Permalinks give 404: the try_files line is missing. nginx does not read .htaccess.

When Plesk with the WordPress Toolkit is the easier route

Everything above is yours to maintain: the OS, nginx, PHP, MariaDB, certificates, fail2ban and backups. If you would rather only run WordPress, VMHeaven’s Plesk hosting includes the WordPress Toolkit: clone a live site into staging, push the result back when it works, update plugins and themes across every install from one list, and apply the hardening WordPress ships without. Free auto-renewing SSL and daily backups with up to ten restore points are included, and the account is set up automatically about a minute after payment. A VPS stays the better choice when you need root: your own PHP and server settings, Redis, other services on the same machine, or several sites that you want to control down to the config file. For that, a Linux VPS with Ubuntu 24.04 or Debian 13 is the starting point for every step on this page.

Frequently asked

Is a VPS good for WordPress?

Yes, when you want root: your own PHP and server settings, caching, several sites and other services on one machine. In exchange you run the OS, web server, PHP and database updates, the security and the backups yourself. If you only want to run WordPress, managed hosting with a panel such as Plesk and its WordPress Toolkit is less work.

What do I need to install WordPress on a VPS?

A web server (nginx or Apache), PHP with PHP-FPM and the mysqli, curl, dom, mbstring, intl, zip and imagick extensions, and MariaDB or MySQL. WordPress.org recommends PHP 8.3 or newer, MariaDB 10.11 or newer or MySQL 8.0 or newer, and HTTPS. Ubuntu 24.04 and Debian 13 meet that with their own packages.

How do I install WordPress from the command line?

With WP-CLI: wp core download fetches the files, wp config create --dbname=NAME --dbuser=USER --prompt=dbpass writes wp-config.php, and wp core install --url --title --admin_user --admin_email creates the site and prints a generated admin password. Run them as the user PHP-FPM runs as, www-data on Debian and Ubuntu, before the site is reachable from the internet.

What permissions should WordPress files have on a VPS?

Directories 755 and files 644, with wp-config.php at 640 or tighter because it holds the database password. Never use 777. Owning the files as www-data lets WordPress update itself from wp-admin; with several sites, give each its own PHP-FPM pool and system user.

Related articles

All guides