This guide installs WordPress on a fresh VPS with the best-performing stack for the resources: Nginx, PHP-FPM 8.3 and MariaDB. Allow about twenty minutes. The commands are written for Ubuntu 24.04 LTS, which ships PHP 8.3 by default; differences with Debian 12 are pointed out.
Prefer a graphical interface? Hosting with Plesk installs WordPress in one click: see our web hosting. And if you are unsure about the server size, first read which VPS to host WordPress (in French).
1. Prerequisites
- A freshly installed VPS with at least 2 GB of RAM (4 GB recommended for a business site).
- Root access over SSH. If you have not done so yet, follow first steps on your VPS.
- A domain name whose
Arecord points to the VPS IP address. See configuring DNS.
Throughout the guide, replace mysite.com with your domain.
2. Install Nginx, MariaDB and PHP 8.3
apt update && apt upgrade -y
apt install -y nginx mariadb-server \
php8.3-fpm php8.3-mysql php8.3-curl php8.3-gd php8.3-intl \
php8.3-mbstring php8.3-xml php8.3-zip php8.3-bcmath php-imagick
These extensions cover what WordPress asks for in its "Site Health" screen: intl for languages, gd and imagick for images, zip to install plugins from the admin panel.
php8.3 with php8.2 everywhere in this guide, or add Sury's repository to get PHP 8.3. WordPress recommends PHP 8.3 or newer.Then secure MariaDB: this command removes anonymous accounts and the test database.
mariadb-secure-installation
Answer Y to every question. On Ubuntu, MariaDB's root account authenticates through the system: mariadb run as root asks for no password, and that is intended. To go further, see securing MariaDB.
3. Create the database
First generate a strong password, then create a database and a user dedicated to this site:
openssl rand -base64 24
mariadb
CREATE DATABASE mysite CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'mysite'@'localhost' IDENTIFIED BY 'THE_GENERATED_PASSWORD';
GRANT ALL PRIVILEGES ON mysite.* TO 'mysite'@'localhost';
FLUSH PRIVILEGES;
EXIT;
One user per site: if one site is compromised, the attacker cannot reach the other sites' databases.
4. Download and configure WordPress
cd /var/www
wget https://wordpress.org/latest.tar.gz
tar xzf latest.tar.gz && rm latest.tar.gz
mv wordpress mysite.com
cd mysite.com
cp wp-config-sample.php wp-config.php
Open wp-config.php and fill in DB_NAME, DB_USER and DB_PASSWORD. Then replace the block of security keys (the lines AUTH_KEY to NONCE_SALT) with unique values generated by the official service:
curl -s https://api.wordpress.org/secret-key/1.1/salt/
Also add these two lines above /* That's all, stop editing! */:
define('DISABLE_WP_CRON', true); // replaced by a real cron, see step 8
define('DISALLOW_FILE_EDIT', true); // no code editor in the admin panel
Finally, give the files to the web server user:
chown -R www-data:www-data /var/www/mysite.com
find /var/www/mysite.com -type d -exec chmod 755 {} \;
find /var/www/mysite.com -type f -exec chmod 644 {} \;
chmod 640 /var/www/mysite.com/wp-config.php
5. Configure Nginx
Create the file /etc/nginx/sites-available/mysite.com:
server {
listen 80;
listen [::]:80;
server_name mysite.com www.mysite.com;
root /var/www/mysite.com;
index index.php;
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;
}
# sensitive files
location ~ /\.(?!well-known) { deny all; }
location = /xmlrpc.php { deny all; }
location ~* /(?:uploads|files)/.*\.php$ { deny all; }
location ~* \.(?:css|js|jpe?g|png|gif|webp|avif|svg|ico|woff2?)$ {
expires 30d;
add_header Cache-Control "public";
access_log off;
}
}
Three rules deserve an explanation. The \. in \.php$ matters: without the backslash, the dot matches any character. Blocking xmlrpc.php closes an entry point widely used for brute-force attacks; only remove it if you use the WordPress mobile app or Jetpack. Finally, no PHP file must be able to run from the uploaded media folder.
ln -s /etc/nginx/sites-available/mysite.com /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx
6. Tune PHP
PHP's default limits block media uploads above 2 MB. Create an override file rather than editing php.ini: it survives updates.
cat > /etc/php/8.3/fpm/conf.d/99-wordpress.ini <<'EOF'
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
max_execution_time = 120
EOF
systemctl restart php8.3-fpm
Keep upload_max_filesize in line with client_max_body_size in Nginx, otherwise the smaller of the two values applies.
7. Enable HTTPS
apt install -y certbot python3-certbot-nginx
certbot --nginx -d mysite.com -d www.mysite.com --redirect
Certbot gets the certificate, adds the HTTPS configuration and redirects HTTP. Renewal is automatic. Details and troubleshooting in Nginx and Let's Encrypt.
8. Finish the installation
Open https://mysite.com: the WordPress wizard asks for the site name and the administrator account. Choose a username other than admin and a long password.
Then set up the system cron declared in step 4. By default, WordPress runs its scheduled tasks during visits, which slows some pages down and does not run when the site has no traffic.
echo '*/5 * * * * www-data /usr/bin/php /var/www/mysite.com/wp-cron.php >/dev/null 2>&1' > /etc/cron.d/wordpress-mysite
Finally, in Settings → Permalinks, choose "Post name": readable addresses are better for SEO.
9. Secure and optimise
The site works. Here is what makes it last, in order of priority:
- Firewall: only the SSH, 80 and 443 ports open. See configuring UFW.
- Key-based SSH and root login disabled. See securing SSH.
- Fail2ban on SSH and on
wp-login.php. See advanced Fail2ban. - Daily off-server backups, files and database. See backups with Borg.
- Performance: OPcache, Nginx page cache and Redis. See optimising PHP-FPM and the Nginx cache.
Frequently asked questions
MySQL or MariaDB for WordPress?
Both work. WordPress recommends MySQL 8.0 or newer, or MariaDB 10.11 or newer. MariaDB is the default database on Debian and Ubuntu, it is compatible with WordPress without any change, and it is the simplest choice on a VPS.
Why Nginx rather than Apache?
Nginx uses less memory per connection and serves static files more efficiently, which matters on a small VPS. Apache remains a good choice if you depend on .htaccess files, which Nginx does not read: with Nginx, rewrite rules go in the server configuration.
How many WordPress sites can I install on a VPS?
As many as memory allows. Repeat steps 3 to 7 for each site, with a separate database, user, folder and Nginx file. A 4 GB VPS comfortably hosts several small sites with caching enabled.
How do I update WordPress on a VPS?
From the admin panel, as on any hosting, since the files belong to the web server user. Take a backup before every major update. To manage several sites from the command line, the WP-CLI tool updates everything with a single command.



