3v-Hosting Blog

Required Nginx settings for WordPress

Administration

12 min read


For a long time, WordPress was perceived as a tool for quickly launching websites, because all you had to do was choose a suitable theme, install a couple of plugins, activate caching, and that was it - the project was up and running. But as traffic grows, it becomes clear that true performance is determined not by the PHP code of the theme or even the database settings, but by the server configuration. It is the web server, in our case Nginx, that sets the bandwidth limit for the project, as it determines the routing of requests, response speed, caching efficiency, and resistance to high loads.

If a site receives at least a few thousand visits per day, any unfinished Nginx directives begin to manifest themselves in the form of errors, delays, and other unpredictable behavior. A properly configured server, on the other hand, allows WordPress to run stably, quickly, and with a large performance margin. In this article, we will look at a complete set of configurations that turn a regular VPS server into a ready-made platform for a serious WordPress project.

 

 

 

 

Proper PHP-FPM configuration

PHP-FPM is the core of dynamic request processing in WordPress. Its configuration determines how fast the site's backend will work and what load will be placed on the CPU and RAM. Errors in the PHP-FPM configuration often look like “site slowdowns,” even though the PHP logic itself may be working perfectly.

The main problem is the use of default pool parameters. For example, the configuration pm = dynamic with too low a value of pm.max_children creates an effect where the server looks “half-empty” while WordPress serves a queue of requests. Correct process configuration allows you to distribute the load evenly and prevent freezes under peak loads.

 

Recommended PHP-FPM values for VPS

To make it easier for you to select the parameters you need, we have provided a simple reference table below.

 

Table - PHP-FPM parameter selection

RAM pm.max_children pm.start_servers pm.min_spare_servers pm.max_spare_servers
1 GB 3-4 1 1 2
2 GB 6-8 2 2 4
4 GB 10-15 3 3 6

 

 

Monitoring PHP-FPM

If you still want to determine the exact values, then you need to monitor the processes. To do this, you can use the following command:

ps aux | grep php-fpm journalctl -u php8.2-fpm top -Hp $(pidof php-fpm)

 

 

The conclusions are quite simple: if the processes reach their maximum, increase the limits, and if the RAM runs out, decrease them.

 

Sockets vs. TCP

To work with Nginx, it is better to use a Unix socket:

fastcgi_pass unix:/run/php/php8.2-fpm.sock;

 

It works faster and more stable than TCP connections, especially on weak VPS. This improvement provides a noticeable performance gain and reduces delays between Nginx and PHP-FPM.

 

 

 

 

Routing in WordPress using the try_files construct

WordPress uses a front controller in its work, where almost all requests go to index.php. This allows you to flexibly manage your site's URL structure, human-readable URLs, custom post types, and REST API. But this scheme only works if the try_files directive is configured correctly.

One line is enough to either ensure stable website operation or completely break its routing. Errors in the description of try_files are the cause of a huge number of incorrect 404s, non-working plugins, and incorrect redirects. The basic correct configuration looks like this:

try_files $uri $uri/ /index.php?$args; 

 

Inexperienced administrators often make mistakes that can lead to a variety of unpleasant consequences. In the table below, we have listed the most common ones and described the consequences of these errors.

 

Table - errors in the try_files configuration

Error Consequence
Missing $args parameter Pagination breaks, as well as WordPress search and WooCommerce filters
Arguments order changed Some pages start being served as static content
Duplication across multiple location blocks Different plugins may route requests inconsistently
Passing only /index.php Some URLs work without GET parameters and behave incorrectly

 

To check the correctness of URL processing, you can use the following command:

curl -I https://site.com/test/ curl -I https://site.com/page/2/?filter=color

 

If the server returns a 404 where it shouldn't, then there is an error in try_files.

 

 

 

 

Caching static files

WordPress dynamically generates only the HTML part of the page. Everything else, including images, style sheets, fonts, and JavaScript, can and should be served by Nginx, without the involvement of PHP-FPM. This significantly reduces the load on your server.

Properly selected caching headers allow the browser and CDN to almost completely eliminate repeated requests for static resources. Here is a simple example of a caching configuration:

location ~* \.(jpg|jpeg|png|gif|svg|css|js|ico|woff2?)$ {

expires 30d;

add_header Cache-Control “public, no-transform”;

}

 

 

Table - Cache lifetime depending on file type

File type Recommended cache duration
CSS, JS 30–90 days
WOFF2 180 days
PNG/JPG/SVG 30–180 days
Theme files 7–30 days (if updated frequently)

The durations given in this table are still approximate, and we recommend that you select them yourself depending on your content management style or the predominant type of content posted on your website. Proper caching can reduce the proportion of PHP requests from 70% to 5-10% of the total number of requests received by the server.

 

 

 

 

 

Compression using Gzip and Brotli

Compression is perhaps one of the most underrated optimization tools. It significantly reduces the size of the server response packet and thus speeds up the loading of the site. Most configurations are limited to using gzip, although Brotli provides better compression in some cases and has long been supported by almost all browsers.

For websites with a lot of CSS and JS, Brotli can reduce the response weight by 20-25% more than gzip.

 

Example block for gzip

gzip on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript application/xml;

 

Example block for Brotli

brotli on;

brotli_comp_level 5;

brotli_min_length 1024;

brotli_types text/plain text/css application/javascript application/json application/xml+rss application/xml application/font-woff application/font-woff2 image/svg+xml;

 

 

IMPORTANT! Brotli is not a panacea, and there are at least a few cases when it should not be used:

  • VPS with low RAM but high traffic;
  • Projects with a large number of simultaneous compressions;
  • Servers where CPU is a limiting resource, i.e. when the server performs a lot of calculations using the processor.

 

 

 

 

 

Correct handling of downloads

WordPress allows you to upload large images, PDFs, archives, and other heavy files. If Nginx settings do not take this into account, users may encounter errors and be unable to download content.

By default, Nginx only allows files up to 1 MB to be uploaded, which makes WordPress virtually unusable. Therefore, it is recommended to immediately increase this limit to 64 MB by default:

client_max_body_size 64M;

 

However, when working with WooCommerce, CSV imports, or video content, this limit should be increased to 100-200 MB. Also, when uploading large files, it is recommended to disable buffering, as this reduces the load on the disk during the upload of large files:

proxy_request_buffering off;

 

 

 

 

 

 

 

Ensuring security and SEO optimization

The modern internet requires the mandatory use of HTTPS. Due to the fact that WordPress is currently the undisputed leader among CMSs, it is particularly vulnerable to bots, scanners, and data interception. Therefore, the server must provide secure and correct encryption.

Incorrect HTTPS configuration can cause redirect loops, mixed content issues, and slowdowns.

Let's start with the mandatory header:

add_header Strict-Transport-Security “max-age=31536000” always;

 

This header enables the HTTP Strict Transport Security (HSTS) mechanism, one of the key technologies that improves the security of a website running on HTTPS.

There are also several optional but desirable headers. For example:

add_header X-Frame-Options “DENY” always;

add_header X-Content-Type-Options “nosniff” always;

add_header Referrer-Policy “strict-origin” always;

add_header X-XSS-Protection “1; mode=block” always;

add_header Permissions-Policy “geolocation=(), microphone=(), camera=()” always;

 

 

What each of them does:

Header Purpose
X-Frame-Options DENY Completely prevents the site from being opened inside an iframe, providing strong protection against clickjacking
X-Content-Type-Options nosniff Prevents MIME-sniffing and the loading of potentially dangerous files
Referrer-Policy strict-origin Sends only the domain, not the full URL, during navigation, improving user privacy
X-XSS-Protection Enables built-in XSS filtering in older browsers
Permissions-Policy Explicitly blocks unnecessary APIs such as geolocation, microphone, camera, etc.

 

 

Minimal SSL configuration for websites

Minimal SSL configuration is a set of settings that does not attempt to “get the maximum score in tests,” but provides an adequate balance between security, compatibility, and ease of support. For most WordPress projects, it is enough to enable modern protocols, secure ciphers, and regularly update certificates to be confident about the security of your project. Below, we will provide the basic SSL configuration in Nginx, as well as a detailed description of what each parameter does and how to incorporate it into your web server's working configuration.

So, the basic block in the server (for port 443) might look something like this:

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

 

 

ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    # The rest of the site configuration...
}

 

Let's take a closer look at the key parameters:

ssl_protocols TLSv1.2 TLSv1.3;

This directive specifies which TLS versions are allowed to connect to the site.

  • The outdated and insecure TLS 1.0 and 1.1 protocols are disabled;
  • Only TLS 1.2 and TLS 1.3 remain, which are currently considered the de facto standard;
  • This improves security and usually does not break compatibility, because the vast majority of current clients and browsers support these versions.

If the site is targeted at very old devices, such as older Android or older built-in browsers, then you will have to evaluate the statistics and proceed accordingly, but for modern projects, the combination of 1.2 + 1.3 is a reasonable minimum.

 

ssl_ciphers HIGH:!aNULL:!MD5;

This directive is responsible for the set of ciphers (cipher suites) that the server allows to be used.

  • HIGH- includes only strong ciphers;
  • !aNULL- prohibits ciphers without authentication (anonymous key exchange), which are vulnerable and do not guarantee authenticity;
  • !MD5 - excludes ciphers that use MD5, as MD5 is considered cryptographically insecure.

In fact, this line filters the list of possible ciphers down to a set that provides normal security without exotic and weak combinations.

 

ssl_prefer_server_ciphers on;

By default, the client (browser) may attempt to impose a cipher from the list of supported ones on the server. Enabling this option directly tells Nginx:

  • “Use the server's priority, not the client's”;
  • The server itself will select the best (in its opinion) cipher from the intersection of the supported lists;
  • This allows you to guarantee the exclusion of weak ciphers, even if the client supports them and tries to offer them.

As a result, it is the server that controls security, rather than relying on possible oddities on the client side.

 

 

 

Optimization for REST API and XML-RPC

Modern WordPress sites increasingly rely on REST API. It is used by WooCommerce online stores, integrations with external services, React/Vue SPA applications, and any headless architectures. REST API processes dynamic requests, so it is important that Nginx directs them to WordPress correctly and does not try to interpret them as regular files. If this is not done, API routes may return a 404 code, work slowly, or conflict with other routing rules.

To ensure that Nginx passes requests to the REST API to the WordPress core, a separate block is used:

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

 

This construct forces all API requests to be sent to index.php, ensuring correct route processing, WooCommerce operation, and external integrations.

 

Now let's move on to XML-RPC.

XML-RPC is an old mechanism for remote access to WordPress that was used before the REST API appeared. Today, it is only used by a few applications (such as mobile apps on WordPress) and some older integrations. However, the xmlrpc.php file itself is one of the most frequently attacked entry points, with attackers sending thousands of requests through it to brute-force passwords.

If your project does not use XML-RPC, it is safer to disable it:

location = /xmlrpc.php { deny all; } 

 

This reduces the load on the server, decreases the risk of brute force attacks, and makes the system more secure without affecting the functionality of most modern websites.

 

 

 

 

 

Protection against bots, scanners, and traffic spikes

Even the perfect WordPress can “go down” if hundreds of requests are sent to wp-login.php or XML-RPC in a minute. Limiting the frequency of requests mitigates such attacks and prevents PHP-FPM overload. In WordPress, this is handled by so-called limits or rate limits. Here is an example of a basic limit configuration that will work for most projects:

limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;

limit_req zone=one burst=20 nodelay;

 

Table - Limit recommendations

Site type Recommended limit
Blog 10–15 r/s
News site 20–30 r/s
E-commerce 30–50 r/s
Corporate website 10 r/s

 

You can check whether the set limits are working by viewing the log with the command:

grep “limiting requests” /var/log/nginx/error.log 

 

 

 

 

 

FastCGI caching

We have already touched on the topic of caching static files above. But it is not only static files that can be cached.

FastCGI cache is one of the most powerful tools for speeding up WordPress today. It allows Nginx to serve HTML pages without calling PHP, which significantly reduces the load and increases the server's response speed.

Unlike caching plugins, FastCGI works at the web server level and is much more effective when the resource has high traffic.

A basic example of a FastCGI configuration looks something like this:

fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=WORDPRESS:100m inactive=60m;
fastcgi_cache_key “$scheme$request_method$host$request_uri”;
fastcgi_cache_use_stale error timeout invalid_header http_500 http_503;

 

IMPORTANT! FastCGI cache cannot be used with all components; there are exceptions due to the logic of these components.

  • Authorized users - The use of personalized data makes caching impossible;
  • WooCommerce Cart / Checkout - The dynamics of changes in the Cart also make it impossible to cache it;
  • Personal account pages - As in the first case, there may be unique data in the personal account that cannot be cached within the entire project.

 

 

 

 

Diagnosing problems in the Nginx configuration

Even a correctly configured server can encounter configuration errors, lack of resources, or incorrect paths. The ability to quickly diagnose a problem is an important part of operating a WordPress project for its administrator or owner. Therefore, below we have provided several commands that greatly facilitate this task.

Checking the Nginx configuration:

nginx -t

 

Viewing errors in the log:

tail -f /var/log/nginx/error.log

 

Checking the server response:

curl -I https://site.com/

 

Simple load testing:

ab -n 1000 -c 50 https://site.com/

 

 

 

 

 

 

FAQ

 

Why is WordPress running slowly when CPU load is minimal?

Most often, PHP-FPM hits a limit set by pm.max_children. In this case, requests are queued and the site slows down, even if the processor is “empty.” Increase the process limit and check the load again.

 

 

How can I check if the FastCGI cache is working?

Look at the response headers:

curl -I https://site.com/

If the cache is configured correctly, you will see X-FastCGI-Cache: HIT or MISS.

If there is no header, it means that the cache is not being used or is not configured.

 

 

Why can't Nginx connect to php-fpm.sock?

Usually, the path to the socket is incorrect. Check:

ls /run/php/

and compare it with the directive:

fastcgi_pass unix:/run/php/php8.2-fpm.sock;

 

If the paths match, make sure PHP-FPM is running.

 

 

Should gzip_static be enabled?

Yes, if your project generates pre-compressed .gz versions of files (e.g., frontend builders).

If there are no such files, the parameter can be left disabled, as it will not change anything.

 

 

How to protect wp-login.php without plugins?

The simplest and most effective way is to simply limit the frequency of requests:

limit_req zone=one burst=5 nodelay; 

 

Or allow access only to certain IPs if the login is rarely used.

 

 

Should XML-RPC be disabled?

If you don't use the WordPress mobile app or older integrations, then yes, it's better to disable it:

location = /xmlrpc.php { deny all; } 

 

This reduces the risk of brute force attacks and reduces the load on the server.

 

 

 

 

Conclusions

WordPress performance is not just about plugins and theme optimization. The main components of speed, such as PHP processing, correct routing, effective caching, security, and load control, are provided at the web server level.

By following the settings described in this article, you can turn an ordinary inexpensive VPS into a reliable platform capable of handling serious projects and high loads. Such a server will ensure stability, improve your project's SEO metrics, reduce resource consumption, and increase the overall responsiveness of the site. For businesses, this means faster page loading, better conversion rates, and resistance to seasonal and daily traffic spikes.

What Is VPS Used For?
What Is VPS Used For?

A clear guide to what VPS is used for: real cases, examples for developers, business setups, VPN, CI/CD and more. Learn how to choose the right VPS plan.

9 min