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
WPUSERandPASSWORDwith 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 USERstatement above, soIDENTIFIED BYis 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.sockfor Debian 13,php8.2-fpm.sockfor Debian 12, orphp7.4-fpm.sockfor 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 changedupload_max_filesizein 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) andwwwsubdomain 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_LIMITin wp-config.php ormemory_limitin php.ini - Plugin conflict — Rename
wp-content/pluginsfolder to disable all plugins - Theme error — Rename
wp-content/themes/your-themeto 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_passsinstead offastcgi_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.
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!
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 thesites-enableddirectory. Since this guide uses Debian’s default Nginx packages, thesites-enabledpattern works out of the box.For readers using the nginx.org repository (mainline or stable builds), add the include directive to
/etc/nginx/nginx.confinside thehttpblock:Alternatively, place your server block directly in
/etc/nginx/conf.d/example.com.confinstead 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.