How to Install WordPress with Nginx on Debian

This guide walks you through installing WordPress with Nginx on Debian, creating a production-ready LEMP stack (Linux, Nginx, MariaDB, PHP) that delivers fast page loads and efficient resource usage. WordPress powers blogs, business sites, and e-commerce stores with thousands of plugins and themes. By the end, you will have a configured database, optimized PHP settings, and free SSL certificates from Let’s Encrypt on your Debian server.

Additionally, this guide uses the default Nginx, MariaDB, and PHP versions included in Debian’s official repositories. As a result, this approach keeps the setup straightforward and ensures packages receive security updates through standard system maintenance.

Install LEMP For WordPress

The default PHP, MariaDB, and Nginx versions differ across Debian releases. Debian 13 (Trixie) includes PHP 8.4, MariaDB 11.8, and Nginx 1.26. Debian 12 (Bookworm) includes PHP 8.2, MariaDB 10.11, and Nginx 1.22. Debian 11 (Bullseye) includes PHP 7.4, MariaDB 10.5, and Nginx 1.18. All commands in this guide work across these versions, but you must adjust PHP socket paths to match your version.

Update Debian Before Installing LEMP Stack

Before you install WordPress, update your Debian system to ensure all packages are current and prevent potential dependency conflicts. Running updates first also ensures you receive the latest security patches for your base system. Open your terminal and run:

sudo apt update && sudo apt upgrade

The first command refreshes your package lists from the repositories, while the second command upgrades all installed packages to their newest versions. When prompted about disk space usage, type Y and press Enter to proceed.

Install the Necessary Packages

WordPress and its installation process require several utility packages that may not be present on minimal Debian installations. In particular, these tools handle file downloads, archive extraction, and version control operations. Install them with:

sudo apt install curl git wget unzip zip

Specifically, here’s what each package provides:

  • curl — Transfers data from URLs, useful for API requests and downloading files
  • wget — Downloads files from web servers, used to fetch the WordPress archive
  • unzip/zip — Extracts and creates ZIP archives, required for WordPress installation
  • git — Version control system, helpful for managing custom themes or plugins

Notably, if any packages are already installed, APT will simply skip them and confirm they’re at the latest version.

Install Nginx (LEMP Part 1)

Nginx serves as the web server in the LEMP stack. Specifically, Debian 13 includes Nginx 1.26, Debian 12 includes Nginx 1.22, and Debian 11 includes Nginx 1.18. For more detailed configuration options, see the guide on installing Nginx on Debian. To begin, install Nginx with:

sudo apt install nginx

After installing, subsequently verify Nginx is running:

systemctl status nginx

Expected output:

● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running)

However, if the Nginx service isn’t active, you can set it in motion with the following command:

sudo systemctl enable nginx --now

Importantly, this command ensures Nginx starts with each server reboot, which is essential for a functional LEMP stack.

The default Nginx version works well for most WordPress sites. For access to newer features like HTTP/3 or improved TLS performance, see the guide on installing Nginx Mainline on Debian.

Setting Up UFW Firewall for Nginx

Configuring the UFW firewall with your Nginx server is crucial for security and to permit external access to standard web ports. Fortunately, Nginx offers profiles that simplify the UFW setup process.

Installing UFW

First, if you haven’t installed UFW on your Debian system yet, execute the command below. For a comprehensive firewall setup, see the guide on installing UFW on Debian.

sudo apt install ufw
Configure Firewall Rules Before Enabling UFW

Before enabling UFW, configure all necessary rules. UFW blocks all incoming connections by default, so adding rules first prevents lockouts.

Critical: If you are connected via SSH, you must allow SSH before enabling UFW or you will be locked out of your server.

First, allow SSH access:

sudo ufw allow OpenSSH

Next, allow web traffic. Use “Nginx Full” to open both HTTP (port 80) and HTTPS (port 443):

sudo ufw allow 'Nginx Full'

Keep port 80 open even after obtaining SSL certificates. Let’s Encrypt uses HTTP for domain validation during certificate renewals. Blocking port 80 will cause renewal failures.

Now enable the firewall:

sudo ufw enable

Verify the firewall status and rules:

sudo ufw status

Expected output:

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
Nginx Full (v6)            ALLOW       Anywhere (v6)
Alternative UFW Profiles

To view all available Nginx profiles:

sudo ufw app list

The Nginx profiles are:

  • Nginx HTTP — port 80 only
  • Nginx Secure — port 443 only (avoid until certificates are issued)
  • Nginx Full — ports 80 and 443 (recommended)

Additionally, you can create other UFW rules to secure your server and LEMP setup with WordPress, and you should invest time in locking your server down if it is exposed to the public.

Install MariaDB (LEMP Part 2)

MariaDB provides enhanced performance compared to MySQL and serves as the database component in the LEMP stack. In particular, Debian 13 includes MariaDB 11.8, Debian 12 includes MariaDB 10.11, and Debian 11 includes MariaDB 10.5. For specific version requirements or advanced configuration, see the guide on installing MariaDB on Debian.

Accordingly, to install MariaDB, run:

sudo apt install mariadb-server mariadb-client

Likewise, after installation, check MariaDB’s status:

systemctl status mariadb

Expected output:

● mariadb.service - MariaDB 10.x.x database server
     Loaded: loaded (/lib/systemd/system/mariadb.service; enabled; preset: enabled)
     Active: active (running)

In such cases, if MariaDB isn’t running, start it with:

sudo systemctl enable mariadb --now

Similarly, this ensures MariaDB starts with each system reboot, which is vital for a stable LEMP stack and WordPress setup.

Secure MariaDB with Security Script

For data protection, you must secure your MariaDB installation. Notably, fresh MariaDB installations might have lax security defaults, exposing them to threats. However, the mysql_secure_installation script can bolster your database’s defenses.

Afterward, run the security script:

sudo mysql_secure_installation

In turn, this script guides you through several security configurations:

  • Setting the root password or opting for the unix_socket for unauthorized access prevention.
  • Deleting anonymous user accounts to limit database access to authorized users.
  • Restricting remote login for root user accounts.
  • Removing the test database to avoid unauthorized access and potential data leaks.

Importantly, answer each prompt attentively; these settings profoundly affect your database’s security. Answer Y to each prompt and press Enter:

Enter current password for root (enter for none): [Press Enter]
Switch to unix_socket authentication [Y/n] Y
Change the root password? [Y/n] Y
New password: [Enter a strong password]
Re-enter new password: [Confirm password]
Remove anonymous users? [Y/n] Y
Disallow root login remotely? [Y/n] Y
Remove test database and access to it? [Y/n] Y
Reload privilege tables now? [Y/n] Y

All done!  If you've completed all of the above steps, your MariaDB
installation should now be secure.

At this point, after completing the steps, your MariaDB setup is secure and ready for WordPress.

Install PHP and PHP-FPM (LEMP Part 3)

For a complete LEMP stack, you need to install PHP. Essentially, PHP acts as the bridge between Nginx and MariaDB, facilitated by PHP-FPM and other essential modules for WordPress. Debian 13 includes PHP 8.4, Debian 12 includes PHP 8.2, and Debian 11 includes PHP 7.4.

This guide uses Debian’s default PHP version. For newer PHP releases from third-party repositories, see the PHP installation guide for Debian.

Now, run the following command to install PHP, PHP-FPM, and the required modules:

sudo apt install php php-fpm php-mbstring php-bcmath php-xml php-mysql php-common php-gd php-cli php-curl php-zip php-imagick php-ldap php-intl

Then, after the installation, check your PHP version and the PHP-FPM service status:

php -v

Example output on Debian 13:

PHP 8.4.x (cli) (built: ...)
Copyright (c) The PHP Group
Zend Engine v4.4.x, Copyright (c) Zend Technologies
    with Zend OPcache v8.4.x, Copyright (c), by Zend Technologies

Subsequently, check the PHP-FPM service status using your installed version:

# Check status using your PHP version
systemctl status php8.4-fpm   # Debian 13
systemctl status php8.2-fpm   # Debian 12
systemctl status php7.4-fpm   # Debian 11

Expected output:

● php8.x-fpm.service - The PHP FastCGI Process Manager
     Loaded: loaded (/lib/systemd/system/php8.x-fpm.service; enabled; preset: enabled)
     Active: active (running)

Remember your PHP version number. You will need it when configuring the Nginx server block socket path later.

Pre-Installation Configuration For WordPress with LEMP

Create WordPress Directory Structure

With the LEMP stack installed, you can now download and set up WordPress. The official WordPress archive contains all core files needed to run the CMS. You can either visit the WordPress.org download page or use wget to download directly:

wget https://wordpress.org/latest.zip

Typically, the expected output shows the download progress:

--2024-12-05 10:00:00--  https://wordpress.org/latest.zip
Resolving wordpress.org... 198.143.164.252
Connecting to wordpress.org|198.143.164.252|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 26214400 (25M) [application/zip]
Saving to: 'latest.zip'

latest.zip          100%[===================>]  25.00M  10.5MB/s    in 2.4s

2024-12-05 10:00:03 (10.5 MB/s) - 'latest.zip' saved

Next, extract the archive to Nginx’s default web root directory. The unzip command creates a wordpress subdirectory automatically:

sudo unzip latest.zip -d /var/www/html/

After extraction, set the correct ownership so that Nginx (running as www-data) can read and write WordPress files. This step is critical because without proper ownership, WordPress cannot save settings, upload media, or install plugins:

sudo chown -R www-data:www-data /var/www/html/wordpress/

Now set appropriate permissions for directories and files. Directories need execute permission (755) so the web server can traverse them, while files need read/write for the owner only (644) to prevent unauthorized modifications:

Set directory permissions:

sudo find /var/www/html/wordpress -type d -exec chmod 755 {} \;

Set file permissions:

sudo find /var/www/html/wordpress -type f -exec chmod 644 {} \;

Verify the ownership and permissions are correct:

ls -la /var/www/html/wordpress/

Expected output shows www-data ownership:

total 236
drwxr-xr-x  5 www-data www-data  4096 Dec  5 10:00 .
drwxr-xr-x  3 root     root      4096 Dec  5 10:00 ..
-rw-r--r--  1 www-data www-data   405 Dec  5 10:00 index.php
-rw-r--r--  1 www-data www-data 19915 Dec  5 10:00 license.txt
-rw-r--r--  1 www-data www-data  7409 Dec  5 10:00 readme.html
-rw-r--r--  1 www-data www-data  7387 Dec  5 10:00 wp-activate.php
drwxr-xr-x  9 www-data www-data  4096 Dec  5 10:00 wp-admin
...

With proper permissions in place, WordPress can function securely while still allowing necessary file operations.

Create a Database for WordPress

To run WordPress on your Debian LEMP stack, you must create a database using MariaDB. For advanced database configuration including performance tuning and remote access, see the guide on installing MariaDB on Debian. Access the MariaDB shell as root using the following command:

sudo mariadb -u root

Once in the MariaDB shell, create a new database using the following command:

CREATE DATABASE WORDPRESSDB;

Next, create a new user account for WordPress with the following command:

CREATE USER 'WPUSER'@localhost IDENTIFIED BY 'PASSWORD';

Replace WPUSER and PASSWORD with your desired username and password.

Finally, assign the newly created user account access to the WordPress database only:

GRANT ALL PRIVILEGES ON WORDPRESSDB.* TO 'WPUSER'@'localhost';

The password was already set in the CREATE USER statement above, so IDENTIFIED BY is not needed here. Using quotes around the username and hostname ('WPUSER'@'localhost') follows SQL best practice.

After creating the user account, flush the privileges to ensure the new changes take effect:

FLUSH PRIVILEGES;

Before exiting, verify that the database was created successfully:

SHOW DATABASES;

Expected output includes your new database:

+--------------------+
| Database           |
+--------------------+
| WORDPRESSDB        |
| information_schema |
| mysql              |
| performance_schema |
+--------------------+

Also verify the user was created with correct privileges:

SHOW GRANTS FOR 'WPUSER'@'localhost';

Expected output shows ALL PRIVILEGES on your WordPress database:

+----------------------------------------------------------------------+
| Grants for WPUSER@localhost                                          |
+----------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `WPUSER`@`localhost` IDENTIFIED BY PASSWORD... |
| GRANT ALL PRIVILEGES ON `WORDPRESSDB`.* TO `WPUSER`@`localhost`      |
+----------------------------------------------------------------------+

Exit the MariaDB shell:

EXIT;

Set WordPress Configuration Files

Setting up the WordPress configuration files is an essential step in the installation process. Specifically, this involves renaming the sample wp-config.php file and entering the necessary configuration details.

First, navigate to the WordPress directory using the following command:

cd /var/www/html/wordpress/

Then, copy the wp-config-sample.php to wp-config.php using the following command:

sudo cp wp-config-sample.php wp-config.php

Afterwards, using a text editor, bring up the newly copied wp-config.php file:

sudo nano wp-config.php

Next, enter the database name, user account with a password, and host IP address if necessary.

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */ 

define( 'DB_NAME', 'WORDPRESSDB' );                 <--------------- change this

/* MySQL database username */ 

define( 'DB_USER', 'WPUSER' );                      <--------------- change this

/* MySQL database password */

define( 'DB_PASSWORD', 'PASSWORD' );                <--------------- change this

/* MySQL hostname, change the IP here if external DB set up */ 

define( 'DB_HOST', 'localhost' );

/* Database Charset to use in creating database tables. */

define( 'DB_CHARSET', 'utf8' );

/* The Database Collate type. Don't change this if in doubt. */

define( 'DB_COLLATE', '' );

Furthermore, in addition to these settings, you can also add the following to the wp-config.php file to improve WordPress management:

/** ## Save files direct method ## */
define( 'FS_METHOD', 'direct' );

/** ## Increase memory limit, 256MB is recommended ## */
define('WP_MEMORY_LIMIT', '256M');

Your dedicated server’s or VPS’s memory limit can vary depending on your system’s capacity. You can increase or decrease the 256 MB memory limit in small increments, such as 128 MB, 256 MB, 512 MB, etc.

Only make small adjustments to the memory limit for optimal performance and stability. Monitor your server’s RAM usage after changes.

After completing the database settings, your wp-config.php should look like this:

// ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'WORDPRESSDB' );

/** Database username */
define( 'DB_USER', 'WPUSER' );

/** Database password */
define( 'DB_PASSWORD', 'PASSWORD' );

/** Database hostname */
define( 'DB_HOST', 'localhost' );

/** Database charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8' );

/** The database collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', '' );

/** ## Save files direct method ## */
define( 'FS_METHOD', 'direct' );

/** ## Increase memory limit, 256MB is recommended ## */
define('WP_MEMORY_LIMIT', '256M');

Implementing WordPress Security Salt Keys

Enhancing your WordPress installation’s security is paramount, and one effective way to achieve this is by setting up WordPress security salt keys. In essence, these keys act as an augmented security shield, fortifying your WordPress site against potential threats and bolstering user authentication and data encryption.

Generating Security Salt Keys

Following this, to produce your security salt keys, navigate to the WordPress secret-key API: https://api.wordpress.org/secret-key/1.1/salt/. Once you’ve generated these keys, it’s vital to replace the placeholder lines in your wp-config.php file with your unique keys. This step fortifies user authentication and data encryption.

Importantly, do not use the example lines provided here or elsewhere, as they are for illustrative purposes. Using pre-set salt keys can expose your site to vulnerabilities. Therefore, always generate distinct keys for every WordPress setup to ensure optimal security.

Integrating Security Salt Keys

To embed the freshly generated security salt keys into your wp-config.php file, open the file in a text editor:

sudo nano /var/www/html/wordpress/wp-config.php

Next, find the section containing the placeholder keys and replace them with your generated values. The salt keys section should look similar to this after replacement:

/**#@+
 * Authentication unique keys and salts.
 *
 * Change these to different unique phrases! You can generate these using
 * the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}.
 *
 * You can change these at any point in time to invalidate all existing cookies.
 * This will force all users to have to log in again.
 *
 * @since 2.6.0
 */
define('AUTH_KEY',         'VI@9bl!&zoFQ:wd|;R3{o+YodV{bQN`V4r0+9ULs9hj?A;hUMWQi1N,(');
define('SECURE_AUTH_KEY',  '[m0xr1B?Z%7i9,r]j&pAf5z>sLQT mRo_F]iu J#kkgx?v45xtreEv,<7)T|JUTO');
define('LOGGED_IN_KEY',    '~eX4qk?c]ZN40,ST2B--0YI|81.@UyCw^7x||nQw+KXz|7o+m>mgL9T4#CE{khOqJ3gw,');

The keys shown above are examples only. Generate your own unique keys from the WordPress secret-key API and never copy these directly.

Finally, after replacing the keys, save and close the file. In nano, press CTRL+X, then Y, then Enter.

Nginx Server Block Configuration for WordPress LEMP Setup

Setting up the Nginx server block correctly is vital for a seamless WordPress installation via the web UI. Most importantly, it’s essential to get the “try_files $uri $uri/ /index.php?$args;” directive right. Leaving out the “?$args” can interfere with WordPress’s REST API. Therefore, to avoid potential hiccups during the installation, follow these instructions closely.

First, create a new server configuration file for your WordPress installation on Nginx. Replace “example.com” with your actual domain name in the following command:

sudo nano /etc/nginx/sites-available/example.com.conf

Additionally, for Nginx to work with PHP, you must include the “location ~ .php$” in the server block configuration file. Below is a sample configuration you can use as a reference.

Ensure you adjust the root path and domain names to fit your setup:

server {
  listen 80;
  listen [::]:80;
  server_name www.example.com example.com;
  root /var/www/html/wordpress;
  index index.php index.html index.htm index.nginx-debian.html;

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

  location ~* /wp-sitemap.*\.xml {
    try_files $uri $uri/ /index.php$is_args$args;
  }

  client_max_body_size 100M;

  location ~ \.php$ {
    fastcgi_pass unix:/run/php/php8.4-fpm.sock;
    include snippets/fastcgi-php.conf;
    fastcgi_buffer_size 128k;
    fastcgi_buffers 4 128k;
    fastcgi_intercept_errors on;
  }

  gzip on;
  gzip_comp_level 6;
  gzip_min_length 1000;
  gzip_proxied any;
  gzip_disable "msie6";
  gzip_types application/atom+xml application/geo+json application/javascript application/x-javascript application/json application/ld+json application/manifest+json application/rdf+xml application/rss+xml application/xhtml+xml application/xml font/eot font/otf font/ttf image/svg+xml text/css text/javascript text/plain text/xml;

  location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv)$ {
    expires 90d;
    access_log off;
  }

  location ~* \.(?:svgz?|ttf|ttc|otf|eot|woff2?)$ {
    add_header Access-Control-Allow-Origin "*";
    expires 90d;
    access_log off;
  }

  location ~ /\.ht {
    access_log off;
    log_not_found off;
    deny all;
  }
}

Important: Adjust the PHP-FPM socket path to match your PHP version. Use php8.4-fpm.sock for Debian 13, php8.2-fpm.sock for Debian 12, or php7.4-fpm.sock for Debian 11.

For example, if running Debian 12 with PHP 8.2, change fastcgi_pass unix:/run/php/php8.4-fpm.sock; to fastcgi_pass unix:/run/php/php8.2-fpm.sock;. Run php -v to confirm your version.

Understanding the WordPress Nginx Server Block

For those new to setting up Nginx and WordPress, here’s a breakdown of the server block example:

Basic Server Settings:

  • Server block fundamentals define the IP address, port for Nginx to listen on, and server names.
  • Meanwhile, the root directive points to the primary directory containing the website files.
  • The index directive instructs Nginx on identifying index files when serving the site.

Location Settings:

  • Location blocks dictate how Nginx processes requests for different URLs.
  • Specifically, the initial location block manages requests to the site’s root URL, utilizing the try_files directive.
  • The subsequent location block processes requests specifically for the WordPress sitemap.xml file.

PHP Handling Settings:

  • PHP handling directives determine how Nginx processes PHP files.
  • In particular, the fastcgi_pass directive points to the PHP-FPM socket file’s location.
  • The fastcgi_param directive assigns the SCRIPT_FILENAME parameter’s value to the requested PHP file’s location.
  • The include directives pull in additional configuration files for the FastCGI module.
  • Directives like fastcgi_buffer_size and fastcgi_buffers designate the buffer size for data transfer between Nginx and PHP-FPM.
  • The fastcgi_intercept_errors directive empowers Nginx to capture and manage PHP errors.

Gzip Compression Settings:

  • Compression directives configure Gzip, reducing the file size delivered to the client.
  • First, the gzip directive activates Gzip compression.
  • Directives like gzip_comp_level and gzip_min_length determine the compression level and the minimum file size for compression, respectively.
  • The gzip_proxied directive identifies which request types undergo compression.
  • The gzip_types directive enumerates the MIME types eligible for compression.

File Caching Settings:

  • Caching directives optimize static file delivery, enhancing website speed.
  • For example, the initial location block establishes the expiration duration for asset and media files.
  • The subsequent location block sets the expiration for font and SVG files.
  • Directives such as access_log and log_not_found govern the request logging.
  • The add_header directive appends the Access-Control-Allow-Origin header, permitting font and SVG loading from external domains.

.htaccess File Blocking:

  • This setting restricts access to files starting with .ht, typically sensitive server configuration files.

Setting Up the Nginx Server Block with a Symbolic Link

Specifically, to wrap up the Nginx server block configuration, you must activate the configuration file from the “sites-available” directory. This is achieved by creating a symbolic link to the “sites-enabled” directory.

Next, execute the command below, ensuring you replace “example.com.conf” with your configuration file’s name:

sudo ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/

This command establishes a symbolic link between the directories, granting Nginx access to the configuration file. Subsequently, after setting this up, validate the configuration with:

sudo nginx -t

Expected output:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Accordingly, if the test returns no errors, restart Nginx to apply the server block changes:

sudo systemctl restart nginx

Then, verify Nginx restarted successfully:

systemctl status nginx --no-pager
● nginx.service - A high performance web server and a reverse proxy server
     Active: active (running)

With these steps completed, your WordPress site should now be accessible via Nginx.

Configuring PHP.ini for Optimal WordPress Performance

Tuning your PHP settings is crucial for achieving the best performance with WordPress. To handle media files efficiently in WordPress, consider increasing the maximum upload size, post size, and memory limit. Additionally, you might also need to tweak the maximum execution time and input variables to prevent potential issues.

To access your php.ini file, use the terminal. The file path includes your PHP version number:

# Debian 13 (PHP 8.4)
sudo nano /etc/php/8.4/fpm/php.ini

# Debian 12 (PHP 8.2)
sudo nano /etc/php/8.2/fpm/php.ini

# Debian 11 (PHP 7.4)
sudo nano /etc/php/7.4/fpm/php.ini

Now, to tailor the PHP settings, find and adjust the following lines in your php.ini file:

; Increase to the maximum file size you want to upload (recommended 50-100MB)
upload_max_filesize = 100M

; Increase to the maximum post size (recommended 50-100MB)
post_max_size = 100M

; Increase maximum execution time (recommended 150-300 seconds)
max_execution_time = 300

; Increase maximum GET/POST/COOKIE input variables (recommended 5000-10000)
max_input_vars = 5000

; Increase memory limit (recommended 256M or 512M, ensure your system has enough RAM)
memory_limit = 256M

After modifying your PHP settings, it’s vital to restart the PHP-FPM server. Consequently, this ensures the new configurations are active, allowing your WordPress site to operate at its best.

Increase Nginx Server Client Max Body Size

To accommodate larger file uploads on your WordPress site, you’ll need to tweak the Nginx server block. Essentially, this ensures that Nginx can handle larger HTTP request bodies, which is essential when dealing with sizable file uploads.

Modifying the Nginx Server Block

To verify this, open your server block configuration file and confirm the following line matches your php.ini upload limit:

client_max_body_size 100M;

The sample server block configuration earlier already includes client_max_body_size 100M;. Adjust this value if you changed upload_max_filesize in php.ini.

After modifying the Nginx configuration, test and reload it:

sudo nginx -t && sudo systemctl reload nginx

Restarting PHP-FPM

After adjusting the PHP settings in php.ini, restart the PHP-FPM server for those changes to take effect. Use the command matching your PHP version:

# Debian 13 (PHP 8.4)
sudo systemctl restart php8.4-fpm

# Debian 12 (PHP 8.2)
sudo systemctl restart php8.2-fpm

# Debian 11 (PHP 7.4)
sudo systemctl restart php7.4-fpm

Install WordPress Front-end

After finalizing the backend setup and configuration, it’s now time to launch the WordPress front end on your domain. First, start the installation by heading to your domain, prefixed by “https://” or “http://.” Alternatively, you can directly access “https://www.yoursite.com/wp-admin/install.php.”

Accordingly, this URL directs you to the front-end installation wizard.

Step 1: Select WordPress Language

First, select your desired language and click “Continue.”

Step 2: Create Admin User For WordPress

Next, you’ll land on a page prompting you to input your site title, username, password, and the main admin’s email address for the WordPress site.

Importantly, for security reasons, choose a robust password and provide a valid email address. Keep in mind that you can modify other settings later within the WordPress settings panel.

For those developing their site and wishing to keep it private from search engines like Google or Bing, there’s an option to “strongly discourage search engines from indexing.”

Step 3: Proceed and Click Install WordPress Button

Once you’ve filled out your details and preferences, hit the “Install WordPress” button. A successful installation will redirect you to the login page.

Step 4: Proceed to log in on the WordPress Admin Page

Next, input your login details and press “Log in.” This action will usher you into the WordPress dashboard, where you can craft or import your website.

Step 5: View and Adjust the WordPress site via WordPress Admin

Essentially, the WordPress dashboard serves as your command center. From here, you can draft new posts, design pages, handle themes and plugins, and tailor your site’s look, content, and operations.

Moreover, with its user-friendly interface, the dashboard empowers you to swiftly establish your website, enabling you to design a captivating and professional site with minimal effort.

Additional Tips For WordPress with Nginx

Now that your WordPress installation is running, consider these security and performance enhancements. For server-level protection against brute force attacks, see the guide on installing Fail2ban on Debian.

Securing WordPress and Nginx with Let’s Encrypt SSL Certificate

Securing your WordPress site with HTTPS protects user data and improves search rankings. Conveniently, Let’s Encrypt provides free SSL certificates that auto-renew. For a more detailed walkthrough, see the guide on securing Nginx with Let’s Encrypt on Debian.

Installing Certbot

First, start by installing the certbot package with the command:

sudo apt install python3-certbot-nginx

Generating the SSL Certificate

Once you’ve installed the certbot package, generate your SSL certificate with the following:

sudo certbot --nginx --agree-tos --redirect --hsts --staple-ocsp --email you@example.com -d example.com -d www.example.com

Include both the apex domain (example.com) and www subdomain to ensure both URLs receive certificates. The http-01 challenge requires port 80 to remain open until certificate issuance completes.

Additionally, during this process, Certbot will prompt you to confirm your email. You’ll also have the option to receive emails from the EFF. Simply decide whether to opt-in based on your preferences.

Subsequently, after installing the certificate, your website’s URL will switch from HTTP to HTTPS. Visitors accessing the old HTTP URL will automatically be redirected to the new HTTPS URL. Furthermore, this configuration ensures HTTPS 301 redirects, a Strict-Transport-Security header, and OCSP Stapling for top-tier security.

Setting Up Automatic Certificate Renewal

Let’s Encrypt certificates expire after 90 days. Fortunately, Debian’s certbot package installs a systemd timer that handles automatic renewal. To confirm, verify the timer is active:

sudo systemctl status certbot.timer

Expected output:

● certbot.timer - Run certbot twice daily
     Loaded: loaded (/lib/systemd/system/certbot.timer; enabled; preset: enabled)
     Active: active (waiting)

Next, test that renewal works correctly:

sudo certbot renew --dry-run

Expected output on success:

Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)

Once the dry run succeeds, automatic renewal is configured. Consequently, the timer runs twice daily and renews certificates within 30 days of expiration.

Resolving PHP Session Errors

Sometimes, encountering session-saving issues, especially when using certain plugins? The root of the problem might be incorrect user permissions in the /var/lib/php/sessions/ directory. Fortunately, you can address this with a straightforward command.

Adjusting Directory Permissions

To resolve this, run the command below to set the correct permissions:

sudo chown -R www-data:www-data /var/lib/php/sessions/

This command sets the www-data user and group as the owners of the sessions directory. As a result, WordPress can write session data without any hitches. Particularly, this adjustment is vital for the seamless operation of your WordPress site, mainly if you’re using plugins that handle automated tasks, such as posting to social media.

Overall, addressing PHP session errors is key to boosting your website’s performance and improving the user experience.

Resolving HTTPS Redirect Loop in WordPress

If your WordPress site finds itself trapped in a redirect loop after activating HTTPS, it’s likely because WordPress keeps trying to redirect to the secure HTTPS version, but the loop never completes. To tackle this, you can modify your wp-config.php file with specific lines of code.

Modifying the wp-config.php File

Insert the following lines into your wp-config.php:

define('FORCE_SSL_ADMIN', true);

if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false) {
    $_SERVER['HTTPS'] = 'on';
}

Here’s a breakdown of what each line does:

  • The first line sets the FORCE_SSL_ADMIN constant to true, ensuring all admin pages use HTTPS.
  • The subsequent code checks if the HTTP_X_FORWARDED_PROTO header contains the term “https.” If it finds a match, it designates the HTTPS server variable as “on.” This action informs WordPress that the connection is secure.

Integrating these lines into your wp-config.php file, you should be able to break free from the HTTPS redirect loop and ensure your WordPress site operates smoothly with its new secure connection.

Fix Domain Name Redirect Loop

A redirect loop in your WordPress site can sometimes stem from a mismatch between the domain name specified in your wp-config.php file and your website’s domain name. To address this, you’ll need to verify and possibly adjust the domain name in the configuration.

Checking the wp-config.php File

First, inspect the following lines in your wp-config.php:

define('WP_HOME','https://example.com');
define('WP_SITEURL','https://example.com');

Subsequently, if the domain name doesn’t align with your website’s domain, correct it accordingly.

Troubleshooting Common Issues

502 Bad Gateway Error

Occasionally, when accessing your WordPress site, the browser displays:

502 Bad Gateway
nginx/1.26.x

Essentially, this error means Nginx cannot connect to PHP-FPM. This usually happens because the socket path is wrong or PHP-FPM isn’t running.

Step 1: Check which PHP-FPM sockets exist:

ls /run/php/

Expected output (example for Debian 13):

php8.4-fpm.pid  php8.4-fpm.sock  php-fpm.sock

Step 2: Verify your Nginx config uses the correct socket. Open your server block and check the fastcgi_pass line matches the socket file shown above:

grep fastcgi_pass /etc/nginx/sites-available/example.com.conf

Step 3: Verify PHP-FPM is running:

# Debian 13
systemctl status php8.4-fpm --no-pager

# Debian 12
systemctl status php8.2-fpm --no-pager

# Debian 11
systemctl status php7.4-fpm --no-pager

Expected output includes Active: active (running). If stopped, start it:

sudo systemctl start php8.4-fpm   # Adjust version as needed

Step 4: After fixing, test and reload Nginx:

sudo nginx -t && sudo systemctl reload nginx

Database Connection Error

Occasionally, WordPress displays this error on the front end:

Error establishing a database connection

Typically, this means WordPress cannot connect to MariaDB. This usually results from wrong credentials in wp-config.php or MariaDB not running.

Step 1: Verify MariaDB is running:

systemctl status mariadb --no-pager

Step 2: Test the database credentials from wp-config.php:

sudo mariadb -u WPUSER -p -e "SHOW DATABASES;"

When prompted, enter the password from your wp-config.php. Successful output shows:

+--------------------+
| Database           |
+--------------------+
| WORDPRESSDB        |
| information_schema |
+--------------------+

However, if authentication fails with Access denied, recreate the user with the correct password:

sudo mariadb -u root -e "DROP USER 'WPUSER'@'localhost'; CREATE USER 'WPUSER'@'localhost' IDENTIFIED BY 'your_new_password'; GRANT ALL PRIVILEGES ON WORDPRESSDB.* TO 'WPUSER'@'localhost'; FLUSH PRIVILEGES;"

Afterwards, update wp-config.php to match the new password.

Permission Denied Errors

Sometimes WordPress shows errors when installing plugins or uploading media:

Installation failed: Could not create directory.

Unable to create directory wp-content/uploads/2024/12. Is its parent directory writable by the server?

Step 1: Check current ownership:

ls -la /var/www/html/wordpress/

Ideally, files should show www-data www-data ownership. If they show root root or another user, fix with:

sudo chown -R www-data:www-data /var/www/html/wordpress/
sudo find /var/www/html/wordpress -type d -exec chmod 755 {} \;
sudo find /var/www/html/wordpress -type f -exec chmod 644 {} \;

Step 2: Verify the fix:

ls -la /var/www/html/wordpress/wp-content/

Expected output:

drwxr-xr-x  6 www-data www-data 4096 Dec  5 10:00 .
drwxr-xr-x  5 www-data www-data 4096 Dec  5 10:00 ..
drwxr-xr-x  4 www-data www-data 4096 Dec  5 10:00 plugins
drwxr-xr-x  5 www-data www-data 4096 Dec  5 10:00 themes
drwxr-xr-x  3 www-data www-data 4096 Dec  5 10:00 uploads

White Screen of Death (WSOD)

Sometimes WordPress displays a completely blank white page with no error message. Typically, this indicates a PHP fatal error that isn’t being displayed.

Step 1: Enable WordPress debug mode to reveal the error. Open wp-config.php:

sudo nano /var/www/html/wordpress/wp-config.php

Find the line define( 'WP_DEBUG', false ); and change it to:

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', true );

Step 2: Reload the page. The error message should now appear. Common causes include:

  • Memory exhaustion — Increase WP_MEMORY_LIMIT in wp-config.php or memory_limit in php.ini
  • Plugin conflict — Rename wp-content/plugins folder to disable all plugins
  • Theme error — Rename wp-content/themes/your-theme to fall back to default

Step 3: Check the PHP-FPM error log for details:

# Debian 13
sudo tail -50 /var/log/php8.4-fpm.log

# Debian 12
sudo tail -50 /var/log/php8.2-fpm.log

# Debian 11
sudo tail -50 /var/log/php7.4-fpm.log

Step 4: After fixing the issue, disable debug mode by reverting the changes:

define( 'WP_DEBUG', false );

Never leave debug mode enabled on production sites. Debug output can expose sensitive information like file paths and database queries to visitors.

Nginx Configuration Test Fails

Occasionally, when running nginx -t, you see syntax errors:

nginx: [emerg] unknown directive "fastcgi_passs" in /etc/nginx/sites-enabled/example.com.conf:18
nginx: configuration file /etc/nginx/nginx.conf test failed

Step 1: Open the configuration file at the line number indicated:

sudo nano +18 /etc/nginx/sites-available/example.com.conf

Step 2: Fix the typo or syntax error. Common mistakes include:

  • Missing semicolons at the end of directives
  • Typos in directive names (like fastcgi_passs instead of fastcgi_pass)
  • Unmatched braces { }
  • Wrong PHP-FPM socket path

Step 3: Test again and reload:

sudo nginx -t && sudo systemctl reload nginx

Expected successful output:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Remove WordPress and LEMP Stack

Should you need to remove WordPress and the LEMP stack from your Debian system, follow these steps. Essentially, this process removes the packages, configuration files, and databases created during installation.

Remove WordPress Files

Warning: The following command permanently deletes all WordPress files including uploads, themes, and plugins. Back up any content you want to keep first: sudo cp -r /var/www/html/wordpress ~/wordpress-backup

sudo rm -rf /var/www/html/wordpress/

Remove the WordPress Database and User

Next, connect to MariaDB and remove the database and user:

sudo mariadb -u root

Then, run the following SQL commands:

DROP DATABASE WORDPRESSDB;
DROP USER 'WPUSER'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Remove the Nginx Server Block

Afterwards, remove the site configuration:

sudo rm /etc/nginx/sites-available/example.com.conf
sudo rm /etc/nginx/sites-enabled/example.com.conf
sudo nginx -t && sudo systemctl reload nginx

Remove LEMP Stack Packages (Optional)

Finally, to remove Nginx, MariaDB, and PHP entirely:

sudo apt remove nginx mariadb-server mariadb-client php php-fpm php-mbstring php-bcmath php-xml php-mysql php-common php-gd php-cli php-curl php-zip php-imagick php-ldap php-intl
sudo apt autoremove

Additionally, to also remove configuration files (complete uninstall):

sudo apt purge nginx mariadb-server mariadb-client php php-fpm php-mbstring php-bcmath php-xml php-mysql php-common php-gd php-cli php-curl php-zip php-imagick php-ldap php-intl

Warning: The following commands permanently delete all Nginx site configurations in /etc/nginx, MariaDB configuration in /etc/mysql, and all MariaDB databases in /var/lib/mysql. Back up any data you need before proceeding.

sudo rm -rf /etc/nginx /etc/mysql /var/lib/mysql

Verify Removal

After removal, refresh the package cache and confirm the packages are no longer installed:

sudo apt update
apt-cache policy nginx mariadb-server php

Expected output shows no installed version:

nginx:
  Installed: (none)
  Candidate: 1.26.x-x
mariadb-server:
  Installed: (none)
  Candidate: 1:11.x.x-x
php:
  Installed: (none)
  Candidate: 2:8.x+xxx

Conclusion

You now have a working WordPress installation running on Nginx with MariaDB and PHP-FPM. The configuration includes gzip compression, static file caching, and SSL encryption through Let’s Encrypt. Moving forward, for production sites, consider implementing Redis object caching for improved database performance and setting up automated backups of both your WordPress files and MariaDB database. Finally, keep your system updated with sudo apt update && sudo apt upgrade to receive security patches for all LEMP stack components.

2 thoughts on “How to Install WordPress with Nginx on Debian”

  1. On Debian 12, nginx (stable from nginx repo) was configured to use `/etc/nginx/conf.d/*.conf` rather than `/etc/nginx/sites-enabled/*.conf` to search for sites, in spite of this directory being available.

    It might be good to update the guide to suggest verifying and possibly adding an `include /etc/nginx/sites-enabled/*.conf` instruction into the `http` settings of `/etc/nginx/nginx.conf`.

    Otherwise, great guide; thanks!

    Reply
    • Thanks for catching this, Dylan. You’re right that Nginx packages from nginx.org use /etc/nginx/conf.d/ by default, while Debian’s packaged Nginx includes the sites-enabled directory. Since this guide uses Debian’s default Nginx packages, the sites-enabled pattern works out of the box.

      For readers using the nginx.org repository (mainline or stable builds), add the include directive to /etc/nginx/nginx.conf inside the http block:

      include /etc/nginx/sites-enabled/*.conf;

      Alternatively, place your server block directly in /etc/nginx/conf.d/example.com.conf instead of using the sites-available/sites-enabled pattern. Both approaches work. Thanks for the suggestion to clarify this for users on non-default Nginx builds.

      Reply

Leave a Comment