Complete Guide to WordPress Deployment on Debian with Nginx: From Installation to Optimization

Last modified on 17 September 2025 in VPS hosting
  • 36 min read
  • 24
  • 0
Spartak Itskovich

Spartak Itskovich

Game Content Writer

WordPress is the world's most popular content management system¹, used by more than 43% of all websites. Debian, as one of the most stable Linux distributions², provides a reliable foundation for hosting WordPress. This comprehensive guide covers the entire process of deploying WordPress on Debian using the LEMP stack³ (Linux, Nginx, MariaDB/MySQL, PHP), from initial system setup to advanced optimization and security methods.

¹ CMS (Content Management System) - a program that allows creating and editing websites without programming knowledge
² Linux distribution - an operating system based on the Linux kernel with a set of programs and utilities
³ LEMP stack - a set of software for web development: Linux (operating system), Nginx (web server), MySQL/MariaDB (database), PHP (programming language)

 

Linux root terminal session showing directory navigation, ping test, and package search commands relevant to server management.

System Preparation and Prerequisites

System Requirements

Before starting WordPress installation on Debian, you need to ensure your system meets the minimum requirements. For stable WordPress operation, it's recommended to have at least 1 GB of RAM, 20 GB of disk space, and a stable internet connection.

To begin, update your system to the latest version through the terminal⁴:

Terminal (command line) - a text interface for interacting with the operating system through commands

sudo apt update && sudo apt upgrade -y
# sudo - execute command with administrator privileges
# apt update - update the list of available packages
# && - execute next command only if previous one was successful
# apt upgrade -y - install all available updates without prompts

This command first updates the list of available packages⁵ (apt update), then installs updates (apt upgrade).

Package - a pre-compiled program ready for installation

Check the Debian version using the command:

cat /etc/debian_version
# cat - display file contents on screen
# /etc/debian_version - file containing Debian version

Network and Firewall Configuration

Security configuration begins with proper network connections and firewall⁶ setup. Install UFW (Uncomplicated Firewall) for basic protection:[4]

Firewall - a program that controls network traffic and blocks dangerous connections

sudo apt install ufw -y
# sudo apt install - install package with administrator privileges
# ufw - firewall package name
# -y - automatically answer "yes" to all questions

sudo ufw allow ssh
# sudo ufw allow - allow connections through firewall
# ssh - allow SSH connections (port 22)

sudo ufw allow 'Nginx Full'
# 'Nginx Full' - allow HTTP (port 80) and HTTPS (port 443) for Nginx

sudo ufw enable
# sudo ufw enable - activate firewall

These commands install the firewall, allow SSH⁷ connections and HTTP/HTTPS traffic for Nginx, then activate protection.

SSH (Secure Shell) - protocol for secure remote server access

LEMP Stack Installation

Installing Nginx Web Server

Nginx⁸ is a high-performance web server that provides better performance and lower resource consumption.

Diagram showing the components of the LEMP stack with Nginx as the web server, PHP-FPM as the scripting layer, and MariaDB/MySQL as the database layer, all running on the Linux operating system.

Installing Nginx on Debian:

Nginx - web server that processes HTTP requests from browsers and serves web pages

sudo apt install nginx -y
# sudo apt install - install package with administrator privileges
# nginx - Nginx web server package name
# -y - automatically confirm installation

After installation, start and enable Nginx for automatic startup at system boot:

sudo systemctl start nginx
# sudo systemctl start - start service
# nginx - service name to start

sudo systemctl enable nginx
# sudo systemctl enable - enable service autostart at boot
# nginx - service name to enable

Check service status⁹:

Service - a program that runs in the background and performs certain functions

sudo systemctl status nginx
# sudo systemctl status - check service status
# nginx - service name to check

You can test Nginx by visiting http://localhost or your server's IP address¹⁰ in a web browser.[1][3]

¹⁰ IP address - unique numerical identifier of a computer on the network

Installing MySQL/MariaDB

MariaDB¹¹ is a modern replacement for MySQL that provides better performance and additional features. For WordPress, it's recommended to use MariaDB:[5][6]

¹¹ MariaDB/MySQL - database management system that stores website information (articles, users, settings)

sudo apt install mariadb-server mariadb-client -y
# sudo apt install - install packages with administrator privileges
# mariadb-server - MariaDB server component
# mariadb-client - client utilities for connecting to MariaDB
# -y - automatically confirm installation

Start the database service:

sudo systemctl start mariadb
# sudo systemctl start - start service
# mariadb - MariaDB service name

sudo systemctl enable mariadb
# sudo systemctl enable - enable autostart at boot
# mariadb - service name to enable

Run MariaDB security setup:

sudo mysql_secure_installation
# sudo - execute with administrator privileges
# mysql_secure_installation - script for MariaDB security configuration

During setup, set a strong password for the root user¹² and answer "Y" to all security questions.[6][5]

¹² Root user - system administrator with full access privileges

Installing PHP and PHP-FPM

PHP-FPM¹³ (FastCGI Process Manager) is the optimal choice for working with Nginx, providing better performance and security. Install PHP along with necessary modules¹⁴:[2][3][7]

¹³ PHP-FPM - process manager for PHP that efficiently handles requests from web server
¹⁴ PHP modules - additional components that extend PHP capabilities for working with databases, images, etc.

sudo apt install php php-fpm php-mysql php-cli php-common php-zip php-curl php-mbstring php-xml php-gd php-intl php-bcmath -y
# sudo apt install - install packages with administrator privileges
# php - main PHP package
# php-fpm - FastCGI Process Manager for PHP
# php-mysql - module for working with MySQL/MariaDB databases
# php-cli - command line interface for PHP
# php-common - common files for PHP
# php-zip - module for working with ZIP archives
# php-curl - module for HTTP requests
# php-mbstring - module for working with multibyte strings
# php-xml - module for working with XML
# php-gd - module for working with images
# php-intl - internationalization module
# php-bcmath - module for precise mathematical calculations
# -y - automatically confirm installation

Check PHP version:

php -v
# php - run PHP
# -v - show PHP version

Start and enable PHP-FPM:

sudo systemctl start php8.2-fpm  # replace with your PHP version
# sudo systemctl start - start service
# php8.2-fpm - PHP-FPM service name (version may differ)

sudo systemctl enable php8.2-fpm
# sudo systemctl enable - enable autostart at boot
# php8.2-fpm - service name to enable

Database Configuration for WordPress

Creating Database

Log into MySQL/MariaDB as root user:

sudo mysql -u root -p
# sudo - execute with administrator privileges
# mysql - client for connecting to MariaDB
# -u root - connect as root user
# -p - prompt for password when connecting

Create a separate database¹⁵ for WordPress:[5][6]

¹⁵ Database - structured data set that stores all website information

CREATE DATABASE wordpress_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CREATE DATABASE - command to create new database
-- wordpress_db - database name for WordPress
-- CHARACTER SET utf8mb4 - set UTF-8 encoding with emoji support
-- COLLATE utf8mb4_unicode_ci - set sorting rules for Unicode

This command creates a database named wordpress_db and configures UTF-8¹⁶ encoding to support all world languages.

¹⁶ UTF-8 - text encoding standard that supports symbols of all languages

Create a separate user for WordPress and grant appropriate privileges:

CREATE USER 'wp_user'@'localhost' IDENTIFIED BY 'secure_password';
-- CREATE USER - command to create new user
-- 'wp_user' - username for WordPress
-- @'localhost' - user can connect only from local server
-- IDENTIFIED BY 'secure_password' - set password for user

GRANT ALL PRIVILEGES ON wordpress_db.* TO 'wp_user'@'localhost';
-- GRANT ALL PRIVILEGES - grant all access rights
-- ON wordpress_db.* - to all tables in wordpress_db database
-- TO 'wp_user'@'localhost' - to wp_user from localhost

FLUSH PRIVILEGES;
-- FLUSH PRIVILEGES - apply privilege changes

EXIT;
-- EXIT - exit MariaDB client

Replace secure_password with a strong password consisting of uppercase and lowercase letters, numbers, and special characters.[6][5]

Downloading and Installing WordPress

Downloading WordPress

¹⁷ Temporary directory (/tmp) - folder for storing files that will be deleted after reboot

cd /tmp
# cd - change current directory
# /tmp - go to temporary directory

wget https://wordpress.org/latest.tar.gz
# wget - utility for downloading files from Internet
# https://wordpress.org/latest.tar.gz - URL of latest WordPress version

The wget command downloads files from the Internet, and cd changes the current directory.

Extract¹⁸ the downloaded file:

¹⁸ Archive - compressed file containing multiple files and folders

tar xzvf latest.tar.gz
# tar - utility for working with archives
# x - extract files
# z - handle gzip compression
# v - show extraction process (verbose)
# f - specify archive file
# latest.tar.gz - archive file name to extract

Copy WordPress files to the web server's root directory¹⁹:

¹⁹ Web server root directory - folder where website files are stored (/var/www/html)

sudo cp -R wordpress/* /var/www/html/
# sudo - execute with administrator privileges
# cp - file copy command
# -R - copy recursively (including subfolders)
# wordpress/* - all files from wordpress folder
# /var/www/html/ - destination directory (web server root)

Set correct file permissions²⁰:[1][4]

²⁰ File permissions - access rights that determine who can read, write, or execute files

sudo chown -R www-data:www-data /var/www/html/
# sudo chown - change file ownership
# -R - recursively for all files and folders
# www-data:www-data - set owner and group to www-data
# /var/www/html/ - directory to change ownership

sudo find /var/www/html/ -type d -exec chmod 755 {} \;
# sudo find - find files and folders with administrator privileges
# /var/www/html/ - directory to search
# -type d - search only directories (folders)
# -exec chmod 755 {} \; - execute chmod 755 for each found folder
# 755 - permissions: owner can do everything, others can only read and execute

sudo find /var/www/html/ -type f -exec chmod 644 {} \;
# sudo find - find files with administrator privileges
# /var/www/html/ - directory to search
# -type f - search only files (not folders)
# -exec chmod 644 {} \; - execute chmod 644 for each found file
# 644 - permissions: owner can read and write, others can only read

These commands set file ownership (www-data - web server user) and correct permissions.

WordPress database configuration screen during installation wizard setup.

Creating Configuration File

Copy the sample configuration file:

cd /var/www/html/
# cd - change current directory
# /var/www/html/ - go to website root directory

sudo cp wp-config-sample.php wp-config.php
# sudo cp - copy file with administrator privileges
# wp-config-sample.php - WordPress sample configuration file
# wp-config.php - main WordPress configuration file

Edit the configuration file using a text editor²¹:

²¹ Text editor (nano) - program for editing text files in terminal

sudo nano wp-config.php
# sudo nano - open text editor with administrator privileges
# wp-config.php - file to edit

Change the following lines according to the created database:

define('DB_NAME', 'wordpress_db');
// define - PHP function to define constant
// 'DB_NAME' - constant name for database name
// 'wordpress_db' - constant value (your database name)

define('DB_USER', 'wp_user');
// 'DB_USER' - constant for database username
// 'wp_user' - username created for WordPress

define('DB_PASSWORD', 'secure_password');
// 'DB_PASSWORD' - constant for database password
// 'secure_password' - database user password

define('DB_HOST', 'localhost');
// 'DB_HOST' - constant for database server address
// 'localhost' - database is on the same server

Configuring Nginx Server Blocks

Creating Server Block

Nginx uses Server Blocks²² to serve multiple domains²³.[8][9]

Example of an Nginx server block configuration specifying server name, document root, index files, and try_files directive on CentOS 7.

Create a new configuration file for your domain:

²² Server Block - Nginx configuration for individual website or domain
²³ Domain - website name on the Internet (e.g., example.com)

sudo nano /etc/nginx/sites-available/your-domain.com
# sudo nano - open editor with administrator privileges
# /etc/nginx/sites-available/ - directory for website configurations
# your-domain.com - configuration file name (replace with your domain)

Example of an Nginx server block configuration specifying server name, document root, index files, and try_files directive on CentOS 7.

Add the following configuration for WordPress with Nginx:

server {
    # server - start of server configuration block
   
    listen 80;
    # listen 80 - listen for HTTP requests on port 80
   
    listen [::]:80;
    # listen [::]:80 - listen for HTTP requests on port 80 for IPv6
   
    server_name your-domain.com www.your-domain.com;
    # server_name - domain names served by this block
   
    root /var/www/html;
    # root - website root directory
   
    index index.php index.html index.htm;
    # index - index files that Nginx looks for by default

    # Logging
    access_log /var/log/nginx/your-domain.com-access.log;
    # access_log - file for recording visit logs
   
    error_log /var/log/nginx/your-domain.com-error.log;
    # error_log - file for recording error logs

    # Main location block
    location / {
        # location / - handle all URLs starting with /
       
        try_files $uri $uri/ /index.php$is_args$args;
        # try_files - try to find file, then directory, otherwise pass to PHP
        # $uri - requested URI
        # $uri/ - try as directory
        # /index.php$is_args$args - pass to index.php with parameters
    }

    # Processing PHP files through PHP-FPM
    location ~ \.php$ {
        # location ~ \.php$ - process all files ending with .php
       
        include snippets/fastcgi-php.conf;
        # include - include additional configuration file
       
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        # fastcgi_pass - pass request to PHP-FPM through Unix socket
       
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        # fastcgi_param - set parameter for PHP-FPM
        # SCRIPT_FILENAME - full path to PHP file
       
        include fastcgi_params;
        # include fastcgi_params - include standard FastCGI parameters
    }

    # Deny access to system files
    location ~ /\.ht {
        # location ~ /\.ht - block for files starting with .ht
       
        deny all;
        # deny all - deny access to everyone
    }

    # Static files optimization
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        # location ~* - case-insensitive matching for static files
       
        expires 1y;
        # expires 1y - set caching period to 1 year
       
        add_header Cache-Control "public, immutable";
        # add_header - add HTTP caching header
       
        log_not_found off;
        # log_not_found off - don't log missing static files
    }

    # WordPress specific settings
    location = /favicon.ico {
        # location = /favicon.ico - exact match for favicon
       
        log_not_found off;
        # don't log missing favicon
       
        access_log off;
        # don't log favicon requests
    }

    location = /robots.txt {
        # location = /robots.txt - exact match for robots.txt
       
        allow all;
        # allow all - allow access to everyone
       
        log_not_found off;
        # don't log if file not found
       
        access_log off;
        # don't log robots.txt requests
    }

    # Protection from access to configuration files
    location ~* \.(conf|engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)$|^(\..*|Entries.*|Repository|Root|Tag|Template)$|\.php_
    {
        # complex regex to protect system files
       
        deny all;
        # deny all - deny access to these files
    }
}

This configuration defines how Nginx handles requests to your website.

Activate the new site by creating a symbolic link²⁴:

²⁴ Symbolic link - shortcut that points to another file or folder

sudo ln -s /etc/nginx/sites-available/your-domain.com /etc/nginx/sites-enabled/
# sudo ln - create link with administrator privileges
# -s - create symbolic link
# /etc/nginx/sites-available/your-domain.com - link source
# /etc/nginx/sites-enabled/ - directory for active sites

sudo nginx -t
# sudo nginx -t - check Nginx configuration syntax

sudo systemctl reload nginx
# sudo systemctl reload - reload Nginx configuration

Remove the default site:

sudo unlink /etc/nginx/sites-enabled/default
# sudo unlink - remove symbolic link
# /etc/nginx/sites-enabled/default - default Nginx configuration

Configuring PHP-FPM for Optimal Performance

Creating Separate Pool for WordPress

PHP-FPM allows creating separate pools²⁵ for better resource management. Create a dedicated pool for WordPress:[7]

²⁵ PHP-FPM pool - group of PHP processes for serving one or multiple websites

sudo nano /etc/php/8.2/fpm/pool.d/wordpress.conf
# sudo nano - open editor with administrator privileges
# /etc/php/8.2/fpm/pool.d/ - directory for PHP-FPM pool configurations
# wordpress.conf - pool configuration file for WordPress

Add the following configuration:

[wordpress]
; [wordpress] - PHP-FPM pool name

user = www-data
; user - user under which PHP processes run

group = www-data
; group - user group for PHP processes

listen = /run/php/php8.2-fpm-wordpress.sock
; listen - socket for connection with Nginx

listen.owner = www-data
; listen.owner - socket file owner

listen.group = www-data
; listen.group - socket file owner group

listen.mode = 0660
; listen.mode - socket file access permissions

pm = dynamic
; pm - process management mode (dynamic = dynamic)

pm.max_children = 20
; pm.max_children - maximum number of child processes

pm.start_servers = 4
; pm.start_servers - number of processes at startup

pm.min_spare_servers = 2
; pm.min_spare_servers - minimum number of idle processes

pm.max_spare_servers = 6
; pm.max_spare_servers - maximum number of idle processes

pm.max_requests = 500
; pm.max_requests - after how many requests to restart process

; PHP settings
php_admin_value[memory_limit] = 256M
; memory_limit - maximum memory for one PHP script

php_admin_value[max_execution_time] = 300
; max_execution_time - maximum script execution time in seconds

php_admin_value[upload_max_filesize] = 64M
; upload_max_filesize - maximum file size for upload

php_admin_value[post_max_size] = 64M
; post_max_size - maximum POST request size

; Security
php_admin_flag[allow_url_fopen] = off
; allow_url_fopen - disable opening URLs as files (security)

php_admin_value[disable_functions] = exec,passthru,shell_exec,system
; disable_functions - disable dangerous PHP functions

These settings define how many PHP processes to start and what limits to set.

Update Nginx configuration to use the new pool:

location ~ \.php$ {
    # location ~ \.php$ - block for processing PHP files
   
    include snippets/fastcgi-php.conf;
    # include - include standard FastCGI configuration
   
    fastcgi_pass unix:/run/php/php8.2-fpm-wordpress.sock;
    # fastcgi_pass - pass request to new WordPress pool
   
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    # fastcgi_param - set script path parameter
   
    include fastcgi_params;
    # include - include standard FastCGI parameters
}

Restart PHP-FPM:

sudo systemctl restart php8.2-fpm
# sudo systemctl restart - restart service
# php8.2-fpm - PHP-FPM service name

Completing WordPress Installation

Web Interface Setup

Open a web browser and navigate to your domain address. You will see the WordPress initial setup screen.

WordPress database configuration screen during installation wizard setup.

WordPress setup configuration page for entering database details during installation.

WordPress setup configuration page for entering database details during installation.

Select interface language and enter site information:

  • Site name

  • Administrator username

  • Strong password

  • Administrator email address

After completing setup, you can log into the WordPress admin panel²⁶ at http://your-domain.com/wp-admin/.

²⁶ Admin panel - web interface for managing website, adding content and settings

WordPress Security Configuration

Protecting wp-config.php File

The wp-config.php file contains critically important information about your site. Unlike other web servers, Nginx doesn't use .htaccess files²⁷, so protection is configured directly in the server block:[10][11]

²⁷ .htaccess files - configuration files for Apache web servers, Nginx doesn't support them

# Deny access to wp-config.php
location ~* wp-config\.php {
    # location ~* wp-config\.php - case-insensitive match for wp-config.php
   
    deny all;
    # deny all - deny access to all users
}

# Deny access to logs and configuration files
location ~* \.(log|conf)$ {
    # location ~* \.(log|conf)$ - files with .log and .conf extensions
   
    deny all;
    # deny all - deny access to these files
}

Hiding Nginx Version

Add to main Nginx configuration:

sudo nano /etc/nginx/nginx.conf
# sudo nano - open editor with administrator privileges
# /etc/nginx/nginx.conf - main Nginx configuration file

http {
    # http - main HTTP configuration block

    # Hide Nginx version
    server_tokens off;
    # server_tokens off - don't show Nginx version in headers
   
    # Additional security headers
    add_header X-Frame-Options SAMEORIGIN;
    # X-Frame-Options SAMEORIGIN - allow frames only from same domain
   
    add_header X-Content-Type-Options nosniff;
    # X-Content-Type-Options nosniff - prevent browser from guessing content type
   
    add_header X-XSS-Protection "1; mode=block";
    # X-XSS-Protection - protection from XSS attacks
   
    add_header Referrer-Policy "strict-origin-when-cross-origin";
    # Referrer-Policy - control referrer header transmission
}

These headers²⁸ protect against various types of attacks.

²⁸ HTTP headers - additional information transmitted with web pages to configure browser behavior

Limiting Login Attempts at Nginx Level

Create request rate limiting zones²⁹:

²⁹ Rate limiting - limiting number of requests per time period to protect from attacks

http {
    # http - main HTTP configuration block
   
    limit_req_zone $binary_remote_addr zone=wp_login:10m rate=1r/s;
    # limit_req_zone - create request limiting zone
    # $binary_remote_addr - grouping key (client IP address)
    # zone=wp_login:10m - zone name and memory size (10 megabytes)
    # rate=1r/s - allow 1 request per second
   
    limit_req_zone $binary_remote_addr zone=wp_admin:10m rate=5r/s;
    # zone=wp_admin:10m - zone for admin panel
    # rate=5r/s - allow 5 requests per second for admin panel
}

server {
    # server - server configuration block
   
    # Limits for wp-login.php
    location = /wp-login.php {
        # location = /wp-login.php - exact match for login page
       
        limit_req zone=wp_login burst=2 nodelay;
        # limit_req - apply limits
        # zone=wp_login - use wp_login zone
        # burst=2 - allow burst load up to 2 requests
        # nodelay - don't delay allowed requests
       
        include snippets/fastcgi-php.conf;
        # include - include standard FastCGI configuration
       
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        # fastcgi_pass - pass request to PHP-FPM
    }
   
    # Limits for wp-admin
    location ^~ /wp-admin/ {
        # location ^~ /wp-admin/ - URLs starting with /wp-admin/
       
        limit_req zone=wp_admin burst=5 nodelay;
        # apply limits for admin panel with burst load up to 5
    }
}

SSL/TLS Configuration with Let's Encrypt

Installing Certbot for Nginx

Let's Encrypt³⁰ provides free SSL certificates³¹:[12][13]

³⁰ Let's Encrypt - non-profit organization that issues free SSL certificates
³¹ SSL certificate - digital document that ensures encrypted connection between browser and server

sudo apt install python3-certbot-nginx -y
# sudo apt install - install package with administrator privileges
# python3-certbot-nginx - Certbot client for Nginx
# -y - automatically confirm installation

Obtaining SSL Certificate

Run Certbot for automatic SSL configuration:

sudo certbot --nginx -d your-domain.com -d www.your-domain.com
# sudo certbot - run Certbot with administrator privileges
# --nginx - use Nginx plugin
# -d your-domain.com - domain for certificate
# -d www.your-domain.com - additional domain (with www)

Certbot automatically modifies Nginx configuration, adding SSL settings:

server {
    # server - HTTPS server configuration block
   
    listen 443 ssl http2;
    # listen 443 - listen for HTTPS on port 443
    # ssl - activate SSL
    # http2 - HTTP/2 protocol support
   
    listen [::]:443 ssl http2;
    # IPv6 support for HTTPS
   
    server_name your-domain.com www.your-domain.com;
    # server_name - domains for this block
   
    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    # ssl_certificate - path to SSL certificate
   
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
    # ssl_certificate_key - path to SSL private key
   
    # SSL optimization
    ssl_session_cache shared:SSL:10m;
    # ssl_session_cache - SSL session cache (10MB shared cache)
   
    ssl_session_timeout 10m;
    # ssl_session_timeout - SSL session lifetime (10 minutes)
   
    ssl_protocols TLSv1.2 TLSv1.3;
    # ssl_protocols - supported TLS versions (only secure ones)
   
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
    # ssl_ciphers - allowed encryption algorithms
   
    ssl_prefer_server_ciphers off;
    # ssl_prefer_server_ciphers off - allow client to choose cipher
   
    # HSTS
    add_header Strict-Transport-Security "max-age=63072000" always;
    # HSTS - force browser to use HTTPS (2 years)
}

# HTTP to HTTPS redirect
server {
    # server - block for redirecting HTTP traffic
   
    listen 80;
    # listen 80 - listen for HTTP requests
   
    listen [::]:80;
    # IPv6 support for HTTP
   
    server_name your-domain.com www.your-domain.com;
    # server_name - domains for redirect
   
    return 301 https://$server_name$request_uri;
    # return 301 - permanent redirect (code 301)
    # https://$server_name$request_uri - to HTTPS version of same URL
}

Automatic Certificate Renewal

Configure automatic certificate renewal through cron³²:

³² Cron - task scheduler in Linux that allows automatically running commands on schedule

sudo crontab -e
# sudo crontab -e - edit task schedule for root user

Add the following line for daily renewal check at 12:00:

0 12 * * * /usr/bin/certbot renew --quiet
# 0 12 * * * - execution time (minute hour day month day_of_week)
# /usr/bin/certbot renew - certificate renewal command
# --quiet - quiet mode (no output if everything is fine)

Complete WordPress Optimization with Nginx

Configuring FastCGI Caching

FastCGI cache³³ is one of the most effective optimization methods for Nginx with PHP.

Nginx FastCGI cache architecture illustrating cache layers from browser to database and their impact on server response speed.

Add to Nginx configuration:

³³ FastCGI cache - mechanism for storing ready pages for faster serving of repeated requests

http {
    # http - main HTTP configuration block
   
    # FastCGI cache settings
    fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=WORDPRESS:100m inactive=60m;
    # fastcgi_cache_path - path to cache directory
    # /var/cache/nginx - directory for storing cache
    # levels=1:2 - cache subfolder structure (1 level, 2 characters)
    # keys_zone=WORDPRESS:100m - memory zone for cache keys (100MB)
    # inactive=60m - remove inactive files after 60 minutes
   
    fastcgi_cache_key "$scheme$request_method$host$request_uri";
    # fastcgi_cache_key - cache key (protocol+method+host+URI)
   
    fastcgi_cache_use_stale error timeout invalid_header http_500;
    # fastcgi_cache_use_stale - serve stale cache on errors
   
    fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
    # fastcgi_ignore_headers - ignore these headers when caching
}

server {
    # server - server configuration block
   
    # Caching settings for PHP
    location ~ \.php$ {
        # location ~ \.php$ - block for PHP files
       
        # Standard PHP-FPM configuration
        include snippets/fastcgi-php.conf;
        # include - include standard FastCGI configuration
       
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        # fastcgi_pass - pass request to PHP-FPM
       
        # FastCGI cache
        fastcgi_cache WORDPRESS;
        # fastcgi_cache - use WORDPRESS cache zone
       
        fastcgi_cache_valid 200 60m;
        # fastcgi_cache_valid 200 60m - cache successful responses for 60 minutes
       
        fastcgi_cache_valid 404 1m;
        # cache 404 errors for 1 minute
       
        # Adding cache headers
        add_header X-Cache $upstream_cache_status;
        # X-Cache - header with cache status (HIT/MISS/BYPASS)
       
        # Skip caching for admin panel and logged users
        fastcgi_cache_bypass $skip_cache;
        # fastcgi_cache_bypass - skip cache if $skip_cache = 1
       
        fastcgi_no_cache $skip_cache;
        # fastcgi_no_cache - don't store in cache if $skip_cache = 1
    }
   
    # Variables for cache management
    set $skip_cache 0;
    # set $skip_cache 0 - allow caching by default
   
    # Skip caching for POST requests
    if ($request_method = POST) {
        # if - condition to check request method
       
        set $skip_cache 1;
        # set cache skip flag for POST requests
    }
   
    # Skip caching for URLs with parameters
    if ($query_string != "") {
        # if - check if there are GET parameters in URL
       
        set $skip_cache 1;
        # don't cache pages with parameters
    }
   
    # Skip caching for admin panel
    if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") {
        # if - check URI with regular expression
        # ~* - case-insensitive matching
       
        set $skip_cache 1;
        # don't cache admin pages and system files
    }
   
    # Skip caching for logged users
    if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
        # if - check cookies for authorization markers
       
        set $skip_cache 1;
        # don't cache for logged users
    }
}

Create cache directory:

sudo mkdir -p /var/cache/nginx
# sudo mkdir - create directory with administrator privileges
# -p - create all necessary parent directories
# /var/cache/nginx - path to cache directory

sudo chown www-data:www-data /var/cache/nginx
# sudo chown - change directory ownership
# www-data:www-data - set owner and group to www-data

Static Files Optimization

Configure efficient caching for static files³⁴:

³⁴ Static files - images, CSS, JavaScript and other files that don't change dynamically

server {
    # server - server configuration block
   
    # Media files caching
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        # location ~* - case-insensitive matching for images and styles
       
        expires 1y;
        # expires 1y - set caching period to 1 year
       
        add_header Cache-Control "public, immutable";
        # Cache-Control - public cache, files are immutable
       
        add_header Vary Accept-Encoding;
        # Vary Accept-Encoding - cache different versions for compression
       
        log_not_found off;
        # don't log missing static files
    }
   
    # Font caching
    location ~* \.(woff|woff2|ttf|svg|eot)$ {
        # location ~* - block for web fonts
       
        expires 1y;
        # cache fonts for 1 year
       
        add_header Cache-Control "public, immutable";
        # public immutable cache for fonts
       
        add_header Access-Control-Allow-Origin "*";
        # allow font usage from other domains
    }
   
    # Gzip compression
    gzip on;
    # gzip on - activate gzip compression
   
    gzip_vary on;
    # gzip_vary - add Vary: Accept-Encoding header
   
    gzip_min_length 1024;
    # gzip_min_length - compress files larger than 1KB
   
    gzip_comp_level 6;
    # gzip_comp_level - compression level (1-9, 6 is optimal)
   
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/javascript
        application/xml+rss
        application/json;
    # gzip_types - file types for compression
}

Configuring Brotli Compression

For even better compression³⁵, install Brotli module:

³⁵ Brotli - modern compression algorithm that provides better results than Gzip

sudo apt install nginx-module-brotli -y
# sudo apt install - install package with administrator privileges
# nginx-module-brotli - Brotli module for Nginx
# -y - automatically confirm installation

Add to Nginx configuration:

load_module modules/ngx_http_brotli_filter_module.so;
# load_module - load Brotli filter module

load_module modules/ngx_http_brotli_static_module.so;
# load_module - load module for pre-compressed files

http {
    # http - main HTTP configuration block
   
    # Brotli compression
    brotli on;
    # brotli on - activate Brotli compression
   
    brotli_comp_level 6;
    # brotli_comp_level - Brotli compression level (0-11, 6 is balanced)
   
    brotli_types
        text/plain
        text/css
        application/json
        application/javascript
        text/xml
        application/xml
        application/xml+rss
        text/javascript;
    # brotli_types - file types for Brotli compression
}

PHP-FPM Optimization

Configure optimal parameters for your server in /etc/php/8.2/fpm/php.ini file:

sudo nano /etc/php/8.2/fpm/php.ini
# sudo nano - open editor with administrator privileges
# /etc/php/8.2/fpm/php.ini - PHP configuration file

; Memory optimization
memory_limit = 256M
; memory_limit - maximum memory for PHP script (256 megabytes)

max_execution_time = 300
; max_execution_time - maximum script execution time (5 minutes)

max_input_vars = 3000
; max_input_vars - maximum number of input variables

; OPcache optimization
opcache.enable=1
; opcache.enable - activate OPcache

opcache.memory_consumption=128
; opcache.memory_consumption - memory for OPcache (128MB)

opcache.interned_strings_buffer=8
; opcache.interned_strings_buffer - string buffer (8MB)

opcache.max_accelerated_files=4000
; opcache.max_accelerated_files - maximum number of cached files

opcache.revalidate_freq=2
; opcache.revalidate_freq - check file changes every 2 seconds

opcache.fast_shutdown=1
; opcache.fast_shutdown - fast shutdown

opcache.enable_cli=1
; opcache.enable_cli - activate OPcache for command line

; File uploads
upload_max_filesize = 64M
; upload_max_filesize - maximum file size for upload

post_max_size = 64M
; post_max_size - maximum POST request size

; Sessions
session.save_handler = files
; session.save_handler - store sessions in files

session.save_path = "/tmp"
; session.save_path - directory for session files

OPcache³⁶ significantly speeds up PHP script execution.

³⁶ OPcache - mechanism for caching compiled PHP code to speed up execution

Flowchart showing opcode caching process in PHP scripts, illustrating how precompiled code improves execution efficiency important for WordPress performance optimization.

Nginx Performance Monitoring

Activate Nginx statistics module:

sudo nano /etc/nginx/sites-available/your-domain.com
# sudo nano - open site configuration for editing

Add location for statistics:

location /nginx_status {
    # location /nginx_status - block for Nginx statistics
   
    stub_status;
    # stub_status - activate statistics module
   
    allow 127.0.0.1;
    # allow 127.0.0.1 - allow access only from localhost
   
    deny all;
    # deny all - deny access to everyone else
}

Check statistics:

curl http://localhost/nginx_status
# curl - utility for HTTP requests
# http://localhost/nginx_status - Nginx statistics URL

Clearing FastCGI Cache

Create a script³⁷ for clearing cache:

³⁷ Script - automated sequence of commands

sudo nano /usr/local/bin/clear-nginx-cache.sh
# sudo nano - create new script file with administrator privileges
# /usr/local/bin/ - directory for local scripts

#!/bin/bash
# #!/bin/bash - specify interpreter for script execution

sudo rm -rf /var/cache/nginx/*
# sudo rm - delete files with administrator privileges
# -rf - recursively and forcefully
# /var/cache/nginx/* - all Nginx cache files

sudo systemctl reload nginx
# sudo systemctl reload - reload Nginx configuration

echo "Nginx FastCGI cache cleared"
# echo - output message about successful clearing

Make script executable:

sudo chmod +x /usr/local/bin/clear-nginx-cache.sh
# sudo chmod - change file permissions
# +x - add execution permission
# /usr/local/bin/clear-nginx-cache.sh - path to script

Backup and Maintenance

Backup Strategy

Create script for automatic backup including Nginx configuration:

sudo nano /usr/local/bin/wp-backup.sh
# sudo nano - create new backup script

#!/bin/bash
# #!/bin/bash - bash interpreter for script execution

# Variables
DB_NAME="wordpress_db"
# DB_NAME - WordPress database name

DB_USER="wp_user"
# DB_USER - database user

DB_PASSWORD="secure_password"
# DB_PASSWORD - database password

WP_PATH="/var/www/html"
# WP_PATH - path to WordPress files

NGINX_PATH="/etc/nginx"
# NGINX_PATH - path to Nginx configuration

BACKUP_PATH="/backups/wordpress"
# BACKUP_PATH - directory for storing backups

DATE=$(date +%Y%m%d_%H%M%S)
# DATE - current date and time for file name

# Create backup directory
mkdir -p $BACKUP_PATH
# mkdir -p - create directory and all parent directories

# Database backup
mysqldump -u$DB_USER -p$DB_PASSWORD $DB_NAME > $BACKUP_PATH/database_$DATE.sql
# mysqldump - MySQL/MariaDB database export utility
# > - redirect output to file

# WordPress files backup
tar -czf $BACKUP_PATH/wordpress_files_$DATE.tar.gz -C $WP_PATH .
# tar - archiving utility
# -czf - create gzip archive from file
# -C - change directory before archiving

# Nginx configuration backup
tar -czf $BACKUP_PATH/nginx_config_$DATE.tar.gz -C $NGINX_PATH .
# archive Nginx configuration files

# Delete old backups (older than 30 days)
find $BACKUP_PATH -type f -name "*.sql" -mtime +30 -delete
# find - find files older than 30 days and delete them
# -type f - only files
# -mtime +30 - modified more than 30 days ago
# -delete - delete found files

find $BACKUP_PATH -type f -name "*.tar.gz" -mtime +30 -delete
# delete old archives

echo "Backup completed: $DATE"
# echo - output completion message

Nginx Log Monitoring

Configure log rotation³⁸ for better management:

³⁸ Log rotation - automatic archiving and deletion of old log files to save disk space

sudo nano /etc/logrotate.d/nginx-wordpress
# sudo nano - create log rotation configuration

/var/log/nginx/*.log {
    # /var/log/nginx/*.log - path to all Nginx log files
   
    daily
    # daily - perform rotation daily
   
    missingok
    # missingok - don't error if file is missing
   
    rotate 30
    # rotate 30 - keep 30 old log versions
   
    compress
    # compress - compress old log files
   
    delaycompress
    # delaycompress - don't compress immediately, but on next rotation
   
    notifempty
    # notifempty - don't rotate empty files
   
    create 0644 www-data adm
    # create - create new log file with permissions 0644, owner www-data, group adm
   
    sharedscripts
    # sharedscripts - execute scripts only once for all files
   
    prerotate
        # prerotate - commands before rotation
        if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
            run-parts /etc/logrotate.d/httpd-prerotate; \
        fi
        # execute additional scripts if they exist
    endscript
   
    postrotate
        # postrotate - commands after rotation
        invoke-rc.d nginx rotate >/dev/null 2>&1
        # reload Nginx to create new log files
    endscript
}

Troubleshooting Common Issues

502 Bad Gateway Error

This error³⁹ often occurs due to PHP-FPM issues. Check:[14][15]

³⁹ 502 Bad Gateway - error that occurs when Nginx cannot connect to PHP-FPM

  1. PHP-FPM status:

sudo systemctl status php8.2-fpm
# sudo systemctl status - check PHP-FPM service status

  1. Correct socket path in Nginx configuration

  2. PHP-FPM socket permissions⁴⁰:

⁴⁰ Socket - file for communication between Nginx and PHP-FPM

ls -la /run/php/
# ls - show file list with details
# -la - show all files with detailed information
# /run/php/ - directory with PHP-FPM sockets

413 Request Entity Too Large Error

Increase upload limits in Nginx:

server {
    # server - server configuration block
   
    client_max_body_size 64M;
    # client_max_body_size - maximum request size from client (64MB)
}

Nginx requires manual configuration of rewrite rules⁴¹. Make sure your configuration has:

⁴¹ Rewrite rules - instructions for URL redirection

location / {
    # location / - main block for all URLs
   
    try_files $uri $uri/ /index.php$is_args$args;
    # try_files - try file, directory, otherwise pass to PHP
    # $uri - requested URI
    # $uri/ - try as directory
    # /index.php$is_args$args - pass to index.php with parameters
}

FastCGI Cache Issues

If caching problems occur:

  1. Check cache directory permissions:

sudo chown -R www-data:www-data /var/cache/nginx
# sudo chown - change ownership recursively for all files
# www-data:www-data - set owner and group

sudo chmod -R 755 /var/cache/nginx
# sudo chmod - change permissions recursively
# 755 - permissions: owner can do everything, others can read and execute

  1. Clear cache:

sudo rm -rf /var/cache/nginx/*
# sudo rm - delete files with administrator privileges
# -rf - recursively and forcefully
# /var/cache/nginx/* - all cache files

sudo systemctl reload nginx
# sudo systemctl reload - reload configuration without stopping service

SSL Issues

Check SSL configuration:

sudo nginx -t
# sudo nginx -t - check Nginx configuration syntax

sudo certbot certificates
# sudo certbot certificates - show information about installed certificates

If certificate doesn't auto-renew:

sudo certbot renew --dry-run --nginx
# sudo certbot renew - renew certificates
# --dry-run - test mode without real changes
# --nginx - use Nginx plugin

Conclusion

Deploying WordPress on Debian with Nginx provides high performance and reliability thanks to the advantages of the LEMP stack. Nginx demonstrates significantly better performance metrics, especially under high load, due to its efficient event-driven architecture.[1][2]

Diagram showing the components of the LEMP stack with Nginx as the web server, PHP-FPM as the scripting layer, and MariaDB/MySQL as the database layer, all running on the Linux operating system.

Key Advantages of Nginx for WordPress:

Performance: Nginx consumes less memory and CPU resources, handling thousands of concurrent connections with minimal resource overhead.[2]

Diagram showing the components of the LEMP stack with Nginx as the web server, PHP-FPM as the scripting layer, and MariaDB/MySQL as the database layer, all running on the Linux operating system.

FastCGI caching provides significant website acceleration by reducing load on PHP and the database.

Scalability⁴²: Nginx architecture allows easy application scaling to serve millions of requests per day without significant performance degradation.[1][3]

⁴² Scalability - system's ability to handle growing load by adding resources

Security: Centralized configuration management at the server level increases security since all settings are controlled by the administrator.[16]

Flexibility: Nginx Server Blocks provide more configuration options, allowing creation of complex routing and request processing rules.[8][9]

A properly configured WordPress with Nginx on Debian can provide response times under 100 milliseconds even under high load.

Nginx FastCGI cache architecture illustrating cache layers from browser to database and their impact on server response speed.

Regular maintenance, monitoring, and updates ensure your site remains fast, secure, and stable over time.

  1. https://www.rosehosting.com/blog/how-to-install-wordpress-with-nginx-on-debian-10/       
  2. https://spinupwp.com/hosting-wordpress-yourself-nginx-php-mysql/    
  3. https://www.linuxtuto.com/how-to-install-wordpress-on-debian-12/      
  4. https://linuxcapable.com/how-to-install-wordpress-with-nginx-on-debian-linux/  
  5. https://mariadb.com/resources/blog/how-to-install-and-run-wordpress-with-mariadb/    
  6. https://computingforgeeks.com/install-wordpress-with-nginx-on-ubuntu-debian/    
  7. https://www.digitalocean.com/community/tutorials/php-fpm-nginx  
  8. https://spinupwp.com/hosting-wordpress-yourself-setting-up-sites/  
  9. https://www.digitalocean.com/community/tutorials/install-wordpress-nginx-ubuntu  
  10. https://melapress.com/secure-wp-config-php-file/ 
  11. https://www.malcare.com/blog/secure-site-with-wp-config/ 
  12. https://snapshooter.com/learn/guides/install-wordpress 
  13. https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-debian-10 
  14. https://www.rosehosting.com/blog/how-to-solve-the-most-common-wordpress-errors/ 
  15. https://docs.vultr.com/common-wordpress-troubleshooting-guide 
  16. https://developer.wordpress.org/advanced-administration/server/web-server/nginx/ 

Rate the article on a 5-point scale

Be the first to rate this news!
Find mistake? Select it and press Ctrl + Enter to let us know.
×

Report a bug

Error text

Your choice