Skip to content

TutorialsCMS & websites

How to install WordPress with Nginx, PHP-FPM and MariaDB (LEMP)

Install WordPress on Ubuntu or Debian with Nginx, PHP-FPM and MariaDB: verified download, official Nginx rules, Let’s Encrypt TLS, WP-CLI, backups and updates.

  • Intermediate
  • 45 min read
  • Updated

Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13

This guide is not available in your language yet, so it is shown in English.

On this page
  1. Prerequisites
  2. Step 1 — Install Nginx, MariaDB and PHP-FPM
  3. Step 2 — Create the database and user
  4. Step 3 — Download WordPress and verify the checksum
  5. Step 4 — Configure wp-config.php
  6. Step 5 — Set ownership and permissions
  7. Step 6 — Configure Nginx and PHP
  8. Step 7 — Enable HTTPS with Certbot
  9. Step 8 — Finish the installation in the browser
  10. Step 9 — Install WP-CLI
  11. Step 10 — Apply hardening basics
  12. Back up and restore
  13. Update WordPress
  14. Troubleshooting
  15. 502 Bad Gateway
  16. 413 Request Entity Too Large or upload size exceeded
  17. Pretty permalinks return 404
  18. Error establishing a database connection
  19. WordPress asks for FTP credentials when installing plugins
  20. Next steps

WordPress is an open-source content management system for blogs, company sites and online shops, extended by a large catalogue of themes and plugins. A LEMP stack (Linux, Nginx, MariaDB, PHP-FPM) is a lean and fast way to host it on your own server, with full control over PHP settings, caching and security.

This guide builds the stack from your distribution's own packages on Ubuntu 24.04, Ubuntu 26.04, Debian 12 or Debian 13. You create a database, download WordPress from wordpress.org and verify the checksum it publishes, generate secret keys with the official API, apply the Nginx rules from WordPress's documentation, add a free Let's Encrypt certificate with Certbot and install a verified WP-CLI. The guide ends with hardening basics, backups, updates and fixes for common errors.

Prerequisites

  • A server running Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12 or Debian 13, without another web server listening on ports 80 and 443.
  • A non-root user with sudo rights and SSH key login, prepared as in Secure a new Linux server and Set up SSH keys.
  • A domain such as example.com with A (and AAAA) records for example.com and www.example.com pointing at the server, so that Certbot can validate it.

WordPress recommends PHP 8.3 or newer and MariaDB 10.11 or newer (or MySQL 8.0+), and still runs on PHP 7.4 and MySQL 5.5.5 as end-of-life legacy minimums. The distribution packages used in this guide give you these versions, all of which current WordPress releases support:

ReleasePHPMariaDB
Ubuntu 24.04 LTS8.310.11
Ubuntu 26.04 LTS8.511.8
Debian 128.210.11
Debian 138.411.8

Debian 12's PHP 8.2 works but is below WordPress's recommendation; for a new server, prefer Debian 13 or one of the Ubuntu releases. WordPress does not publish CPU or memory minimums. The WordPress hosting handbook recommends a PHP memory_limit of at least 128 MB and 256 MB as a default, so treat the figures below as a conservative starting point for one site.

ResourceMinimum (official)Suggested starting point
CPUNot published1 vCPU
RAMNot published (PHP memory_limit 128 MB or more)2 GB
DiskNot published20 GB SSD plus your media library

Step 1 — Install Nginx, MariaDB and PHP-FPM

Install the web server, the database server, PHP-FPM and the PHP extensions that the WordPress hosting handbook lists as required or highly recommended. Extensions such as json, hash, exif, fileinfo and openssl are already part of the base PHP packages:

Bash
sudo apt update
sudo apt install nginx mariadb-server php-fpm php-mysql php-curl php-xml php-mbstring php-intl php-zip php-gd php-imagick

Check the versions and the PHP-FPM socket:

Bash
php -v
mariadb --version
ls -l /run/php/

/run/php/ should contain a version-specific socket such as php8.3-fpm.sock and a php-fpm.sock link to it. The Nginx configuration in Step 6 uses that link, so it works on every release in this guide.

Step 2 — Create the database and user

Run the MariaDB hardening script first. On Ubuntu and Debian the MariaDB root account already logs in through the Unix socket, so press Enter when asked for the current root password, keep socket authentication, and answer Y to removing anonymous users, disallowing remote root login, removing the test database and reloading the privilege tables:

Bash
sudo mariadb-secure-installation

Generate a strong password for the WordPress database user and note it:

Bash
openssl rand -hex 24

Open the MariaDB shell with sudo mariadb and create the database and a user that can only access it. Replace change-me with the generated password:

SQL
CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'change-me';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Test the login as the new user. You should see a single row with the value 1:

Bash
mariadb -u wpuser -p wordpress -e "SELECT 1;"

Step 3 — Download WordPress and verify the checksum

Download the current release from wordpress.org together with the SHA-1 checksum that wordpress.org publishes for it, and compare the two:

Bash
cd /tmp
curl -fsSLO https://wordpress.org/latest.tar.gz
curl -fsSL https://wordpress.org/latest.tar.gz.sha1 -o latest.tar.gz.sha1
echo "$(cat latest.tar.gz.sha1)  latest.tar.gz" | sha1sum -c -

The output must be latest.tar.gz: OK. If it reports a mismatch, delete both files and download them again; a new release may have been published between the two downloads. Then unpack WordPress into /var/www/wordpress:

Bash
sudo tar -xzf /tmp/latest.tar.gz -C /var/www/
ls /var/www/wordpress

Step 4 — Configure wp-config.php

Create wp-config.php from the sample file and fill in the database settings. Replace change-me with the password from Step 2; the hexadecimal password contains no characters that would break the sed command:

Bash
cd /var/www/wordpress
sudo cp wp-config-sample.php wp-config.php
sudo sed -i "s/database_name_here/wordpress/; s/username_here/wpuser/; s/password_here/change-me/" wp-config.php

WordPress signs cookies and sessions with eight secret keys and salts. Fetch a fresh set from the official generator:

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

Open the file with sudo nano wp-config.php, delete the eight define lines that contain put your unique phrase here and paste the eight lines from the generator in their place. Every request to the generator returns new random values, so never reuse keys from another site. Save the file and check the syntax:

Bash
php -l /var/www/wordpress/wp-config.php

You should see No syntax errors detected.

Step 5 — Set ownership and permissions

WordPress's hardening guide recommends 755 for directories and 644 for files, and making wp-config.php readable only by you and the web server. This guide gives the files to the www-data user that PHP-FPM runs as, so that WordPress can install plugins and apply automatic updates on its own:

Bash
sudo chown -R www-data:www-data /var/www/wordpress
sudo find /var/www/wordpress -type d -exec chmod 755 {} \;
sudo find /var/www/wordpress -type f -exec chmod 644 {} \;
sudo chmod 440 /var/www/wordpress/wp-config.php

Step 6 — Configure Nginx and PHP

Create the server block with sudo nano /etc/nginx/sites-available/wordpress. It is based on the single-site configuration and the global restrictions in WordPress's Nginx documentation, adapted to the Debian and Ubuntu package layout. The deny rules come before the PHP location so that PHP files inside uploads can never run, and hidden files except .well-known are blocked:

Nginx
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/wordpress;
    index index.php;

    client_max_body_size 64M;

    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    location ~ /\.(?!well-known) {
        deny all;
    }

    location ~* /(?:uploads|files)/.*\.php$ {
        deny all;
    }

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

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

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|webp)$ {
        expires max;
        log_not_found off;
    }
}

Raise PHP's upload and memory limits to match client_max_body_size. The drop-in file goes into the PHP-FPM configuration directory of your PHP version, which the first command detects:

Bash
PHPV=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
sudo tee /etc/php/$PHPV/fpm/conf.d/99-wordpress.ini > /dev/null <<'EOF'
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
max_execution_time = 120
EOF
sudo systemctl restart php$PHPV-fpm

Enable the site, disable the default page, test the configuration and reload Nginx:

Bash
sudo ln -s /etc/nginx/sites-available/wordpress /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
curl -I http://example.com

nginx -t must report syntax is ok and test is successful. The curl request returns a redirect to /wp-admin/install.php, which shows that PHP and the database connection work.

Step 7 — Enable HTTPS with Certbot

Open the firewall for SSH and web traffic, then let Certbot's Nginx plugin obtain a Let's Encrypt certificate and add the HTTPS configuration and the HTTP-to-HTTPS redirect to your server block. Nginx reverse proxy with Certbot explains Certbot and its options in more detail:

Bash
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

This uses the Certbot packages from your distribution; the Certbot team recommends its snap instead, as the Nginx guide shows. Either works, but install only one of them. Certbot asks for an email address for expiry notices and for agreement to the Let's Encrypt terms. Its packages install a systemd timer that renews certificates automatically; test renewal once:

Bash
sudo certbot renew --dry-run

Step 8 — Finish the installation in the browser

Open https://example.com. WordPress asks for the language, then for the site title, an administrator username, a password and an email address. Do not use admin or another obvious username, keep the strong generated password or use your password manager, and only tick "Discourage search engines" for a staging site.

After logging in at https://example.com/wp-admin, open Settings → Permalinks, choose Post name and save. Open any post: if it loads under its pretty URL, the try_files rule works.

WordPress emails password resets, new-user details and comment notifications through PHP's mail function, which needs a local mail server that this stack does not include, so install an SMTP plugin and connect it to your relay with host smtp.example.com, port 587 and STARTTLS.

Step 9 — Install WP-CLI

WP-CLI manages WordPress from the shell: updates, plugins, users, search-and-replace and database exports. Download the official Phar build with its SHA-512 checksum and GPG signature, verify both, then install it:

Bash
cd /tmp
curl -fsSLO https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
curl -fsSLO https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.sha512
curl -fsSLO https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.asc
echo "$(cat wp-cli.phar.sha512)  wp-cli.phar" | sha512sum -c -
curl -fsSL https://raw.githubusercontent.com/wp-cli/builds/gh-pages/wp-cli.pgp | gpg --import
gpg --verify wp-cli.phar.asc wp-cli.phar

sha512sum must print wp-cli.phar: OK, and gpg must report a good signature from the WP-CLI release key with the fingerprint 63AF 7AA1 5067 C056 16FD DD88 A3A2 E8F2 26F0 BC06, as published in the WP-CLI handbook. A warning that the key is not certified with a trusted signature is normal for a freshly imported key. Then install the Phar and run it as www-data, the owner of the files:

Bash
php wp-cli.phar --info
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
sudo -u www-data wp --path=/var/www/wordpress core version

The last command prints the installed WordPress version.

Step 10 — Apply hardening basics

Add two constants to wp-config.php with sudo nano /var/www/wordpress/wp-config.php, above the line that says to stop editing. The first removes the built-in plugin and theme file editor, which WordPress's hardening guide calls often the first tool an attacker uses after logging in; the second forces logins and the admin area over HTTPS:

Text
define( 'DISALLOW_FILE_EDIT', true );
define( 'FORCE_SSL_ADMIN', true );

Then check that PHP files in the uploads folder really cannot run, as WordPress's Nginx documentation recommends. The request must return 403 Forbidden:

Bash
sudo -u www-data mkdir -p /var/www/wordpress/wp-content/uploads
echo '<?php echo "test";' | sudo -u www-data tee /var/www/wordpress/wp-content/uploads/test.php > /dev/null
curl -I https://example.com/wp-content/uploads/test.php
sudo rm /var/www/wordpress/wp-content/uploads/test.php

Further basics from the hardening guide: install plugins and themes only from wordpress.org or other trusted sources and delete the ones you do not use, enable two-factor authentication for administrators with a plugin, and keep the server itself patched, for example with Ubuntu's or Debian's unattended-upgrades.

Back up and restore

A WordPress site consists of the database, the wp-content folder (themes, plugins, uploads), wp-config.php and your Nginx server block. The core files can always be downloaded again. Create the backups with MariaDB's own dump tool and tar:

Bash
sudo mkdir -p /opt/backups
sudo sh -c 'mariadb-dump --single-transaction --default-character-set=utf8mb4 wordpress | gzip > /opt/backups/wordpress-db-$(date +%F).sql.gz'
sudo tar czf /opt/backups/wordpress-files-$(date +%F).tar.gz -C /var/www/wordpress wp-content wp-config.php
sudo cp /etc/nginx/sites-available/wordpress /opt/backups/nginx-wordpress-$(date +%F).conf
sudo chmod 600 /opt/backups/wordpress-* /opt/backups/nginx-wordpress-*

--single-transaction takes a consistent snapshot of the InnoDB tables without locking the site. Copy /opt/backups to another machine or object storage regularly; a backup on the same server is lost together with it.

To restore on a new server, repeat Steps 1 to 3 and 6 to 7. In Step 2, create the database user with the same password that DB_PASSWORD in the backed-up wp-config.php contains. Then put the files back and import the database:

Bash
sudo tar xzf /opt/backups/wordpress-files-2026-10-09.tar.gz -C /var/www/wordpress
gunzip -c /opt/backups/wordpress-db-2026-10-09.sql.gz | sudo mariadb wordpress
sudo chown -R www-data:www-data /var/www/wordpress
sudo chmod 440 /var/www/wordpress/wp-config.php

Replace the dates with those in your file names. If the site moves to a new domain, run sudo -u www-data wp --path=/var/www/wordpress search-replace 'https://old.example.com' 'https://example.com' after the import.

Update WordPress

By default WordPress installs minor and security releases automatically in the background; Dashboard → Updates shows whether automatic updates for major versions are enabled too, and plugins and themes have their own auto-update switches. Read the release notes before a major update, back up first and update manually with WP-CLI if you prefer to control the timing:

Bash
sudo -u www-data wp --path=/var/www/wordpress core check-update
sudo -u www-data wp --path=/var/www/wordpress core update
sudo -u www-data wp --path=/var/www/wordpress core update-db
sudo -u www-data wp --path=/var/www/wordpress plugin update --all
sudo -u www-data wp --path=/var/www/wordpress theme update --all

Nginx, MariaDB and PHP come from your distribution and are updated with sudo apt update && sudo apt upgrade. WP-CLI updates itself with sudo wp cli update. When you upgrade the operating system to a new release with a different PHP version, check that your plugins support it and repeat the PHP drop-in file from Step 6 for the new version.

Troubleshooting

502 Bad Gateway

Nginx cannot reach PHP-FPM. Check that the service runs with systemctl status 'php*-fpm' --no-pager and that /run/php/php-fpm.sock exists. If your system has no php-fpm.sock link, set fastcgi_pass to the version-specific socket, for example unix:/run/php/php8.3-fpm.sock, then run sudo nginx -t and reload Nginx. The Nginx error log at /var/log/nginx/error.log names the exact cause.

413 Request Entity Too Large or upload size exceeded

Nginx rejects requests larger than client_max_body_size, and PHP rejects files larger than upload_max_filesize or post_max_size. Raise all three to the same value, restart PHP-FPM and reload Nginx. Media → Add New shows the effective maximum upload size.

The location / block is missing its try_files $uri $uri/ /index.php?$args; line, or another server block answers for the domain. Run sudo nginx -T | grep -n server_name to see all server blocks, fix the configuration, test it with sudo nginx -t and reload.

Error establishing a database connection

The values in wp-config.php do not match the database user, or MariaDB is not running. Test the credentials with mariadb -u wpuser -p wordpress -e "SELECT 1;", check DB_HOST (it must be localhost) and run systemctl status mariadb --no-pager.

WordPress asks for FTP credentials when installing plugins

PHP-FPM cannot write to the WordPress folder, usually after files were copied in as root. Restore the ownership with sudo chown -R www-data:www-data /var/www/wordpress, or, if you chose the stricter ownership model, install plugins with WP-CLI instead.

Next steps

Frequently asked questions

Which PHP version does each release install, and does WordPress support it?

Ubuntu 24.04 ships PHP 8.3, Ubuntu 26.04 PHP 8.5, Debian 12 PHP 8.2 and Debian 13 PHP 8.4. Current WordPress releases support all four. WordPress recommends PHP 8.3 or newer, so Debian 12 works but sits below the recommendation.

Do I need an .htaccess file with Nginx?

No. Nginx ignores .htaccess files. Pretty permalinks work through the try_files line in the server block, which sends requests for missing files to index.php, and the deny rules in the same block replace the usual .htaccess protections.

Should the web server own the WordPress files?

It is a trade-off. Giving www-data ownership lets WordPress install plugins and apply automatic updates itself. WordPress's hardening guide prefers core files owned by your own user; then you update through WP-CLI or SSH instead of the dashboard.

How do I back up WordPress on a LEMP server?

Dump the database with mariadb-dump, archive wp-content and wp-config.php, and keep a copy of the Nginx server block. Store the archives off the server. The WordPress core files can always be downloaded again.

Can I run this on a HyperDC server?

Yes. The steps work on a HyperDC Linux VPS, VDS or dedicated server with root access running Ubuntu 24.04, Ubuntu 26.04, Debian 12 or Debian 13. Size the server for your traffic, plugins and media library.

Sources

Генерирај лозинка

Please confirm