Nginx + PHP-FPM cheatsheet.
Basic config
server {
listen 80;
server_name example.com;
root /var/www/html/public;
index index.php;
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;
# Or: fastcgi_pass 127.0.0.1:9000;
}
location ~ /\.ht { deny all; }
}
fastcgi-php.conf snippet
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
try_files $fastcgi_script_name =404;
Laravel
server {
listen 80;
server_name app.example.com;
root /var/www/app/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ ^/index\.php(/|$) {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
internal;
}
location ~ \.php$ { return 404; }
}
Only index.php is served as PHP; prevents direct script execution.
WordPress
server {
root /var/www/wordpress;
index index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
# WP-specific
location = /favicon.ico { log_not_found off; access_log off; }
location = /robots.txt { log_not_found off; access_log off; allow all; }
location ~* \.(css|js|jpg|png|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Block xmlrpc
location = /xmlrpc.php { deny all; }
# Deny PHP in uploads
location ~* /wp-content/uploads/.*\.php$ {
deny all;
}
}
PHP-FPM pool
/etc/php/8.3/fpm/pool.d/www.conf:
[www]
user = www-data
group = www-data
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 500
request_terminate_timeout = 60s
catch_workers_output = yes
clear_env = no
opcache (php.ini)
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0 ; prod: never check; reload to update
opcache.jit_buffer_size=64M
opcache.jit=tracing
In dev, set validate_timestamps=1.
fastcgi_cache
fastcgi_cache_path /var/cache/nginx/fcgi
levels=1:2
keys_zone=fcgi:50m
max_size=1g
inactive=60m;
server {
set $skip_cache 0;
if ($request_method = POST) { set $skip_cache 1; }
if ($query_string != "") { set $skip_cache 1; }
if ($request_uri ~* "/wp-admin/|/wp-login.php") { set $skip_cache 1; }
if ($http_cookie ~* "wordpress_logged_in") { set $skip_cache 1; }
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_cache fcgi;
fastcgi_cache_key "$scheme$host$request_uri";
fastcgi_cache_valid 200 60m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
add_header X-Cache $upstream_cache_status;
include snippets/fastcgi-php.conf;
}
}
Massive speedup for WP.
Status page
location ~ ^/(fpm-status|fpm-ping)$ {
access_log off;
allow 127.0.0.1;
deny all;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include fastcgi_params;
}
pm.status_path = /fpm-status in pool config.
Upload size
client_max_body_size 100m;
; php.ini
upload_max_filesize = 100M
post_max_size = 100M
memory_limit = 256M
max_execution_time = 60
Must match in both nginx and php.
Timeouts
fastcgi_connect_timeout 10s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s;
Multiple PHP versions
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
# Legacy app
location /legacy/ {
location ~ \.php$ {
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}
}
Hide sensitive files
location ~* /(?:config|vendor|.env|composer\.(json|lock)|package(-lock)?\.json) {
deny all;
}
Performance tips
- Unix socket > TCP (lower overhead).
opcache.enable.fastcgi_cachefor read-heavy.- CDN for static assets.
- Right-size
pm.max_childrento RAM.
Common mistakes
pm.max_childrentoo high → OOM.- TCP fastcgi_pass with
localhost(resolves DNS) — use127.0.0.1. validate_timestamps=0in dev → changes don’t take effect.- Allowing PHP execution in upload dirs → RCE risk.
- Missing
internal;on index.php → arbitrary php executable.
Read this next
If you want my Laravel + WP nginx templates, they’re at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .