How to Install PHP 8.3 + PHP-FPM on Ubuntu 24.04 VPS
PHP 8.3 ships with Ubuntu 24.04 "Noble" out of the box, but the version in the default repositories is frozen for the lifetime of the LTS release. If you want the latest point release, multiple PHP versions side by side, or fresh builds of common extensions like redis, imagick, and apcu, you need a third-party repository. The community standard is the Ondrej Surý PPA, maintained by a Debian PHP packager and trusted by millions of production servers. This guide walks you through installing PHP 8.3 + PHP-FPM on a clean Ubuntu 24.04 VPS, from PPA setup to a hardened, OpCache-tuned, Nginx-integrated deployment ready for WordPress, Laravel, Symfony, or any modern PHP application.
Prefer a managed stack? Our CloudCore Starter plan is pre-sized for typical PHP workloads with 4 vCPU, 8 GB RAM, and 100 GB NVMe. Deploy and SSH in under 60 seconds, then follow this guide to get a production-grade PHP-FPM pool running.
Table of Contents
What is PHP-FPM?
PHP-FPM (FastCGI Process Manager) is the modern, production-grade way to run PHP behind a web server. Instead of spawning a PHP interpreter per request (as with legacy mod_php on Apache), PHP-FPM keeps a pool of long-lived worker processes listening on a Unix socket or TCP port. When Nginx receives a request for a .php file, it forwards the request over the FastCGI protocol to one of these workers, which executes the script and returns the response.
This architecture solves several problems at once. Workers are reused across requests, which eliminates the fork/exec overhead per hit and lets OpCache keep compiled bytecode in shared memory between requests -- the single biggest performance win for PHP applications. Workers run as a dedicated user (typically www-data or a per-site user), which is cleaner to sandbox than embedding PHP in the web server. And because PHP-FPM is decoupled from the HTTP server, you can run multiple pools with different PHP versions, user accounts, and tuning parameters on the same box -- ideal for multi-tenant hosting, staging + production side by side, or migrating legacy apps one at a time.
PHP-FPM also exposes rich process-management primitives. A single pool can use pm=static (fixed worker count, lowest latency), pm=dynamic (elastic pool with min/max idle workers, best general-purpose choice), or pm=ondemand (workers spawn per request and die when idle, ideal for low-traffic sites). It ships with slow-request logging, per-pool status endpoints, emergency restart thresholds, and chroot support out of the box. Every serious PHP deployment runs it.
Why Use the Ondrej PPA?
The default Ubuntu 24.04 repositories include only PHP 8.3 and freeze it at the version that shipped with the LTS release. That means:
- No point releases -- if PHP 8.3.7 lands upstream with a security fix, you wait for Ubuntu's security team to backport it.
- No other versions -- you cannot install PHP 8.1, 8.2, or 8.4 alongside 8.3 without a third-party source. This is a hard blocker if you host legacy apps that haven't been certified on 8.3 yet, or if you want to try 8.4 in staging.
- Fewer extensions -- handy PECL extensions like
redis,imagick,apcu,msgpack, andswooleare either outdated or missing entirely.
.deb. Installing redis becomes apt install php8.3-redis instead of a PECL compile.It is the de-facto standard for production PHP on Debian and Ubuntu.
Prerequisites
Before you begin, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access
- SSH access to your server
- At least 2 GB of RAM for a single-site PHP-FPM pool with OpCache (4 GB+ recommended for real-world traffic)
- At least 10 GB of free disk space for PHP + extensions + application code
- Nginx already installed (or plan to install it in Step 8)
Recommended Plan: CloudCore Starter>
For most PHP workloads -- WordPress sites, Laravel apps, Symfony APIs, shared-hosting-replacement boxes -- the CloudCore Starter plan is the sweet spot:>
- 4 vCPU cores
- 8 GB RAM
- 100 GB NVMe SSD
- Unmetered bandwidth>
This gives you enough headroom for an Nginx + PHP-FPM + MariaDB stack with room for OpCache, Redis session storage, and 20-40 concurrent PHP workers before you need to scale up.
Connect to your server via SSH:
ssh root@your-server-ipStep 1: Update System Packages
Always start with a clean package index and applied security updates. This avoids dependency conflicts later when the PPA pulls in newer library versions.
sudo apt update && sudo apt upgrade -yExpected output (abbreviated):
Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease
Hit:2 http://archive.ubuntu.com/ubuntu noble-updates InRelease
Reading package lists... Done
Building dependency tree... Done
Calculating upgrade... Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.If the kernel was upgraded, reboot before continuing:
sudo rebootInstall the small set of utilities needed to add a PPA:
sudo apt install -y software-properties-common ca-certificates lsb-release apt-transport-https gnupgStep 2: Add the Ondrej PHP PPA
Add the PPA and refresh the package index:
sudo add-apt-repository ppa:ondrej/php -y
sudo apt updateExpected output:
Repository: 'deb https://ppa.launchpadcontent.net/ondrej/php/ubuntu/ noble main'
Description: Co-installable PHP versions: PHP 5.6, PHP 7.x and most requested extensions are included.
...
Adding repository.
Press [ENTER] to continue or Ctrl-c to cancel.
Adding deb entry to /etc/apt/sources.list.d/ondrej-ubuntu-php-noble.list
...
Hit:5 https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble InRelease
Reading package lists... DoneVerify PHP 8.3 is now available from the PPA rather than the stock Ubuntu archive:
apt-cache policy php8.3-fpmExpected output:
php8.3-fpm:
Installed: (none)
Candidate: 8.3.x-1+ubuntu24.04.1+deb.sury.org+1
Version table:
8.3.x-1+ubuntu24.04.1+deb.sury.org+1 500
500 https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble/main amd64 Packages
8.3.x-0ubuntu0.24.04.1 500
500 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 PackagesThe deb.sury.org version should be the candidate -- that confirms the PPA is taking precedence.
Step 3: Install PHP 8.3 + PHP-FPM + Extensions
Install PHP-FPM together with the extensions that 95% of real-world PHP apps need:
sudo apt install -y \
php8.3-fpm \
php8.3-cli \
php8.3-common \
php8.3-curl \
php8.3-gd \
php8.3-intl \
php8.3-mbstring \
php8.3-mysql \
php8.3-opcache \
php8.3-readline \
php8.3-soap \
php8.3-xml \
php8.3-zip \
php8.3-bcmath \
php8.3-imagick \
php8.3-redis \
php8.3-apcuWhat each package gives you:
php8.3-fpm-- The FastCGI Process Manager itself. Pulls inphp8.3-common.php8.3-cli-- The command-line interpreter. Needed forcomposer,artisan,wp-cli, and cron jobs.php8.3-curl-- HTTP client (curl_*functions, Guzzle, wp-cron outbound calls).php8.3-gd-- Image manipulation (thumbnails, WordPress uploads).php8.3-intl-- Unicode + internationalization (IntlDateFormatter,Normalizer, required by Symfony/Laravel).php8.3-mbstring-- Multi-byte string functions (UTF-8 handling).php8.3-mysql-- MySQL/MariaDB driver (PDO + mysqli).php8.3-opcache-- Bytecode cache. Huge performance win; always enable.php8.3-readline-- REPL support forphp -a.php8.3-soap-- SOAP client/server (still needed for many B2B integrations).php8.3-xml-- XML parsing (SimpleXML,DOMDocument,XMLReader).php8.3-zip-- ZIP archive handling (Composer, WordPress plugin uploads).php8.3-bcmath-- Arbitrary-precision math (payment processing, crypto).php8.3-imagick-- ImageMagick bindings. Higher-quality image processing than GD.php8.3-redis-- Redis client. Pair withsession.save_handler=redis.php8.3-apcu-- In-memory user cache (APCu). Useful for per-request caches and as a Doctrine cache backend.
Step 4: Verify the Installation
Confirm the PHP version:
php -vExpected output:
PHP 8.3.x (cli) (built: ... ) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.x, Copyright (c) Zend Technologies
with Zend OPcache v8.3.x, Copyright (c), by Zend TechnologiesCheck that PHP-FPM is running:
sudo systemctl status php8.3-fpmExpected output:
● php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager
Loaded: loaded (/lib/systemd/system/php8.3-fpm.service; enabled; preset: enabled)
Active: active (running) since Wed 2026-04-16 10:00:00 UTC; 1min ago
Main PID: 1234 (php-fpm8.3)
Status: "Processes active: 0, idle: 2, Requests: 0, slow: 0, Traffic: 0req/sec"
Tasks: 3 (limit: 9487)
Memory: 14.3M
CPU: 180ms
CGroup: /system.slice/php8.3-fpm.service
├─1234 "php-fpm: master process (/etc/php/8.3/fpm/php-fpm.conf)"
├─1235 "php-fpm: pool www"
└─1236 "php-fpm: pool www"List the enabled modules:
php -mYou should see curl, gd, intl, mbstring, mysqli, OPcache, redis, imagick, apcu and friends in the output.
Confirm the default Unix socket exists:
ls -l /run/php/Expected output:
srw-rw---- 1 www-data www-data 0 Apr 16 10:00 php8.3-fpm.sock
-rw-r--r-- 1 root root 4 Apr 16 10:00 php8.3-fpm.pidThe socket at /run/php/php8.3-fpm.sock is what Nginx will connect to in Step 8.
Step 5: Configure the PHP-FPM Pool
Each PHP-FPM pool is an independent process group with its own user, socket, and tuning parameters. The default pool is defined in /etc/php/8.3/fpm/pool.d/www.conf. Open it:
sudo nano /etc/php/8.3/fpm/pool.d/www.confKey directives to review and tune:
[www] user = www-data group = www-datalisten = /run/php/php8.3-fpm.sock listen.owner = www-data listen.group = www-data listen.mode = 0660
pm = dynamic pm.max_children = 20 pm.start_servers = 4 pm.min_spare_servers = 2 pm.max_spare_servers = 6 pm.max_requests = 500
pm.status_path = /fpm-status ping.path = /fpm-ping
request_terminate_timeout = 60s request_slowlog_timeout = 5s slowlog = /var/log/php8.3-fpm-slow.log
catch_workers_output = yes decorate_workers_output = no
Process Manager Modes
pm = dynamic(recommended default) -- PHP-FPM keeps betweenpm.min_spare_serversandpm.max_spare_serversidle workers ready, up to a hard ceiling ofpm.max_children. Elastic + cheap in RAM.pm = static-- A fixedpm.max_childrenworkers are always running. Lowest latency, predictable RAM usage, best for dedicated single-site servers under constant load.pm = ondemand-- Zero workers until a request arrives, then spawn on demand up topm.max_children. Idle workers are killed afterpm.process_idle_timeout. Best for low-traffic servers hosting many pools.
Calculating pm.max_children
This is the single most important tuning decision. The formula:
pm.max_children = (Total RAM - OS/other services) / avg PHP worker memoryOn an 8 GB VPS running Nginx + MariaDB + PHP-FPM:
- Reserve ~2 GB for OS + Nginx + MariaDB + buffers.
- Leave ~6 GB for PHP workers.
- Measure average PHP worker RSS under load. Typical values: WordPress ~80 MB, Laravel ~120 MB, Magento ~200 MB.
- For WordPress at 80 MB/worker:
6144 / 80 ≈ 76. Setpm.max_children = 60to leave headroom. - For Laravel at 120 MB/worker:
6144 / 120 ≈ 51. Setpm.max_children = 40.
ps -ylC php-fpm8.3 --sort:rss | awk '{sum+=$8; n++} END {print "avg RSS KB:", sum/n}'Why pm.max_requests = 500?
PHP workers accumulate memory over time (leaks in extensions, long-lived autoloaders). Recycling each worker after 500 requests keeps memory bounded without visibly hurting performance. Set it lower (100-200) if you see RSS growth in production.
Reload PHP-FPM after edits:
sudo systemctl reload php8.3-fpmStep 6: Tune php.ini for Web Apps
The PHP-FPM php.ini lives at /etc/php/8.3/fpm/php.ini. The CLI has its own at /etc/php/8.3/cli/php.ini -- remember to edit both if you want artisan or wp-cli to behave the same as the web stack.
Open the FPM config:
sudo nano /etc/php/8.3/fpm/php.iniRecommended changes for modern web apps:
; --- Resource limits --- memory_limit = 256M max_execution_time = 60 max_input_time = 60 max_input_vars = 3000; --- File uploads --- upload_max_filesize = 64M post_max_size = 64M file_uploads = On max_file_uploads = 20
; --- Error handling (production) --- display_errors = Off display_startup_errors = Off log_errors = On error_log = /var/log/php8.3-fpm-error.log
; --- Dates --- date.timezone = UTC
; --- Sessions on Redis --- session.save_handler = redis session.save_path = "tcp://127.0.0.1:6379?database=0" session.gc_maxlifetime = 86400 session.cookie_httponly = 1 session.cookie_secure = 1 session.cookie_samesite = "Lax"
; --- Hide PHP signature --- expose_php = Off
A few notes:
memory_limit--256Mis a sane default. WordPress + WooCommerce + a heavy plugin load may need512M. Laravel + Horizon jobs typically stay under256M.upload_max_filesizeandpost_max_size-- Must both be raised together.post_max_sizeshould be at least as large asupload_max_filesize.session.save_handler = redis-- Requires thephp8.3-redisextension and a running Redis server (sudo apt install redis-server). Moving sessions to Redis eliminates disk I/O and, crucially, lets you scale horizontally across multiple PHP-FPM servers.date.timezone-- PHP emits a warning on every request if this is unset.
sudo php-fpm8.3 -tExpected output:
[16-Apr-2026 10:00:00] NOTICE: configuration file /etc/php/8.3/fpm/php-fpm.conf test is successfulReload:
sudo systemctl reload php8.3-fpmStep 7: Enable and Tune OpCache (+ JIT)
OpCache compiles PHP source to bytecode on first request and caches the bytecode in shared memory, so every subsequent request skips the parse/compile step. On a typical WordPress or Laravel request this is a 3-5x speedup. PHP 8.3 additionally supports JIT compilation (tracing mode), which can deliver another 10-20% on CPU-bound code.
Open the OpCache config:
sudo nano /etc/php/8.3/fpm/conf.d/10-opcache.iniRecommended production settings:
opcache.enable=1 opcache.enable_cli=0 opcache.memory_consumption=256 opcache.interned_strings_buffer=16 opcache.max_accelerated_files=20000 opcache.max_wasted_percentage=10 opcache.validate_timestamps=0 opcache.revalidate_freq=0 opcache.save_comments=1 opcache.fast_shutdown=1
; JIT (PHP 8.0+, tracing mode is best for web apps) opcache.jit_buffer_size=128M opcache.jit=tracing
What each directive does:
opcache.enable=1-- Turn OpCache on.opcache.memory_consumption=256-- Shared memory (MB) for compiled bytecode. 256 MB fits most apps; Magento or large ERP codebases may need 512 MB.opcache.interned_strings_buffer=16-- Memory for deduplicated strings. 16 MB is safe; raise to 32 if you hit the limit.opcache.max_accelerated_files=20000-- Maximum number of PHP files to cache. Count your app:find /var/www/html -type f -name '*.php' | wc -l. Round up to next prime.opcache.validate_timestamps=0-- Production setting. OpCache never checks if source files changed, giving maximum performance. After a deploy, you must manually flush OpCache (see Troubleshooting). On staging/dev, set this to1andopcache.revalidate_freq=2.opcache.jit=tracing+opcache.jit_buffer_size=128M-- Enable tracing JIT with a 128 MB buffer. Tracing mode is optimized for long-running web apps.
sudo systemctl reload php8.3-fpm
php -r 'print_r(opcache_get_status(false));' 2>&1 | headYou can also drop a tiny status script at /var/www/html/opcache.php:
<?php phpinfo(); ?>and browse to http://your-server/opcache.php -- look for the "Zend OPcache" and "opcache.jit" sections. Delete this file immediately after checking -- leaving phpinfo() exposed on production is a real security risk.
Step 8: Connect Nginx to PHP-FPM via FastCGI
If Nginx is not installed yet:
sudo apt install -y nginx
sudo systemctl enable --now nginxFor a deeper Nginx walkthrough (TLS, HTTP/2, gzip, rate limits), see our companion guide How to Install Nginx on Ubuntu 24.04. For a full stack including MariaDB, follow How to Set Up a LEMP Stack on Ubuntu 24.04.
Create a site config at /etc/nginx/sites-available/php-app:
sudo tee /etc/nginx/sites-available/php-app > /dev/null <<'EOF' server { listen 80; server_name example.com www.example.com; root /var/www/html;index index.php index.html;
# Security: don't leak Nginx version server_tokens off;
# Max upload size must match php.ini post_max_size client_max_body_size 64m;
location / { try_files $uri $uri/ /index.php?$query_string; }
location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.3-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params;
# Timeouts (align with PHP-FPM request_terminate_timeout) fastcgi_read_timeout 60s; fastcgi_send_timeout 60s; fastcgi_connect_timeout 5s;
# Buffers (tune for large responses) fastcgi_buffers 16 16k; fastcgi_buffer_size 32k; }
# Deny access to hidden files and sensitive paths location ~ /\.(?!well-known) { deny all; } location ~* \.(env|ini|log|sh|sql)$ { deny all; } } EOF
Enable the site and reload:
sudo ln -s /etc/nginx/sites-available/php-app /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginxThe snippets/fastcgi-php.conf file is shipped by the Nginx package and contains safe defaults for fastcgi_split_path_info and security-critical fastcgi_param directives. Always include it rather than rolling your own.
Test end-to-end:
echo "<?php phpinfo();" | sudo tee /var/www/html/info.php
curl -I http://localhost/info.phpYou should see X-Powered-By: PHP/8.3.x in the response headers (unless you set expose_php = Off, in which case the header is absent and you can confirm via the rendered phpinfo() page instead). Delete info.php after verifying.
Step 9: Run Multiple PHP Versions Side by Side
One of the killer features of the Ondrej PPA is co-installable PHP versions. You can run PHP 8.1 for a legacy app, PHP 8.3 for your main site, and PHP 8.4 for a staging project -- all on the same server, each with its own FPM pool and socket.
Install an additional version, for example PHP 8.1:
sudo apt install -y php8.1-fpm php8.1-cli php8.1-mysql php8.1-curl php8.1-mbstring php8.1-xml php8.1-zip php8.1-opcacheThis creates a fully independent php8.1-fpm systemd service, its own config tree at /etc/php/8.1/, and a distinct socket at /run/php/php8.1-fpm.sock.
Switch the CLI Default with update-alternatives
The php command on the CLI is managed via update-alternatives:
sudo update-alternatives --config phpExpected output:
There are 2 choices for the alternative php (providing /usr/bin/php).Selection Path Priority Status ------------------------------------------------------------
1 /usr/bin/php8.1 81 manual mode 2 /usr/bin/php8.3 83 manual mode
- 0 /usr/bin/php8.3 83 auto mode
Press <enter> to keep the current choice[*], or type selection number:
Pick the version you want as the default php binary. Note that this only affects the CLI -- each FPM pool continues to use its own version based on the socket Nginx points at.
Route Each Site to a Specific Version
In your Nginx site config, choose the socket:
# Legacy app on PHP 8.1
server {
server_name legacy.example.com;
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
include snippets/fastcgi-php.conf;
}
}Main app on PHP 8.3
server {
server_name example.com;
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include snippets/fastcgi-php.conf;
}
}Per-Version Pool Tuning
Each version has its own pool.d/www.conf at /etc/php/<version>/fpm/pool.d/www.conf. You can also create multiple pools within a single version for per-site isolation. Copy www.conf to site1.conf, change [www] to [site1], pick a unique socket path (listen = /run/php/php8.3-site1.sock), set a dedicated user and group, and reload:
sudo systemctl reload php8.3-fpmThis pattern is how multi-tenant PHP hosting is built -- one pool per customer, each running as its own Unix user, each with its own pm.max_children and php_admin_value[memory_limit] overrides.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Nginx returns 502 Bad Gateway | PHP-FPM is not running, the socket path is wrong, or FPM crashed under load | Check service: sudo systemctl status php8.3-fpm. Verify socket: ls -l /run/php/. Tail error log: sudo tail -f /var/log/nginx/error.log. Raise pm.max_children if the log shows "server reached pm.max_children setting". |
| 504 Gateway Timeout | Script ran longer than fastcgi_read_timeout or max_execution_time | Raise both. Investigate slow queries via slowlog at /var/log/php8.3-fpm-slow.log. |
Call to undefined function for a built-in | Extension missing or not enabled for this SAPI | php -m \</td><td>grep <ext><code> to confirm. Install: </code>sudo apt install php8.3-<ext><code>. Reload FPM: </code>sudo systemctl reload php8.3-fpm. |
| Code changes not visible after deploy | OpCache has validate_timestamps=0 and is serving stale bytecode | Reload FPM: sudo systemctl reload php8.3-fpm, or call opcache_reset() from a privileged endpoint, or use cachetool: cachetool opcache:reset --fcgi=/run/php/php8.3-fpm.sock. |
WARNING: [pool www] server reached pm.max_children setting (20), consider raising it in FPM log | Too much concurrent traffic for the configured worker pool | Raise pm.max_children (see formula in Step 5). If RAM is already saturated, move to a bigger plan or add a second server behind a load balancer. |
Uncaught RedisException: Connection refused | Redis server not installed/running | sudo apt install -y redis-server && sudo systemctl enable --now redis-server. Test: redis-cli ping should return PONG. |
Allowed memory size of X bytes exhausted | memory_limit too low for this script | Raise memory_limit in php.ini, or override per-pool with php_admin_value[memory_limit] = 512M in the pool config. |
Too many open connections / connect() to unix:/run/php/php8.3-fpm.sock failed (11: Resource temporarily unavailable) | Socket backlog exhausted under burst traffic | Add listen.backlog = 4096 to the pool config. Check kernel net.core.somaxconn and raise if needed: sudo sysctl -w net.core.somaxconn=4096. |
imagick or redis extension loads on CLI but not FPM | Extension enabled for cli SAPI only | Check: ls /etc/php/8.3/fpm/conf.d/ \</td><td>grep <ext><code>. Enable: </code>sudo phpenmod -s fpm <ext> then reload FPM. |
Viewing Logs
- FPM process errors:
sudo journalctl -u php8.3-fpm -f - PHP errors:
sudo tail -f /var/log/php8.3-fpm-error.log - Slow requests:
sudo tail -f /var/log/php8.3-fpm-slow.log - Nginx access/error:
sudo tail -f /var/log/nginx/access.log /var/log/nginx/error.log
FAQ
Should I use a Unix socket or a TCP port for PHP-FPM?
For single-server deployments where Nginx and PHP-FPM are on the same machine, use a Unix socket (/run/php/php8.3-fpm.sock). It's slightly faster (no TCP/IP stack overhead) and is automatically protected by filesystem permissions. For multi-server setups where Nginx is on a separate load-balancer box, use TCP (listen = 127.0.0.1:9000 on the PHP server, fastcgi_pass 10.0.0.5:9000 on Nginx) and bind to a private network interface. Never expose FPM on a public IP.
What's the difference between pm=dynamic and pm=ondemand?
pm=dynamic keeps a pool of idle workers warm, ready to serve requests instantly -- best for any site with steady traffic. pm=ondemand spawns workers on demand and kills them when idle, saving RAM on low-traffic sites at the cost of a few-millisecond cold-start penalty on the first request after idle. Use ondemand when you host many low-traffic sites on one box (multi-tenant hosting, staging environments) and dynamic for anything with consistent load.
How do I reload OpCache after a deploy without reloading PHP-FPM?
Three options: (1) the simplest is sudo systemctl reload php8.3-fpm, which is a graceful no-downtime reload. (2) Call opcache_reset() from a protected admin endpoint in your app. (3) Install cachetool and run cachetool opcache:reset --fcgi=/run/php/php8.3-fpm.sock from your deploy script -- this talks directly to FPM via FastCGI without any app-level code.
Can I use the Ondrej PPA alongside the stock Ubuntu PHP packages?
Technically yes, but avoid it. Pin priorities get complicated, and a future apt upgrade can silently cross-replace packages. The cleanest approach is: once you add the PPA, let it own every PHP package on the box. If you need to remove the PPA later, use ppa-purge to cleanly downgrade back to Ubuntu packages.
Is PHP-FPM enough for high-traffic WordPress / Laravel, or do I need more?
PHP-FPM + OpCache + JIT gets you very far. For most sites up to ~1M requests/day on a single VPS, tuning the pool size and enabling a full-page cache (WP Super Cache, Nginx FastCGI cache, Varnish in front) matters more than the PHP stack itself. Add Redis for object/session caching, a CDN for static assets, and horizontal scaling behind a load balancer only when you actually hit CPU or concurrency limits on a single box.
Next Steps
Now that PHP 8.3 + PHP-FPM is running on your VPS, here are the natural next moves:
- Install MariaDB or MySQL to complete a LEMP stack -- follow our How to Install MariaDB on Ubuntu 24.04 guide, then tie it all together with the LEMP Stack install guide.
- Add free SSL with Let's Encrypt --
sudo apt install -y certbot python3-certbot-nginx && sudo certbot --nginxgets you HTTPS in under a minute. See How to Set Up Let's Encrypt on Nginx. - Install Composer for Laravel / Symfony / modern PHP apps --
curl -sS https://getcomposer.org/installer | php && sudo mv composer.phar /usr/local/bin/composer. - Tune Nginx FastCGI caching -- cache rendered PHP output at the edge for 10x throughput on cacheable pages.
- Monitor PHP-FPM -- enable the
pm.status_pathendpoint (already set in Step 5) and scrape it withphp-fpm_exporterinto Prometheus + Grafana for per-pool metrics (active workers, slow requests, queue depth). - Read the upstream docs -- php.net/manual/en/install.fpm.php for the authoritative FPM reference, and the Ondrej PPA repo at github.com/oerdnj/deb.sury.org for packaging details and the latest version matrix.
Run PHP 8.3 on CloudCore Starter -- EUR-priced, NVMe-fast>
Our CloudCore Starter plan is the sweet spot for modern PHP workloads:>
- 4 vCPU cores, 8 GB RAM, 100 GB NVMe SSD
- Ubuntu 24.04 LTS pre-installed
- Full root SSH access from minute one
- Unmetered bandwidth
- Deploy in under 60 seconds>
Launch a CloudCore VPS and follow this guide end to end -- you'll have a production-grade PHP-FPM stack running before your coffee gets cold.