Php

Install PHP on Arch Linux and Manjaro (PHP-FPM, Nginx, Extensions)

Arch Linux ships two PHP branches you can install together. The php package is the newest stable branch and php-legacy is the previous one, each with its own binaries, config directory and FPM socket, so you can keep current PHP for new projects and an older branch for an application that has not caught up. The part that trips you up is extensions: many of them ship with the main package but stay disabled, while others live in separate packages that do not enable themselves either.

Original content from computingforgeeks.com - post 7296

Below we install PHP, turn on the extensions a typical web app needs, serve it through PHP-FPM and nginx, and run php-legacy next to it. We tested on Arch Linux with PHP 8.5 and 8.3 from the extra repository in October 2026, and checked Manjaro’s stable branch, which carried the same packages a patch release or two behind.

Install PHP

The php package is the CLI plus the shared extension modules. It pulls in only three libraries on a base system:

sudo pacman -S php

The transaction is small, about 40 MiB installed, because the heavier extensions such as gd and the database drivers for PostgreSQL and SQLite come as separate packages you only add if you need them:

Packages (4) argon2-20190702-7  libzip-1.11.4-1  oniguruma-6.9.10-3  php-8.5.11-1
Total Installed Size:  40.62 MiB

Check the version, and notice that OPcache is already loaded. From PHP 8.5 it is built into the engine, so you no longer install or load it separately, though opcache.enable_cli still defaults to off for command-line scripts:

php -v

The CLI reports the branch Arch ships today. On a rolling release, php moves to the next minor branch through an ordinary pacman -Syu, so check this line after big upgrades and test your apps against it:

PHP 8.5.11 (cli) (built: Sep 23 2026 17:41:29) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.5.11, Copyright (c) Zend Technologies
    with Zend OPcache v8.5.11, Copyright (c), by Zend Technologies

On Manjaro the command is identical. Its stable branch had php 8.5.10-1 and php-legacy 8.3.33-1 when we checked, one or two patch releases behind Arch, because Manjaro holds packages in its testing branches before it releases them to stable. The package names are the same on both, so you can follow the steps below on Manjaro and expect the newer patch level to arrive there a little later.

Enable the extensions you need

A fresh install loads 34 extensions, which covers curl, mbstring, PDO, openssl, zip and the XML family. Several common extensions are compiled and shipped in the same package, but commented out in /etc/php/php.ini, so php -m will not list them until you turn them on:

  • intl, bcmath, iconv, gmp and exif
  • mysqli and pdo_mysql for MariaDB and MySQL
  • sockets, soap, ldap and calendar

Others are not in the main package at all. gd, pgsql, sqlite3, sodium and xsl each come from their own package, named php-gd, php-pgsql, php-sqlite and so on. Rather than editing php.ini, put your extensions in a drop-in file. The config steps use vim, which a minimal install lacks, so add it with sudo pacman -S vim if needed. PHP scans /etc/php/conf.d/, so your list survives a php.ini.pacnew after an upgrade:

sudo vim /etc/php/conf.d/20-extensions.ini

List one extension per line. The order does not matter for these, and keeping them in one file means you can see at a glance what this machine adds on top of the defaults:

extension=bcmath
extension=iconv
extension=intl
extension=mysqli
extension=pdo_mysql
extension=gd

Our first test of this file failed on the last line, because gd was not installed yet. PHP still starts, but it prints the warning on every command, including every Composer run and cron job:

PHP Warning:  PHP Startup: Unable to load dynamic library 'gd' (tried: /usr/lib/php/modules/gd (/usr/lib/php/modules/gd: cannot open shared object file: No such file or directory), /usr/lib/php/modules/gd.so (/usr/lib/php/modules/gd.so: cannot open shared object file: No such file or directory)) in Unknown on line 0

Install the package that provides it. php-gd brings in 16 dependencies, mostly image and AV1/HEIF codec libraries plus a few X11 ones, so it is the heaviest extension you are likely to add:

sudo pacman -S php-gd

The package installs only gd.so and no .ini file, which is why the extension=gd line is still your job. Confirm all six load now:

php -m | grep -E '^(bcmath|iconv|intl|mysqli|pdo_mysql|gd)$'

Each name prints once, with no warning above it. A warning at the top would mean one of the lines points at a module that still isn’t installed:

bcmath
gd
iconv
intl
mysqli
pdo_mysql

php --ini should list /etc/php/conf.d/20-extensions.ini under “Additional .ini files parsed”. If it does not, you’ll usually find the file name lacks the .ini suffix or sits in the wrong directory.

Serve PHP with PHP-FPM and nginx

The CLI is enough for scripts and Composer, but a web server talks to PHP through PHP-FPM, which runs a pool of workers behind a Unix socket. nginx can’t run PHP itself, so every .php request goes over that socket, and FPM’s pool settings decide how many run at once. Install both:

sudo pacman -S nginx php-fpm

Arch’s FPM pool already matches Arch’s nginx. Both run as the http user (UID 33), and the pool listens on a socket that user owns, so there is no socket ownership for you to fix:

grep -E '^(user|group|listen|listen\.owner|listen\.group|pm|pm\.max_children) ' /etc/php/php-fpm.d/www.conf

The defaults from the package work with nginx as shipped, so you don’t have to edit this file to get started:

user = http
group = http
listen = /run/php-fpm/php-fpm.sock
listen.owner = http
listen.group = http
pm = dynamic
pm.max_children = 5

pm.max_children = 5 caps the pool at five PHP requests at a time. That is fine for testing, but raise it before real traffic arrives: divide the RAM you can spare for PHP by the memory one worker uses, which you can read from ps once your app is running. Start FPM and confirm the socket exists:

sudo systemctl enable --now php-fpm
ls -l /run/php-fpm/

The socket is readable and writable only by http. That’s the user nginx runs as, and it’s why other local users can’t talk to FPM directly:

srw-rw---- 1 http http 0 Oct 16 22:06 php-fpm.sock

Arch’s nginx.conf has no include for extra config files, unlike Debian’s sites-enabled, so a file you drop into a directory does nothing until nginx.conf points at it. It also still has its own sample server on port 80, which we left alone. Create a conf.d directory, then add an include line inside the http block, right after include mime.types;:

sudo mkdir -p /etc/nginx/conf.d
sudo vim /etc/nginx/nginx.conf

Add these two lines below include mime.types;. The second one silences a warning that every nginx -t on a stock Arch config prints, could not build optimal types_hash:

    include       conf.d/*.conf;
    types_hash_max_size 4096;

Now create the server block for your app. Keeping each site in its own file under conf.d means you can disable one by renaming it, without touching nginx.conf again:

sudo vim /etc/nginx/conf.d/php-app.conf

We used port 8088 so it didn’t clash with the default server on port 80. Change listen and root for your site:

server {
    listen 8088;
    server_name _;
    root /srv/http/php-app;
    index index.php index.html;

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

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
        fastcgi_index index.php;
        include fastcgi.conf;
    }
}

fastcgi.conf already sets SCRIPT_FILENAME, so you do not need the separate fastcgi_param line older guides add. The try_files $uri =404 inside the PHP block stops nginx from passing requests for files that do not exist to PHP, so PHP never gets a script name it has to guess at. Put a test page in place:

sudo mkdir -p /srv/http/php-app
echo '<?php printf("PHP %s via %s, extensions: %d\n", PHP_VERSION, PHP_SAPI, count(get_loaded_extensions()));' | sudo tee /srv/http/php-app/index.php

Test the config before you start nginx, because a syntax error stops the service from starting at all, then request the page:

sudo nginx -t
sudo systemctl enable --now nginx
curl -s http://localhost:8088/

FPM answered with 41 extensions. Both read /etc/php/conf.d/, so the CLI counts 40 after the drop-in, and FPM adds one more of its own, the cgi-fcgi interface it talks to nginx through:

PHP 8.5.11 via fpm-fcgi, extensions: 41

curl -s http://localhost:8088/some/route returned the same line, so the try_files fallback routes unknown paths to index.php the way most frameworks expect. If the site is reachable from other machines and runs a firewall, open your port there too; our LEMP stack guide for Manjaro adds MariaDB and TLS on top of this setup.

PHP 8.5 served by php-fpm through nginx with php-legacy alongside on Arch Linux

The capture shows the CLI version, the drop-in file being parsed, both services active, the FPM page answering and php-legacy installed next to it.

Tune php.ini for web apps

The packaged php.ini uses conservative production defaults, and most CMS installers complain about at least two of them:

grep -E '^(memory_limit|upload_max_filesize|post_max_size|max_execution_time|date.timezone|;date.timezone|display_errors) ' /etc/php/php.ini

Uploads stop at 2 MB and the timezone is not set:

max_execution_time = 30
memory_limit = 128M
display_errors = Off
post_max_size = 8M
upload_max_filesize = 2M
;date.timezone =

Override them in another drop-in, such as /etc/php/conf.d/30-web.ini, with lines like upload_max_filesize = 64M, post_max_size = 72M and date.timezone = Africa/Nairobi. Keep post_max_size larger than upload_max_filesize, because a form post carries the file plus the other fields, and a post that exceeds the limit reaches your app with empty $_POST and $_FILES; PHP only logs a warning about it. Leave display_errors = Off on anything reachable from the network, since error pages leak paths and queries, and read errors from the journal instead: FPM’s own messages land in journalctl -u php-fpm, while errors from your scripts reach nginx and show up in journalctl -u nginx as FastCGI sent in stderr lines. FPM only reads the config at start, so run sudo systemctl restart php-fpm after any change, while the CLI picks it up on the next command.

Run php-legacy alongside PHP

php-legacy is the previous PHP branch, and it is built to coexist with php. Every binary and path carries a -legacy suffix, so nothing collides:

sudo pacman -S php-legacy php-legacy-fpm

Both versions now answer, each from its own binary, and plain php still points at the newest branch:

php-legacy -v | head -1
php -v | head -1

The CLI versions side by side:

PHP 8.3.35 (cli) (built: Sep 23 2026 17:51:22) (NTS)
PHP 8.5.11 (cli) (built: Sep 23 2026 17:41:29) (NTS)

Its config lives in /etc/php-legacy/, with its own conf.d, so extensions you enabled for php are not enabled here, and you repeat the drop-in under /etc/php-legacy/conf.d/. Extension packages are separate too, php-legacy-gd rather than php-gd. The FPM service and socket follow the same pattern:

sudo systemctl enable --now php-legacy-fpm

Enabling it created php-fpm-legacy.service, and php-legacy-fpm.service is an alias for that unit, so systemctl status may show either name. The pool listens on /run/php-fpm-legacy/php-fpm.sock. Point one site at the legacy branch by changing its fastcgi_pass line:

        fastcgi_pass unix:/run/php-fpm-legacy/php-fpm.sock;

After sudo systemctl reload nginx, our test page reported the older branch with its own extension set:

PHP 8.3.35 via fpm-fcgi, extensions: 32

Give the reload a second before you test. Our first curl straight after reload still reached the old workers and reported 8.5, because nginx starts new workers with the new configuration while the old ones finish their connections, and for a moment both sets answer. Switching back worked the same way, so a single site can move between branches with one line and a reload, without restarting FPM or touching the other sites on the machine. Composer comes from the same repository with sudo pacman -S composer, and it always runs on the main php, as composer --version shows with PHP version 8.5.11 (/usr/bin/php). Run php-legacy /usr/bin/composer for a project pinned to the older branch.

Fix 502 Bad Gateway from nginx

A 502 means nginx couldn’t reach PHP-FPM. We stopped php-fpm and the page returned 502 at once. On Arch you will not find the reason in /var/log/nginx/error.log, because the package builds nginx with error-log-path=stderr, so the errors go to the journal:

sudo journalctl -u nginx -o cat | grep php-fpm | tail -1

The log names the socket it tried, which tells you straight away whether nginx and FPM disagree about the path:

2026/10/16 22:06:54 [crit] 2659#2659: *5 connect() to unix:/run/php-fpm/php-fpm.sock failed (2: No such file or directory) while connecting to upstream, client: 127.0.0.1, server: _, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/run/php-fpm/php-fpm.sock:", host: "localhost:8088"

“No such file or directory” means FPM isn’t running or listens elsewhere. Start it, or check the listen line in the pool file against your fastcgi_pass. “Permission denied” on the same line means nginx’s user can’t open the socket. The socket’s owner comes from listen.owner, listen.group and listen.mode in the pool file, not from the pool’s user, so this happens when you change nginx’s user or those listen.* lines on one side only.

For the PHP side of other distributions, our PHP 8.5 on Ubuntu 26.04 and PHP on Fedora guides cover the same setup with each distribution’s packaging. On Arch, the pacman cheat sheet has the query commands for finding which package provides an extension, and any extension you can only find in the AUR needs an AUR helper like yay.

Keep reading

PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP Php PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP Install KVM and Virt-Manager on Arch Linux Virtualization Install KVM and Virt-Manager on Arch Linux Things to Do After Installing CachyOS Arch Linux Things to Do After Installing CachyOS CachyOS Kernel Guide: Kernel Manager, Schedulers and Tuning Arch Linux CachyOS Kernel Guide: Kernel Manager, Schedulers and Tuning 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) Fixing “/usr/bin/pacman: unrecognized option ‘–color never'” on Arch | Manjaro Arch Linux Fixing “/usr/bin/pacman: unrecognized option ‘–color never'” on Arch | Manjaro

Leave a Comment

Press ESC to close