Chapter 6

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;
    }
}
Pool isolation

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

Siteserver_namerootFPM socket
Productionwww.workshopco.ca/var/www/workshopco/publicphp8.3-fpm-workshopco.sock
Stagingstaging.workshopco.ca/var/www/staging/publicphp8.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
sites-enabled pattern

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

  1. How does Nginx choose a virtual host for a request?
  2. Why use separate PHP-FPM pools per site?
  3. What does the location ~ /\.(?!well-known) block?
Answers
  1. Matches listen port + TLS SNI (443) or Host header (80) against server_name.
  2. Resource isolation and distinct Unix users for security.
  3. Denies hidden files (e.g. .env) but allows ACME challenges under .well-known.