Set up PHP-FPM with NGINX as a reverse proxy and secure it with Let's Encrypt SSL certificates. This configuration provides high performance for PHP applications with proper process isolation and automatic HTTPS.
Prerequisites
- Root or sudo access
- Domain name pointing to your server
- Open ports 80 and 443
What this solves
PHP-FPM (FastCGI Process Manager) with NGINX reverse proxy creates a high-performance setup for PHP applications. This configuration separates web serving from PHP processing, improving security and performance compared to traditional Apache mod_php setups.
You need this when running PHP applications that require better performance, process isolation, or when you want to serve static files efficiently while processing PHP requests separately.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions of all components.
sudo apt update && sudo apt upgrade -y
Install NGINX and PHP-FPM
Install NGINX web server and PHP-FPM with essential PHP extensions for web applications.
sudo apt install -y nginx php8.3-fpm php8.3-mysql php8.3-curl php8.3-gd php8.3-mbstring php8.3-xml php8.3-zip
Install Certbot for SSL certificates
Install Certbot to automatically obtain and manage Let's Encrypt SSL certificates.
sudo apt install -y certbot python3-certbot-nginx
Configure PHP-FPM pool
Create a dedicated PHP-FPM pool for your application with optimized settings for performance and security.
sudo cp /etc/php/8.3/fpm/pool.d/www.conf /etc/php/8.3/fpm/pool.d/www.conf.backup
[www]
user = www-data
group = www-data
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500
php_admin_value[error_log] = /var/log/fpm-php.www.log
php_admin_flag[log_errors] = on
php_value[session.save_handler] = files
php_value[session.save_path] = /var/lib/php/sessions
php_value[soap.wsdl_cache_dir] = /var/lib/php/wsdlcache
security.limit_extensions = .php .php3 .php4 .php5 .php7 .php8
Configure NGINX server block
Create an NGINX server block that will act as a reverse proxy to PHP-FPM for PHP requests while serving static files directly.
sudo mkdir -p /var/www/example.com/public
sudo chown -R www-data:www-data /var/www/example.com
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.php index.html index.htm;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
# Handle static files
location ~* \.(css|gif|ico|jpeg|jpg|js|png|svg|webp|woff|woff2|ttf|eot)$ {
expires 1M;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# PHP-FPM reverse proxy
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# Security settings
fastcgi_param HTTP_PROXY "";
fastcgi_read_timeout 300;
}
# Deny access to sensitive files
location ~ /\. {
deny all;
}
location ~ ~$ {
deny all;
}
# Main location block
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Logging
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
}
Enable the site and test configuration
Enable the NGINX site and test the configuration for syntax errors before restarting services.
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
Create a test PHP file
Create a simple PHP file to test that PHP-FPM is working correctly with NGINX.
sudo chown www-data:www-data /var/www/example.com/public/info.php
sudo chmod 644 /var/www/example.com/public/info.php
Start and enable services
Start both PHP-FPM and NGINX services and enable them to start automatically on boot.
sudo systemctl enable --now php8.3-fpm
sudo systemctl enable --now nginx
sudo systemctl status php8.3-fpm
sudo systemctl status nginx
Obtain SSL certificate
Use Certbot to automatically obtain and configure SSL certificates from Let's Encrypt.
sudo certbot --nginx -d example.com -d www.example.com
Follow the prompts to enter your email and agree to terms of service. Certbot will automatically modify your NGINX configuration to include SSL settings.
Configure automatic certificate renewal
Set up automatic renewal for SSL certificates to ensure they don't expire.
sudo systemctl enable --now certbot.timer
sudo systemctl status certbot.timer
Test the renewal process to ensure it works correctly.
sudo certbot renew --dry-run
Performance optimization
Optimize PHP-FPM settings
Fine-tune PHP-FPM for better performance based on your server's resources.
memory_limit = 256M
max_execution_time = 300
max_input_vars = 3000
upload_max_filesize = 64M
post_max_size = 64M
max_file_uploads = 20
; OpCache settings
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=2
opcache.fast_shutdown=1
Configure NGINX performance settings
Optimize NGINX for better performance with caching and connection handling.
user www-data;
worker_processes auto;
worker_rlimit_nofile 65535;
events {
multi_accept on;
worker_connections 1024;
use epoll;
}
http {
charset utf-8;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
log_not_found off;
types_hash_max_size 2048;
client_max_body_size 16M;
# MIME
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
# Gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
include /etc/nginx/sites-enabled/*;
}
Security hardening
Secure PHP-FPM configuration
Apply additional security settings to PHP-FPM to prevent common vulnerabilities.
; Add these security settings to your existing pool configuration
php_admin_flag[allow_url_fopen] = off
php_admin_flag[allow_url_include] = off
php_admin_flag[enable_dl] = off
php_admin_flag[expose_php] = off
php_admin_flag[display_errors] = off
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen
Configure firewall rules
Set up UFW firewall to allow only necessary ports for web traffic.
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
sudo ufw status
Set up log monitoring
Configure log rotation and basic monitoring for PHP-FPM and NGINX logs.
/var/log/nginx/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0644 www-data adm
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 cat /var/run/nginx.pid
fi
endscript
}
/var/log/fpm-php.www.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0644 www-data adm
postrotate
systemctl reload php8.3-fpm
endscript
}
Restart services with new configuration
Apply all configuration changes by restarting both services.
sudo systemctl restart php8.3-fpm
sudo systemctl restart nginx
Verify your setup
Test that PHP-FPM is working correctly with NGINX and SSL is properly configured.
sudo systemctl status php8.3-fpm
sudo systemctl status nginx
curl -I https://example.com
curl -k https://example.com/info.php | grep "PHP Version"
Check SSL certificate status and configuration.
sudo certbot certificates
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | openssl x509 -noout -dates
Monitor PHP-FPM pool status and performance.
sudo tail -f /var/log/fpm-php.www.log
sudo tail -f /var/log/nginx/example.com.access.log
/var/www/example.com/public/info.php after testing as it exposes system information.Common issues
| Symptom | Cause | Fix |
|---|---|---|
| 502 Bad Gateway error | PHP-FPM not running or socket permissions | sudo systemctl restart php8.3-fpm and check socket permissions |
| PHP files download instead of executing | NGINX not configured to pass PHP to FPM | Check fastcgi_pass directive points to correct socket |
| SSL certificate not renewing | Certbot timer not running | sudo systemctl enable --now certbot.timer |
| Permission denied errors | Wrong file ownership or permissions | sudo chown -R www-data:www-data /var/www/example.com |
| High memory usage | Too many PHP-FPM processes | Adjust pm.max_children in pool configuration |
| Slow PHP execution | OpCache disabled or misconfigured | Enable and tune OpCache settings in php.ini |
Next steps
- Configure PHP Composer for dependency management and autoloading
- Set up PHP application performance monitoring with APM tools and real-time metrics collection
- Configure NGINX virtual hosts with SSL certificates for multiple domains
- Optimize PHP-FPM performance with memory tuning and connection pooling for high-traffic websites
- Configure NGINX rate limiting and DDoS protection with advanced security rules
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Usage function
usage() {
echo "Usage: $0 <domain>"
echo "Example: $0 example.com"
exit 1
}
# Error handling
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Rolling back changes...${NC}"
systemctl stop nginx php-fpm 2>/dev/null || true
rm -f "$NGINX_SITE_CONFIG" 2>/dev/null || true
rm -f "$PHP_FPM_POOL_CONFIG.backup" 2>/dev/null || true
}
trap cleanup ERR
# Check arguments
if [ $# -ne 1 ]; then
usage
fi
DOMAIN="$1"
# Check if running as root or with sudo
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}[ERROR] This script must be run as root or with sudo${NC}"
exit 1
fi
# Auto-detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
PHP_VERSION="8.3"
PHP_FPM_SERVICE="php8.3-fpm"
PHP_FPM_POOL_DIR="/etc/php/8.3/fpm/pool.d"
PHP_FPM_SOCK="/run/php/php8.3-fpm.sock"
NGINX_SITES_DIR="/etc/nginx/sites-available"
NGINX_SITES_ENABLED="/etc/nginx/sites-enabled"
WEB_USER="www-data"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
PHP_VERSION=""
PHP_FPM_SERVICE="php-fpm"
PHP_FPM_POOL_DIR="/etc/php-fpm.d"
PHP_FPM_SOCK="/run/php-fpm/www.sock"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_SITES_ENABLED=""
WEB_USER="nginx"
;;
fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
PHP_VERSION=""
PHP_FPM_SERVICE="php-fpm"
PHP_FPM_POOL_DIR="/etc/php-fpm.d"
PHP_FPM_SOCK="/run/php-fpm/www.sock"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_SITES_ENABLED=""
WEB_USER="nginx"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
PHP_VERSION=""
PHP_FPM_SERVICE="php-fpm"
PHP_FPM_POOL_DIR="/etc/php-fpm.d"
PHP_FPM_SOCK="/run/php-fpm/www.sock"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_SITES_ENABLED=""
WEB_USER="nginx"
;;
*)
echo -e "${RED}[ERROR] Unsupported distribution: $ID${NC}"
exit 1
;;
esac
else
echo -e "${RED}[ERROR] Cannot detect distribution${NC}"
exit 1
fi
# Set config paths
if [ -n "$NGINX_SITES_ENABLED" ]; then
NGINX_SITE_CONFIG="$NGINX_SITES_DIR/$DOMAIN"
else
NGINX_SITE_CONFIG="$NGINX_SITES_DIR/$DOMAIN.conf"
fi
PHP_FPM_POOL_CONFIG="$PHP_FPM_POOL_DIR/www.conf"
echo -e "${GREEN}[1/8] Updating system packages...${NC}"
$PKG_UPDATE
echo -e "${GREEN}[2/8] Installing NGINX and PHP-FPM...${NC}"
if [ "$PKG_MGR" = "apt" ]; then
$PKG_INSTALL nginx php8.3-fpm php8.3-mysql php8.3-curl php8.3-gd php8.3-mbstring php8.3-xml php8.3-zip
else
$PKG_INSTALL nginx php-fpm php-mysqlnd php-curl php-gd php-mbstring php-xml php-zip
fi
echo -e "${GREEN}[3/8] Installing Certbot for SSL certificates...${NC}"
$PKG_INSTALL certbot python3-certbot-nginx
echo -e "${GREEN}[4/8] Configuring PHP-FPM pool...${NC}"
cp "$PHP_FPM_POOL_CONFIG" "$PHP_FPM_POOL_CONFIG.backup"
cat > "$PHP_FPM_POOL_CONFIG" << EOF
[www]
user = $WEB_USER
group = $WEB_USER
listen = $PHP_FPM_SOCK
listen.owner = $WEB_USER
listen.group = $WEB_USER
listen.mode = 0660
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500
php_admin_value[error_log] = /var/log/fpm-php.www.log
php_admin_flag[log_errors] = on
php_value[session.save_handler] = files
php_value[session.save_path] = /var/lib/php/sessions
php_value[soap.wsdl_cache_dir] = /var/lib/php/wsdlcache
security.limit_extensions = .php .php3 .php4 .php5 .php7 .php8
EOF
echo -e "${GREEN}[5/8] Creating web directory and setting permissions...${NC}"
mkdir -p "/var/www/$DOMAIN/public"
chown -R "$WEB_USER:$WEB_USER" "/var/www/$DOMAIN"
chmod 755 "/var/www/$DOMAIN"
chmod 755 "/var/www/$DOMAIN/public"
echo -e "${GREEN}[6/8] Configuring NGINX server block...${NC}"
cat > "$NGINX_SITE_CONFIG" << EOF
server {
listen 80;
server_name $DOMAIN www.$DOMAIN;
root /var/www/$DOMAIN/public;
index index.php index.html index.htm;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
# Handle static files
location ~* \.(css|gif|ico|jpeg|jpg|js|png|svg|webp|woff|woff2|ttf|eot)$ {
expires 1M;
add_header Cache-Control "public, immutable";
try_files \$uri =404;
}
# PHP-FPM reverse proxy
location ~ \.php$ {
try_files \$uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:$PHP_FPM_SOCK;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
include fastcgi_params;
# Security settings
fastcgi_param HTTP_PROXY "";
fastcgi_read_timeout 300;
}
# Deny access to sensitive files
location ~ /\. {
deny all;
}
location ~ ~$ {
deny all;
}
# Main location block
location / {
try_files \$uri \$uri/ /index.php?\$query_string;
}
# Logging
access_log /var/log/nginx/$DOMAIN.access.log;
error_log /var/log/nginx/$DOMAIN.error.log;
}
EOF
echo -e "${GREEN}[7/8] Enabling site and starting services...${NC}"
# Enable site for Debian-based systems
if [ -n "$NGINX_SITES_ENABLED" ]; then
ln -sf "$NGINX_SITE_CONFIG" "$NGINX_SITES_ENABLED/$DOMAIN"
fi
# Test NGINX configuration
nginx -t
# Start and enable services
systemctl enable nginx "$PHP_FPM_SERVICE"
systemctl restart "$PHP_FPM_SERVICE"
systemctl restart nginx
# Configure firewall if available
if command -v ufw >/dev/null 2>&1; then
ufw allow 'Nginx Full' 2>/dev/null || true
elif command -v firewall-cmd >/dev/null 2>&1; then
firewall-cmd --permanent --add-service=http 2>/dev/null || true
firewall-cmd --permanent --add-service=https 2>/dev/null || true
firewall-cmd --reload 2>/dev/null || true
fi
echo -e "${GREEN}[8/8] Creating test PHP file...${NC}"
cat > "/var/www/$DOMAIN/public/index.php" << 'EOF'
<?php
phpinfo();
?>
EOF
chown "$WEB_USER:$WEB_USER" "/var/www/$DOMAIN/public/index.php"
chmod 644 "/var/www/$DOMAIN/public/index.php"
echo -e "${GREEN}Installation completed successfully!${NC}"
echo -e "${YELLOW}Next steps:${NC}"
echo "1. Point your domain $DOMAIN to this server's IP address"
echo "2. Run: certbot --nginx -d $DOMAIN -d www.$DOMAIN"
echo "3. Test your setup at: http://$DOMAIN"
echo "4. Replace the test PHP file with your application"
echo ""
echo -e "${YELLOW}Service status:${NC}"
systemctl is-active nginx && echo -e "NGINX: ${GREEN}Active${NC}" || echo -e "NGINX: ${RED}Inactive${NC}"
systemctl is-active "$PHP_FPM_SERVICE" && echo -e "PHP-FPM: ${GREEN}Active${NC}" || echo -e "PHP-FPM: ${RED}Inactive${NC}"
Review the script before running. Execute with: bash install.sh