Optimize PHP-FPM and Nginx for Ultra-Fast VPS

Optimize Nginx and PHP-FPM on VPS

Optimize PHP-FPM and Nginx for Ultra-Fast VPS

Speed is one of the most important factors in hosting websites and web apps. With a fast server, you will have happier users, better SEO, and a smoother experience, and if you are running PHP sites, two tools make all the difference, including Nginx and PHP-FPM. This guide will teach you how to optimize Nginx and PHP-FPM on VPS for a faster experience.

If you are looking for optimized performance, our flexible and high-performance VPS hosting solutions provide fully managed environments with pre-tuned configurations, ensuring your PHP applications run ultra-fast from day one.

Requirements To Optimize Nginx and PHP-FPM on VPS

This guide provides a complete process to install a high-performance web server stack on an Ubuntu VPS.

You must access your server as a non-root user with sudo privileges and install the required packages, Nginx mainline, and PHP 8.3 from the Ondřej Surý PPA.

Run the system update and install the required packages with the following command:

sudo apt update
sudo apt install software-properties-common curl ca-certificates gnupg lsb-release -y

Add the Nginx and PHP PPA repository with the following commands:

sudo add-apt-repository -y ppa:ondrej/nginx
sudo add-apt-repository -y ppa:ondrej/php

Run the system update again to apply the changes:

sudo apt update

Then, install Nginx and PHP-FPM with its common extensions with the following command:

sudo apt install nginx php8.3-fpm php8.3-cli php8.3-mbstring php8.3-xml \
php8.3-curl php8.3-zip php8.3-gd php8.3-intl php8.3-mysql php8.3-opcache -y

Note: If you prefer to use another PHP version, like PHP 8.2, you can replace it.

Also, install the following useful tools:

sudo apt install htop iotop net-tools unzip git wrk -y

Establish a Performance Baseline for Nginx and PHP

It is recommended to establish a known performance baseline before implementing any performance optimizations.

Verify your Nginx and PHP-FPM service status with the following commands:

sudo systemctl status nginx php8.3-fpm --no-pager
nginx -v
php -v

Create a quick and simple PHP test file with the commands below and set the correct ownership for it:

sudo mkdir -p /var/www/example/public
echo "<?php echo 'OK';" | sudo tee /var/www/example/public/index.php
sudo chown -R www-data:www-data /var/www/example

Now use the command below to configure a minimal Nginx server block:

sudo tee /etc/nginx/sites-available/example.conf >/dev/null <<'NGINX'
server {
    listen 80 default_server reuseport;
    server_name _;
    root /var/www/example/public;
    index index.php;

    access_log /var/log/nginx/example.access.log;
    error_log  /var/log/nginx/example.error.log warn;

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

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_read_timeout 30s;
    }
}
NGINX

Note: The server_name _; means Nginx will respond to any hostname. If you’re just testing or using an IP address, you can leave it as an underscore.

Remove or disable the old default site with the command below:

sudo rm /etc/nginx/sites-enabled/default

Enable the new configuration and check for syntax errors with the commands below:

sudo ln -sf /etc/nginx/sites-available/example.conf /etc/nginx/sites-enabled/example.conf
sudo nginx -t

Reload Nginx to apply the changes:

sudo systemctl reload nginx

You can use the following command to run a quick benchmark:

sudo wrk -t2 -c50 -d30s http://127.0.0.1/

Record the requests and latency; we will compare them after tuning.

System-Level VPS Optimization

In this step, you can perform system tuning that is safe for most VPSs. You can adjust the number of files and network connections the system can handle, and the kernel’s TCP networking stack.

This prevents your server from becoming a bottleneck under load.

Tuning File Descriptors and Connection Limits

The default limit on open files and connections (file descriptors) on Linux is often too low for a busy web server. Nginx uses a file descriptor for each client connection, and if this limit is reached, the server will start refusing requests.

You can safely raise this global limit with the following command:

echo '* soft nofile 1048576
* hard nofile 1048576
root soft nofile 1048576
root hard nofile 1048576' | sudo tee /etc/security/limits.d/99-nofile.conf

Then, create the system configuration directory and set the system-wide file descriptor limit for services with the commands below:

sudo mkdir -p /etc/systemd/system.conf.d

echo '[Manager]
DefaultLimitNOFILE=1048576' | sudo tee /etc/systemd/system.conf.d/99-nofile.conf

Reload the system configuration to apply the changes:

sudo systemctl daemon-reexec

Optimizing Kernel TCP and Networking Parameters

The Linux kernel’s default network settings are designed for general-purpose use, not high-performance web serving. You can optimize it for a higher number of concurrent connections by increasing connection backlogs, enabling socket reuse to reduce TIME-WAIT states, and tuning TCP buffer sizes for better throughput.

The settings we use are kept moderate intentionally, as they work well on most VPS providers and still provide a clear performance boost.

sudo tee /etc/sysctl.d/99-tuned.conf >/dev/null <<'SYS'
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 16384

net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_syncookies = 1

net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1

net.ipv4.ip_local_port_range = 10240 65535

# Keepalive to detect dead peers
net.ipv4.tcp_keepalive_time = 120
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5

# Reduce TIME-WAIT pressure without being too aggressive
net.ipv4.tcp_rmem = 4096 131072 8388608
net.ipv4.tcp_wmem = 4096 131072 8388608
SYS

Reload the system settings to apply the changes:

sudo sysctl --system

PHP-FPM Performance Tuning

While system tuning helps your server run faster overall, most of the real performance boost happens when you optimize PHP-FPM. In this step, we’ll look at two key things:

  • Speeding up PHP execution with OPcache and JIT.
  • Adjusting the PHP-FPM process manager so it can handle your site’s traffic efficiently without wasting memory or CPU.

Setting these up correctly can make PHP run 2 to 5 times faster and keep your server’s performance much more stable.

PHP-FPM OPcache and JIT Configuration

In this step, you can set up OPcache to use memory efficiently and keep cached scripts longer, and also turn on the Just-In-Time (JIT) compiler for an extra speed boost by converting PHP code directly into machine code.

Open the PHP-FPM Ini file with the following command:

sudo nano /etc/php/8.3/fpm/php.ini

Add the following OPcache and JIT settings at the end of the file:

; ---- OPCACHE ----
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=65000
opcache.validate_timestamps=0
opcache.revalidate_freq=0
opcache.jit=1255
opcache.jit_buffer_size=64M

; ---- realpath ----
realpath_cache_size=4096k
realpath_cache_ttl=600

Also, find the lines below and change their values to:

expose_php=0
memory_limit=256M

PHP-FPM Process Manager Configuration

PHP-FPM’s process manager (pm) controls how PHP handles multiple requests at the same time. Picking the wrong mode can waste server resources or even cause crashes when traffic gets heavy.

In this step, we will explain three main process management methods, including on-demand, static, and dynamic. Additionally, you will learn an easy method to calculate the optimal number of PHP worker processes based on your server’s memory.

Choose Process Manager Models

Open the PHP-FPM pool configuration file with the command below:

sudo nano /etc/php/8.3/fpm/pool.d/www.conf

Choose one of the following process manager models and edit the file:

Option A: On-demand, which is good for sites with spiky traffic:

pm = ondemand
pm.max_children = (RAM_for_PHP / avg_php_rss)
pm.process_idle_timeout = 10s
pm.max_requests = 1000

Option B: Static with predictable RAM and max throughput:

pm = static
pm.max_children = N
pm.max_requests = 2000

Option C: Dynamic (Balanced approach):

pm = dynamic
pm.max_children = N
pm.start_servers = min(4, N/4)
pm.min_spare_servers = min(4, N/4)
pm.max_spare_servers = max(8, N/2)
pm.max_requests = 2000
How to Calculate Number of PHP Workers (N)?

1. Measure the average memory used by one PHP worker:

Install smem to measure memory:

sudo apt install smem -y
ps -o rss= -C php-fpm8.3 | awk '{sum+=$1} END {print "avg_kb=" (sum/NR)}'

For example, if the output shows avg_kb=60000, it means ~60 MB per PHP worker.

2. Decide how much RAM to give PHP:

For example, on an 8 GB VPS running only Nginx with PHP, reserve ~2.5 GB for OS, ~0.5 GB for Nginx, and 5 GB RAM for PHP.

3. Calculate N with:

N = floor(RAM_for_PHP_MB / avg_worker_MB)

For example, 5120 MB ÷ 60 MB ≈ 85 workers.

Start lower and increase only if the CPU is not maxed and the RAM is safe.

Other recommended settings in www.conf, you can adjust, include:

; Use the socket, not TCP, for lower latency
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Slowlog to catch slow scripts
request_slowlog_timeout = 2s
slowlog = /var/log/php8.3-fpm/slow.log

; Status page (enable for monitoring)
pm.status_path = /fpm-status

Once you are done, apply the changes by reloading PHP-FPM:

sudo systemctl reload php8.3-fpm

Nginx Global Performance Tuning

At this point, you can configure Nginx at a global level to handle high concurrency, implement smart caching strategies, and reduce latency through TCP optimizations and compression.

Open the Nginx configuration file with the command below:

sudo nano /etc/nginx/nginx.conf

Edit the file as shown below:

user www-data;
worker_processes auto;
worker_rlimit_nofile 1048576;

events {
    worker_connections 65535;
    multi_accept on;
    use epoll;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile      on;
    tcp_nopush    on;            # coalesce packets
    tcp_nodelay   on;
    aio           on;
    server_tokens off;

    keepalive_timeout  20s;
    keepalive_requests 1000;

    # Gzip (safe defaults). If you have zstd module, consider zstd too.
    gzip on;
    gzip_comp_level 5;
    gzip_min_length 1024;
    gzip_proxied any;
    gzip_types text/plain text/css application/json application/javascript application/xml \
               application/rss+xml text/javascript image/svg+xml;

    # Increase buffers for headers if needed
    client_max_body_size 32m;
    client_body_timeout 20s;
    send_timeout 20s;

    # Open file cache speeds up static file delivery
    open_file_cache max=20000 inactive=60s;
    open_file_cache_valid 120s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    # FastCGI defaults used across sites
    fastcgi_buffers 16 32k;
    fastcgi_buffer_size 64k;
    fastcgi_connect_timeout 3s;
    fastcgi_send_timeout 30s;
    fastcgi_read_timeout 30s;

    # FastCGI cache for microcaching (define a cache zone)
    fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=PHPFCGI:128m inactive=60m max_size=4g;

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

FastCGI Microcaching High-Impact Performance Gain

For dynamic PHP applications with heavy traffic, FastCGI microcaching provides the most performance improvements possible.

You can create a FastCGI cache configuration snippet that defines the smart caching rules:

sudo tee /etc/nginx/snippets/fastcgi-php.conf >/dev/null <<'NGINX'
# Common PHP-FPM params
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;

# Real IP (if behind a proxy/load balancer; adjust to your proxy CIDRs)
# set_real_ip_from  10.0.0.0/8;
# real_ip_header    X-Forwarded-For;

# Microcache: cache only GET/HEAD, bypass POST/authenticated users
set $no_cache 0;
if ($request_method = POST) { set $no_cache 1; }
if ($query_string != "")   { set $no_cache 1; }
if ($http_cookie ~* "wordpress_logged_in_|comment_author|PHPSESSID|laravel_session") { set $no_cache 1; }

fastcgi_cache_bypass $no_cache;
fastcgi_no_cache    $no_cache;

fastcgi_cache PHPFCGI;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 301 302 10s;   # cache OK/redirect for 10s
fastcgi_cache_valid 404 1s;            # avoid 404 storms
add_header X-Cache $upstream_cache_status always;

# Buffers and temp files
fastcgi_buffers 16 32k;
fastcgi_buffer_size 64k;

# Security/robustness
fastcgi_intercept_errors on;
NGINX

Then, integrate the cache into the Nginx server blocks by editing the /etc/nginx/sites-available/example.conf file:

server {
    listen 80 default_server reuseport;
    server_name _;
    root /var/www/example/public;
    index index.php;

    access_log /var/log/nginx/example.access.log;
    error_log  /var/log/nginx/example.error.log warn;

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

    location ~ \.php$ {
        include /etc/nginx/snippets/fastcgi-php.conf;
    }

    # Optional: don’t cache /admin or /wp-admin (example)
    location ^~ /admin {
        set $no_cache 1;
        try_files $uri /index.php?$args;
        location ~ \.php$ { include /etc/nginx/snippets/fastcgi-php.conf; }
    }
}

Once you are done, check for syntax errors and reload Nginx to apply the changes:

sudo nginx -t
sudo systemctl reload nginx

Maximizing PHP Performance: Safe Deployment with OPcache

Since you have set opcache.validate_timestamps=0 to get the best performance, PHP won’t automatically notice new code changes. That means you need a reliable way to deploy updates so new code runs right away.

This process safely clears OPcache and reloads PHP-FPM without losing the speed benefits:

# pull new code / rsync / composer install / build steps...
sudo -u www-data php -r 'opcache_reset();' 2>/dev/null || true
sudo systemctl reload php8.3-fpm
sudo systemctl reload nginx

Nginx Cache and PHP-FPM Health Monitoring

At this point, you must validate your tuning efforts, track cache effectiveness, and identify bottlenecks.

Use the following command to access the log that has an X-Cache header; check the cache hit rate:

grep 'X-Cache' /var/log/nginx/example.access.log | awk '{print $NF}' | sort | uniq -c

Then, enable the status in pool (pm.status_path=/fpm-status) and add the following settings:

location /fpm-status {
    allow 127.0.0.1;
    deny all;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}

Use the command below to view the slow log:

tail -f /var/log/php8.3-fpm/slow.log

VPS Performance Validation and Benchmarking

At this step, you can use the same tools from the baseline test and measure your optimizations.

Warm cache with a quick run:

wrk -t2 -c50 -d15s http://127.0.0.1/

Then measure with:

wrk -t4 -c200 -d60s http://127.0.0.1/

After applying the optimizations, you should notice:

  • More requests per second, especially for cached GET requests
  • Lower p95 latency on cached pages or endpoints
  • CPU usage stays below 80% at your target request rate
  • Memory usage stays within expected limits; you can check with htop or ps.

That’s it, you are done with optimizing Nginx and PHP-FPM on VPS for a faster experience.

FAQs

Should I use dynamic, static, or on-demand PHP-FPM pools?

Dynamic: Best for steady workloads with variable traffic; automatically adjusts workers.
Static: Fixed number of workers; ideal for high, predictable load.
Ondemand: Excellent for low or spiky traffic, saving memory.

How do I monitor PHP-FPM and Nginx performance?

For Nginx, use stub_status or log headers like X-Cache to track hits/misses.
For PHP-FPM, enable pm.status_path and slow logs to track worker utilization and slow scripts. Combine with tools like htop, iotop, or wrk for real-time metrics.

How do I safely deploy updates with OPcache enabled?

After deploying new PHP code, reset OPcache (opcache_reset()) and reload PHP-FPM. This ensures the new scripts are loaded without needing to enable timestamp checks in production.

Conclusion

Optimizing PHP-FPM and Nginx on VPS provides faster page loads, higher throughput, and stable resource usage. You just need to remember that every VPS and app is unique, you must start with safe and measured defaults, benchmark before and after changes.

With PerLod Hosting, you will get the best-tuned server stack and robust managed hosting.

We hope you enjoy this guide. Subscribe to our X and Facebook channels to get the latest articles on VPS performance optimization.

For further reading:

Building your own 3D render farm

Improve VPS Performance with the Best Solutions

Set Up a Private VPN Server on Your VPS with WireGuard

Post Your Comment

PerLod delivers high-performance hosting with real-time support and unmatched reliability.

Contact us

Payment methods

payment gateway
Perlod Logo
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.