BLACKBOXHelp Center

WordPress on LEMP (nginx, MariaDB, PHP)

Step-by-step installation of a WordPress site on an Ubuntu or Debian server, with HTTPS, daily backups and basic settings.

generalUpdated 2026-10-09~11 min read
On this page

This guide shows you how to install a WordPress site on the LEMP stack: Linux, nginx (the web server), MariaDB (the database) and PHP-FPM, with a free HTTPS certificate from Let's Encrypt. Run all commands on the server as root, in an SSH session: Connecting to your server over SSH (Windows, macOS, Linux). You'll find the server's IP address in the app.blackbox.md panel, under “Infrastructure” → “My Servers” (app.blackbox.md/my-servers), in the “IP” field on the server card.

Available to: the holder of a BLACKBOX VPS server without a control panel, with root access over SSH. The steps are written for Ubuntu 24.04 LTS and Debian 12; the differences for AlmaLinux 9 and CentOS Stream 9 are in a separate section. On a “VPS hosting” plan with a control panel, you create sites from the control panel: “VPS hosting” plans with a control panel.

Which plan and system to choose

For a typical site, a Standard S or Standard M plan is a good start:

Plan vCPU RAM Disk Price Good for
Standard S 1 2 GB 20 GB 443 MDL/month a company presentation site, a blog
Standard M 2 4 GB 40 GB 578 MDL/month an online store, a site with many plugins

All plans are listed in VPS plans: how to choose. When your site grows, move to a larger plan: Server upgrade: moving to a larger plan.

For the operating system, choose Ubuntu 24.04 LTS (selected by default when you order) or Debian 12. Ubuntu 20.04 LTS and Debian 11 come with PHP 7.4, which the PHP project no longer updates: don't use them for a new site.

Before you install, check that:

In all commands, replace DOMAIN with the site's name without www, for example example.com.

Step 1. Install nginx, MariaDB and PHP

  1. Update the package list and install the components. On Ubuntu and Debian, the services start automatically after installation.

    apt update
    apt install -y nginx mariadb-server php-fpm php-mysql php-curl php-gd php-mbstring php-xml php-zip php-intl curl
  2. Find out which PHP version is installed from the name of the PHP-FPM socket:

    ls /run/php/
    System PHP PHP-FPM socket PHP-FPM service
    Ubuntu 24.04 LTS 8.3 /run/php/php8.3-fpm.sock php8.3-fpm
    Debian 12 8.2 /run/php/php8.2-fpm.sock php8.2-fpm
    Ubuntu 22.04 LTS 8.1 /run/php/php8.1-fpm.sock php8.1-fpm

    Important The examples use PHP 8.3 (Ubuntu 24.04 LTS). On a different system, replace 8.3 with your version everywhere: in the /etc/php/8.3/… paths, in the php8.3-fpm service name and in the socket in the nginx configuration.

  3. Raise the maximum size of files uploaded to WordPress (by default, PHP accepts only 2 MB) and restart PHP-FPM:

    printf 'upload_max_filesize = 64M\npost_max_size = 64M\n' > /etc/php/8.3/fpm/conf.d/99-wordpress.ini
    systemctl restart php8.3-fpm

Step 2. Create the database

  1. Run the MariaDB security script and answer as shown in the table:

    mariadb-secure-installation
    Question Answer
    Enter current password for root (enter for none): press Enter
    Switch to unix_socket authentication [Y/n] n
    Change the root password? [Y/n] n if the script printed “You already have your root account protected, so you can safely answer 'n'.”; otherwise, Y and a strong password
    Remove anonymous users?, Disallow root login remotely?, Remove test database and access to it?, Reload privilege tables now? Y to all of them

    The MariaDB root user logs in through the system's root account (unix_socket authentication), with the mariadb command and no password.

  2. Generate a strong password for the database user and write it down. Below, it appears as DATABASE_PASSWORD:

    openssl rand -hex 20
  3. Open the MariaDB console:

    mariadb
  4. Create the wordpress database and the wpuser user, with rights on this database only, then exit:

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

The wpuser user can connect only from the server itself (localhost). Don't open port 3306 to the internet. More about MariaDB: Databases: MariaDB/MySQL and PostgreSQL.

Step 3. Copy the WordPress files

  1. Download the latest version from wordpress.org and extract it to /var/www/DOMAIN:

    mkdir -p /var/www/DOMAIN
    cd /tmp
    curl -O https://wordpress.org/latest.tar.gz
    tar -xzf latest.tar.gz -C /var/www/DOMAIN --strip-components=1
    rm latest.tar.gz
  2. Create wp-config.php and write the database details into it. Replace DATABASE_PASSWORD with the password from step 2.

    cd /var/www/DOMAIN
    cp wp-config-sample.php wp-config.php
    sed -i "s/database_name_here/wordpress/; s/username_here/wpuser/; s/password_here/DATABASE_PASSWORD/" wp-config.php
  3. Replace the 8 security keys (salts) with unique values generated by the WordPress.org service:

    curl -s https://api.wordpress.org/secret-key/1.1/salt/ -o /tmp/wp-salts.txt
    sed -i '/put your unique phrase here/d' wp-config.php
    sed -i '/#@-/r /tmp/wp-salts.txt' wp-config.php
    rm /tmp/wp-salts.txt
  4. Check the result. You should see 3 DB_ lines with your details and 8 lines, from AUTH_KEY to NONCE_SALT, with long random strings.

    grep -E "DB_(NAME|USER|PASSWORD)|_KEY'|_SALT'" wp-config.php
  5. Give ownership of the files to the www-data user, which nginx and PHP-FPM run as, and set the permissions:

    chown -R www-data:www-data /var/www/DOMAIN
    find /var/www/DOMAIN -type d -exec chmod 755 {} +
    find /var/www/DOMAIN -type f -exec chmod 644 {} +
    chmod 640 /var/www/DOMAIN/wp-config.php

    This way, WordPress can install its own updates and plugins, and wp-config.php, which holds the database password and the keys, can't be read by the system's other users.

Step 4. Configure nginx

  1. Create the site's file:

    nano /etc/nginx/sites-available/DOMAIN
  2. Paste the configuration below, replace DOMAIN, save with Ctrl+O and Enter, then exit with Ctrl+X:

    server {
        listen 80;
        server_name DOMAIN www.DOMAIN;
    
        root /var/www/DOMAIN;
        index index.php index.html;
    
        client_max_body_size 64M;
    
        location / {
            try_files $uri $uri/ /index.php?$args;
        }
    
        location ~* /wp-content/uploads/.*\.php$ {
            deny all;
        }
    
        location ~ \.php$ {
            include snippets/fastcgi-php.conf;
            fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        }
    
        location ~ /\.(?!well-known) {
            deny all;
        }
    }
    Line What it does
    try_files $uri $uri/ /index.php?$args; serves files that exist and sends all other requests to WordPress (required for permalinks)
    fastcgi_pass unix:/run/php/php8.3-fpm.sock; sends .php files to PHP-FPM; the path must match the socket from step 1 exactly
    client_max_body_size 64M; the maximum size of an uploaded file, the same as in PHP
    location ~* /wp-content/uploads/.*\.php$ stops PHP scripts from running in the uploads folder
    location ~ /\.(?!well-known) blocks hidden files (.htaccess, .git), but keeps /.well-known/ reachable for Let's Encrypt
  3. Enable the site, check the configuration and reload nginx:

    ln -s /etc/nginx/sites-available/DOMAIN /etc/nginx/sites-enabled/
    nginx -t
    systemctl reload nginx

    nginx -t must show syntax is ok and test is successful. If it shows an error, fix the line it points to and run the test again before you reload.

Step 5. Enable HTTPS

  1. Install certbot with its nginx plugin:

    apt install -y certbot python3-certbot-nginx
  2. Get the certificate for both names:

    certbot --nginx -d DOMAIN -d www.DOMAIN
  3. On the first run, certbot asks for an email address and for you to accept the Let's Encrypt terms. If it asks whether to redirect HTTP to HTTPS, choose the redirect. At the end, a message starting with “Congratulations!” appears: certbot has added the certificate to the nginx configuration and reloaded nginx.

  4. The certificate is valid for 90 days and renews automatically. Check the renewal:

    certbot renew --dry-run

Details and common errors: Web server with HTTPS: nginx or Apache and Let's Encrypt.

Step 6. Finish the installation in your browser

  1. Open https://DOMAIN. The WordPress installation wizard appears.
  2. Choose the site's language and continue.
  3. Fill in the site title, the administrator username, the password and the email address. Don't use admin as the username, and keep the strong password that WordPress suggests.
  4. Confirm the installation, then sign in to the admin dashboard at https://DOMAIN/wp-admin.

Install WordPress after step 5: that way, the site saves its address with https:// from the start.

Security

Also turn off the code editor in the WordPress dashboard, so that a compromised administrator account can't change the PHP files directly:

sed -i "/That's all, stop editing/i define( 'DISALLOW_FILE_EDIT', true );" /var/www/DOMAIN/wp-config.php

Daily backups

Back up the database and the wp-content folder, with its themes, plugins and uploaded files, every day. The script below runs daily through cron. If the /etc/cron.daily folder doesn't exist on the server, first install the cron package (Ubuntu, Debian) or cronie (AlmaLinux, CentOS Stream).

  1. Create the folder for the backups and the script file:

    mkdir -p /root/backup
    nano /etc/cron.daily/wordpress-backup
  2. Paste the content below, replace DOMAIN and save:

    #!/bin/sh
    D=$(date +%F)
    mysqldump --single-transaction wordpress | gzip > /root/backup/wordpress-db-$D.sql.gz
    tar -czf /root/backup/wp-content-$D.tar.gz -C /var/www/DOMAIN wp-content
    find /root/backup -type f -mtime +7 -delete
  3. Make the script executable and run it once as a test:

    chmod +x /etc/cron.daily/wordpress-backup
    /etc/cron.daily/wordpress-backup
    ls -lh /root/backup

    Two files with today's date should appear. From now on, the script runs every day, and backups older than 7 days are deleted automatically. On a small disk (Standard S has 20 GB), keep an eye on the used space: The server's disk is full.

Warning The backups in /root/backup are on the same server and are lost along with it. Copy them somewhere else regularly: to another server or to external storage. BLACKBOX does not make automatic backups of the data on your server: Backup strategy for your server.

Performance

OPcache. The PHP code cache is on by default. Check it: the output must contain Zend OPcache.

php-fpm8.3 -m | grep -i opcache

Page cache. From the WordPress dashboard, install a single page-caching plugin, for example WP Super Cache. The plugin saves pages once they are generated and serves them again without querying the database, which is much faster.

Swap memory. On plans with 2 GB of RAM, such as Standard S, add 1–2 GB of swap so that traffic spikes don't stop MariaDB for lack of memory: Adding swap space. If the server uses swap all the time, move to a larger plan. How to check the load: Diagnosing server load (CPU, RAM, disk, network).

AlmaLinux 9 and CentOS Stream 9

The steps are the same, with the differences below. SELinux is active: don't turn it off; set the correct labels instead.

  1. Enable PHP 8.3 (by default, the system installs PHP 8.0), install the packages and start the services, which don't start on their own on these systems. The curl extension is included in the base PHP package.

    dnf module enable -y php:8.3
    dnf install -y nginx mariadb-server php-fpm php-mysqlnd php-gd php-mbstring php-xml php-intl php-opcache php-pecl-zip nano tar
    systemctl enable --now nginx mariadb php-fpm
  2. Use the paths and names in the right-hand column:

    What Ubuntu and Debian AlmaLinux and CentOS Stream
    PHP-FPM user and file owner www-data apache
    PHP-FPM socket /run/php/php8.3-fpm.sock /run/php-fpm/www.sock
    nginx site file /etc/nginx/sites-available/DOMAIN and a link in sites-enabled /etc/nginx/conf.d/DOMAIN.conf, no link
    PHP upload limits /etc/php/8.3/fpm/conf.d/99-wordpress.ini /etc/php.d/99-wordpress.ini
    PHP-FPM service php8.3-fpm php-fpm
  3. On these systems, MariaDB listens on all interfaces: bind it to 127.0.0.1 as shown in Databases: MariaDB/MySQL and PostgreSQL.

  4. In the nginx configuration, replace the location ~ \.php$ block with this one (the snippets/fastcgi-php.conf file doesn't exist on these systems):

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass unix:/run/php-fpm/www.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
  5. Give ownership of the files to the apache user, then allow, in SELinux, writing to the site's folder (uploads, updates) and PHP connections to the internet (downloading updates and plugins):

    chown -R apache:apache /var/www/DOMAIN
    dnf install -y policycoreutils-python-utils
    semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/DOMAIN(/.*)?"
    restorecon -Rv /var/www/DOMAIN
    setsebool -P httpd_can_network_connect 1

    If you later move (mv) files into the site's folder from another folder, restore the SELinux labels:

    restorecon -Rv /var/www/DOMAIN
  6. Install certbot from the EPEL repository and start the renewal timer, which doesn't start on its own on these systems:

    dnf install -y epel-release
    dnf install -y certbot python3-certbot-nginx
    systemctl enable --now certbot-renew.timer
  7. To check OPcache, use php-fpm instead of php-fpm8.3:

    php-fpm -m | grep -i opcache

Common problems

Message or symptom Cause What to do
502 Bad Gateway PHP-FPM is stopped, or fastcgi_pass points to a different socket check systemctl status php8.3-fpm and compare fastcgi_pass with the output of ls /run/php/
The browser downloads the PHP file or shows the default nginx page the site file isn't enabled, or the block for .php is missing check the link in sites-enabled, run nginx -t and reload nginx
“Error establishing a database connection” (or its translation) wrong details in wp-config.php, or MariaDB is stopped compare the DB_ lines with step 2; check systemctl status mariadb
413 Request Entity Too Large the file is larger than client_max_body_size raise the limit in nginx and in the 99-wordpress.ini file, then reload nginx and restart PHP-FPM
WordPress asks for FTP details when you install a plugin the files don't belong to the PHP-FPM user run the chown command from step 3 again (apache:apache on AlmaLinux)
Uploads or updates fail on AlmaLinux with “Permission denied” SELinux labels repeat the semanage, restorecon and setsebool commands above
certbot can't get the certificate the domain doesn't point to the server, or port 80 is blocked check the A records; see The site does not open

nginx errors are logged in /var/log/nginx/error.log.

Frequently asked questions

Can I host several WordPress sites on the same server? Yes. For each site, repeat steps 2–6 with a separate database, user, folder in /var/www/ and nginx file; you don't need to run the mariadb-secure-installation script again. Also add the new site's database and folder to the backup script. Keep an eye on memory and disk space.

WordPress doesn't send emails (password resets, contact forms). A new server usually has no email-sending service set up. Install an SMTP plugin and connect it to an account with an email service.

I already have a WordPress site with another provider. How do I move it here? Prepare the server with steps 1, 2 and 4, then follow Moving a site to a BLACKBOX server: copy the files, import the database and switch the DNS at the end.

Can BLACKBOX install WordPress for me? Yes, as a professional service on request, through support. You receive the scope of work, the price and the terms in our team's offer: Professional services on request: administration, DevOps, migration.

Didn't find your answer?Write to us and the support team will reply. support@blackbox.md
Open a ticket