Set up Nginx and PHP 8.4-FPM on a LAN server
Nginx and PHP 8.4 without a third-party repository
Debian 13 “Trixie” contains PHP 8.4 in its official repositories. A new installation in August 2026 therefore does not need the Sury repository. This keeps updates inside Debian's package and security model.
1. Install packages
sudo apt update
sudo apt install -y nginx php8.4-fpm php8.4-cli php8.4-curl \
php8.4-xml php8.4-mbstring php8.4-zip php8.4-gd php8.4-opcache
sudo systemctl enable --now nginx php8.4-fpm
Install php8.4-mysql only if the application uses MySQL or MariaDB.
2. Create the document root
sudo install -d -o www-data -g www-data -m 0750 /var/www/example/public
Use a short test file and remove it after validation:
<?php
header('Content-Type: text/plain; charset=utf-8');
echo "PHP works\n";
Do not leave a public phpinfo() page behind; it exposes extensive runtime information.
3. Configure the Nginx server block
server {
listen 80;
server_name server.example.lan;
root /var/www/example/public;
index index.php index.html;
client_max_body_size 20m;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
try_files $uri =404;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
}
location ~ /\. { deny all; }
}
sudo ln -s /etc/nginx/sites-available/example /etc/nginx/sites-enabled/example
sudo nginx -t
sudo systemctl reload nginx
Nginx documents client_max_body_size for http, server, and location; its default is 1m. The example scopes it to this server.
4. Align PHP limits
In /etc/php/8.4/fpm/php.ini:
upload_max_filesize = 20M
post_max_size = 22M
post_max_size must not be smaller than the file limit. Restart FPM after the change and account for any application-specific limit.
5. Size PHP-FPM from measurements
pm.max_children limits concurrent PHP requests. Do not calculate it from total RAM alone; the OS, database, and other services need memory too.
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 4
pm.max_requests = 500
These are conservative starting values, not a benchmark. Measure real worker memory and inspect FPM logs before raising them.
6. Choose TLS for the actual network
Let's Encrypt can issue publicly trusted certificates when the domain and ACME challenge can be validated. A private name such as server.example.lan cannot use the normal public HTTP challenge. Use an internal CA, another appropriate internal TLS design, or keep access inside a trusted LAN/VPN.
For a public domain that meets the requirements:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
Public exposure materially increases the attack surface and is outside a LAN-only design.
Troubleshooting
- Run
nginx -tbefore every reload. - Inspect
systemctl status nginx php8.4-fpmand the journal. - For
502 Bad Gateway, check the FPM service and socket path. - For HTTP 413, compare Nginx, PHP, and application limits.
php -vchecks the CLI; it does not prove which FPM socket Nginx uses.