How To

Install Nginx With PHP-FPM on Ubuntu 22.04

Nginx does not run PHP code itself. It hands every .php request to PHP-FPM (FastCGI Process Manager) over a Unix socket, so a working stack on Ubuntu is two services, one socket path and a location block that points at it. Nginx holds the same number of concurrent connections in a smaller memory footprint than Apache, which is why this website runs WordPress on it.

Original content from computingforgeeks.com - post 54633

The steps below install Nginx and PHP-FPM from the Ubuntu archive, connect them, serve a test page and confirm the PHP build is patched against PHP2Shell. Every command below was run on a fresh Ubuntu 22.04.5 LTS system in October 2026, with Nginx 1.18.0 and PHP 8.1.2-1ubuntu2.28 from jammy-security. Ubuntu 20.04 and 18.04 are past standard support and their free archives carry no PHP2Shell fix, so on a new server start from a current release with our Nginx and PHP-FPM guide for Ubuntu 24.04.

Features of Nginx Web Server

  • Content Cache: cache static and dynamic content
  • Load Balancing: HTTP, TCP, and UDP load balancing with Layer 7 request routing using URI, cookie, args, and more.
  • Reverse proxy multiple protocols: HTTP, gRPC, memcached, PHP‑FPM, SCGI, uwsgi
  • Handle hundreds of thousands of clients simultaneously
  • Stream HTTP video: FLV, HDS, HLS, MP4
  • HTTP/2 gateway with HTTP/2 server push support
  • Dual‑stack RSA/ECC SSL/TLS offload
  • Monitoring plugins: AppDynamics, Datadog, Dynatrace plug‑ins

Update Ubuntu

Before you begin, you should have a running Ubuntu server that has been updated and upgraded to the latest available packages.

sudo apt update
sudo apt upgrade

If the upgrade pulls in a new kernel, reboot before you continue so you are testing the stack on the system you will actually run.

Install Nginx Web Server

After the system is updated, proceed to install the Nginx package on Ubuntu 22.04:

sudo apt install nginx

The package starts and enables the service as part of the install, so you only need to confirm it is up before going further. A failed start here usually means something else already holds port 80:

systemctl status nginx --no-pager | head -n 3

The Active line should read active (running):

● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2026-10-22 19:38:34 UTC; 2s ago

Apache and Nginx can’t both listen on port 80. A clean Ubuntu server has no Apache, but if one is already running on this box, stop and disable it, then restart Nginx so it owns the port:

sudo systemctl disable --now apache2
sudo systemctl restart nginx

On a server without Apache the first command only prints Unit file apache2.service does not exist, which is harmless. If you need both web servers, move one of them to another port instead.

If you run UFW, allow HTTP and HTTPS so browsers can reach the server:

sudo ufw allow proto tcp from any to any port 80,443

UFW answers Rules updated for IPv4 and IPv6. The rule only takes effect while UFW is enabled, so if the server sits behind a cloud firewall instead, open both ports there. Our UFW commands reference covers enabling it without locking yourself out of SSH.

Install PHP-FPM

PHP-FPM runs PHP as its own service, and Nginx passes requests to it over a socket. Install it with the extensions most PHP applications expect. Leave the bare php package out of this list: on Ubuntu it pulls in Apache with its PHP module, and that Apache then fails to start because Nginx already holds port 80. That’s exactly what happened when we ran the old package list on a fresh server.

sudo apt update
sudo apt install php-fpm php-cli php-mysql php-zip php-gd php-mbstring php-curl php-xml php-pear php-bcmath

Ubuntu 22.04 installs PHP 8.1 and starts php8.1-fpm straight away. JSON support is built into PHP 8, so the old php-json package is not needed and you can leave it out. Confirm the service is running before you wire it to Nginx:

systemctl status php8.1-fpm --no-pager | head -n 3

Look for enabled on the Loaded line and active (running) on the next:

● php8.1-fpm.service - The PHP 8.1 FastCGI Process Manager
     Loaded: loaded (/lib/systemd/system/php8.1-fpm.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2026-10-22 19:39:29 UTC; 125ms ago

The PID and socket files live under /run/php/:

ls /run/php/

You should see three entries. php8.1-fpm.sock is the real socket, and php-fpm.sock is a symlink that Ubuntu points at the default PHP version’s socket through the alternatives system, so a config that uses it keeps working if you later switch PHP branches:

php-fpm.sock  php8.1-fpm.pid  php8.1-fpm.sock

If the directory is empty, PHP-FPM is not running. Check journalctl -u php8.1-fpm for the reason before you move on, because Nginx will only return 502 errors without that socket.

Configure PHP-FPM with Nginx

Nginx needs a location block that hands .php files to that socket. Keep the FastCGI settings in one reusable file so every site on the server can include it:

sudo vim /etc/nginx/php_fastcgi.conf

Paste these settings. The include must be fastcgi.conf, because Ubuntu’s fastcgi_params file doesn’t set SCRIPT_FILENAME. With fastcgi_params PHP-FPM can’t find the script and every PHP page returns File not found., which is what an earlier version of this snippet did in our test:

# 404
try_files $fastcgi_script_name =404;

# fastcgi_params plus SCRIPT_FILENAME
include fastcgi.conf;

# fastcgi settings
fastcgi_pass			unix:/run/php/php-fpm.sock;
fastcgi_index			index.php;
fastcgi_buffers			8 16k;
fastcgi_buffer_size		32k;
fastcgi_hide_header             X-Powered-By;
fastcgi_hide_header             X-CF-Powered-By;

Next, point a site at it. On a fresh server that is the default site, so open its file and you will replace what is there.

sudo vim /etc/nginx/sites-available/default

Replace its contents with this server block. It serves /var/www/html, tries index.php first and sends every .php request through the snippet:

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /var/www/html;
    index index.php index.html index.htm;
    server_name _;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include php_fastcgi.conf;
    }
}

Test the syntax before reloading, because a broken config would stop Nginx on the next restart:

sudo nginx -t
sudo systemctl reload nginx

Both lines of the test should end in syntax is ok and test is successful:

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

Now prove PHP runs through Nginx and not just on the command line. This one-line test page prints the PHP version and the interface that served it:

echo '<?php echo PHP_VERSION, " ", PHP_SAPI, PHP_EOL;' | sudo tee /var/www/html/info.php
curl -s http://localhost/info.php

The answer must end in fpm-fcgi. If you get the PHP source back, Nginx isn’t matching the .php location, or the request hit an old worker during the reload, so retry after a second before you change anything; if you get File not found., the snippet still includes fastcgi_params:

8.1.2-1ubuntu2.28 fpm-fcgi

Delete the test page once it works. Left in place, it tells anyone who requests it which PHP build you run, and you do not want to hand that to a scanner.

sudo rm /var/www/html/info.php

From here, drop your application into /var/www/html or create a server block per site that includes the same php_fastcgi.conf. Until an index.php or index.html exists there, a request for / returns 403, because this server block no longer lists Ubuntu’s index.nginx-debian.html welcome page. That 403 is expected and goes away with your first index file.

Check PHP-FPM for PHP2Shell (CVE-2026-109618, CVE-2026-109619)

PHP2Shell is a pair of critical remote code execution bugs in PHP itself, rated CVSS v4.0 9.2 in PHP’s 20 October pre-announcement and fixed upstream on 21 October 2026 in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 (PHP security advisory). Ubuntu 22.04 ships PHP 8.1, which gets no upstream release, so Ubuntu backported the fix into 8.1.2-1ubuntu2.28 in jammy-security. A server installed with this guide already has it. The catch is that php -v still prints 8.1.2 before and after the fix, so check the package changelog instead:

zgrep -E 'CVE-2026-10961[89]' /usr/share/doc/php8.1-common/changelog.Debian.gz

A patched build prints the patch and both CVE IDs. No output means your package is older than 8.1.2-1ubuntu2.28:

    - debian/patches/CVE-2026-109618_109619.patch: Avoid state corruption via
    - CVE-2026-109618
    - CVE-2026-109619

Minimal images often ship without /usr/share/doc changelogs. This check compares the installed version directly and works either way:

dpkg --compare-versions "$(dpkg-query -W -f='${Version}' php8.1-common)" ge 8.1.2-1ubuntu2.28 && echo patched || echo VULNERABLE

A fresh install stops here. On an existing server that prints VULNERABLE, upgrade every PHP 8.1 package and restart PHP-FPM yourself. Our test server was rolled back to the original 22.04 build first, and the package restarted FPM during the upgrade, but restart anyway: the running workers keep the old code in memory until they are replaced. If libapache2-mod-php8.1 is also installed, add it to the upgrade and restart apache2 too.

sudo apt update
sudo apt install --only-upgrade 'php8.1*'
sudo systemctl restart php8.1-fpm
systemctl show -p ActiveEnterTimestamp php8.1-fpm

The timestamp has to be later than the upgrade. Read the upgrade time from the dpkg log:

zgrep -hE ' (install|upgrade) php8.1-(common|fpm):' /var/log/dpkg.log* | sort | tail -1

On our test server the restart landed 18 seconds after the upgrade line, and the version check then printed patched:

ActiveEnterTimestamp=Thu 2026-10-22 19:41:41 UTC
2026-10-22 19:41:23 upgrade php8.1-common:amd64 8.1.2-1ubuntu2 8.1.2-1ubuntu2.28

Long-running PHP CLI processes (queue workers, anything under supervisor) also keep the old code in memory, so restart those as well. If you skip them, the web side is patched while your background jobs still run the vulnerable build. Servers on newer releases or the Ondrej PPA follow the same pattern with other package names, covered in our PHP2Shell patching guide for Ubuntu.

Ubuntu 20.04 and 18.04 are a different story. The free 20.04 archive stops at 7.4.3-4ubuntu2.29, which is not fixed, and Ubuntu publishes the patched builds only through Ubuntu Pro: 7.4.3-4ubuntu2.29+esm5 of php7.4-common on 20.04 and 7.2.24-0ubuntu0.18.04.17+esm14 of php7.2-common on 18.04. The Ondrej PHP PPA has no packages for focal or bionic any more, so it’s no way out either. With a Pro token (from your Ubuntu Pro dashboard), attach the server and upgrade PHP 7.4 on 20.04. We did not run these Pro steps in our lab:

sudo pro attach YOUR_PRO_TOKEN
sudo apt update && sudo apt install --only-upgrade 'php7.4*'
sudo systemctl restart php7.4-fpm

On 18.04 use 'php7.2*' and restart php7.2-fpm instead. Confirm the result with the same dpkg --compare-versions check shown above, using php7.4-common or php7.2-common and the threshold version for your release, and restart any CLI workers too.

If you can’t attach Pro yet, move the site to a supported release and reduce exposure in the meantime. PHP’s advisory calls such settings partial: patching remains the fix. Add these lines at the end of the FPM pool file, /etc/php/7.4/fpm/pool.d/www.conf on 20.04 or /etc/php/7.2/fpm/pool.d/www.conf on 18.04:

php_admin_flag[session.upload_progress.enabled] = off
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M

The post_max_size cap of 8M limits how much request data PHP decodes. If the application takes file uploads, raise it to 32M and keep upload_max_filesize below that value, otherwise larger uploads fail.

Restart PHP-FPM so every worker picks the values up, and set them in the pool or php.ini, not through ini_set() or .user.ini, which PHP’s advisory says not to rely on. Also check that no other pool file or per-directory setting puts a finite memory_limit back. Then confirm the values through a page served by Nginx, because php -r on the command line reads the CLI configuration and still reports upload progress as enabled. On our 8.1 test pool the page showed session.upload_progress.enabled=0, memory_limit=-1 and post_max_size=8M. An unlimited memory limit lets one request consume all RAM, so watch memory and restore the limit once the server is patched.

Similar guides, including the PHP 8.4 install guide for Ubuntu if you need a newer PHP branch than 8.1:

For reading beyond this guide, see our list of the best books to master web design.

Keep reading

Upgrade Ubuntu 24.04 to Ubuntu 26.04 LTS (Step by Step) Ubuntu Upgrade Ubuntu 24.04 to Ubuntu 26.04 LTS (Step by Step) UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Security UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Php PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Upgrade a Single Package on Ubuntu / Debian with apt Debian Upgrade a Single Package on Ubuntu / Debian with apt CapRover Docker Install on Debian 13: Deploy Your First App Containers CapRover Docker Install on Debian 13: Deploy Your First App Fix mkvirtualenv command not found on Ubuntu Ubuntu Fix mkvirtualenv command not found on Ubuntu

2 thoughts on “Install Nginx With PHP-FPM on Ubuntu 22.04”

Leave a Comment

Press ESC to close