Virtual Hosts & PHP-FPM
Multiple sites on one server, PHP sockets.
Learning objectives
- Host multiple domains on one IP with virtual hosts
- Connect Nginx to PHP-FPM via Unix or TCP socket
- Isolate production and staging configurations
One IP, many sites
Workshop Co.’s VPS at 203.0.113.10 serves production. The same machine might also answer for staging.workshopco.ca if DNS pointed there — or staging runs on 203.0.113.99 with an identical layout. Each hostname gets its own server block: a virtual host.
PHP-FPM connection
PHP runs in a separate pool (PHP-FPM). Nginx passes .php requests to the pool via a socket:
server {
listen 443 ssl http2;
server_name www.workshopco.ca;
root /var/www/workshopco/public;
index index.php;
ssl_certificate /etc/letsencrypt/live/workshopco.ca/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/workshopco.ca/privkey.pem;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm-workshopco.sock;
}
location ~ /\.(?!well-known) {
deny all;
}
}
Use separate FPM pools per site (workshopco vs staging) so a runaway staging script cannot exhaust production workers. Each pool gets its own socket and user.
Worked example — production + staging blocks
| Site | server_name | root | FPM socket |
|---|---|---|---|
| Production | www.workshopco.ca | /var/www/workshopco/public | php8.3-fpm-workshopco.sock |
| Staging | staging.workshopco.ca | /var/www/staging/public | php8.3-fpm-staging.sock |
Staging might add HTTP basic auth so search engines and customers do not stumble on test content:
auth_basic "Staging — Workshop Co.";
auth_basic_user_file /etc/nginx/.htpasswd-staging;
File layout on the server
/etc/nginx/sites-available/workshopco.conf → prod vhost /etc/nginx/sites-available/staging.conf → staging vhost /etc/nginx/sites-enabled/ → symlinks to enabled sites /var/www/workshopco/public/ → prod web root /var/www/staging/public/ → staging web root
Keep configs in sites-available, enable with symlinks in sites-enabled. Disabling a site is removing one link — safer than deleting config.
Try it yourself — new subdomain
Workshop Co. adds intranet.workshopco.ca for staff schedules (PHP app in /var/www/intranet/public). Sketch the server_name, root, and FPM socket name you would use.
Answer
server_name intranet.workshopco.ca;
root /var/www/intranet/public;
fastcgi_pass unix:/run/php/php8.3-fpm-intranet.sock;
Obtain a cert including intranet.workshopco.ca in SANs. Restrict access by IP or VPN if sensitive.
Spot the mistake
PHP files download instead of executing. The Nginx config has root and try_files but no location ~ \.php$ block. Explain the symptom and fix.
Answer
Without FastCGI pass-through, Nginx serves .php as static files. Add a PHP location block with fastcgi_pass to the correct FPM socket.
Quick quiz
- How does Nginx choose a virtual host for a request?
- Why use separate PHP-FPM pools per site?
- What does the location
~ /\.(?!well-known)block?
Answers
- Matches
listenport + TLS SNI (443) orHostheader (80) againstserver_name. - Resource isolation and distinct Unix users for security.
- Denies hidden files (e.g.
.env) but allows ACME challenges under.well-known.