Nginx server blocks are the Nginx equivalent of Apache virtual hosts: each block serves one website based on the requested domain. Pair each site with its own PHP-FPM pool and Linux user and a problem on one site cannot touch the others. This guide sets up two PHP sites on one Nginx server.
Before you start
- A Linux VPS with Nginx and PHP-FPM installed (see the LEMP stack guide).
- Domains pointing to the server.
- Root or sudo access.
Create a user and folder per site
sudo useradd -m -s /usr/sbin/nologin site1
sudo mkdir -p /var/www/site1.com/public
sudo chown -R site1:site1 /var/www/site1.com
sudo chmod 750 /var/www/site1.comLet Nginx read the files by adding its user to the site group: sudo usermod -aG site1 www-data (the user is nginx on AlmaLinux). Repeat for site2.
Create a PHP-FPM pool per site
- Copy the default pool: on Ubuntu the pools live in
/etc/php/8.3/fpm/pool.d/, on AlmaLinux in/etc/php-fpm.d/. Createsite1.conf:[site1] user = site1 group = site1 listen = /run/php/site1.sock listen.owner = www-data listen.group = www-data pm = ondemand pm.max_children = 10 pm.process_idle_timeout = 30s php_admin_value[open_basedir] = /var/www/site1.com:/tmpOn AlmaLinux use
nginxfor the listen owner and group and a socket path like/run/php-fpm/site1.sock. - Restart PHP-FPM:
sudo systemctl restart php8.3-fpm(orphp-fpm).
Create the server block
- Create
/etc/nginx/sites-available/site1.com(AlmaLinux:/etc/nginx/conf.d/site1.com.conf):server { listen 80; server_name site1.com www.site1.com; root /var/www/site1.com/public; index index.php index.html; access_log /var/log/nginx/site1.access.log; error_log /var/log/nginx/site1.error.log; client_max_body_size 64m; location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/run/php/site1.sock; } location ~ /\.(?!well-known) { deny all; } location ~* \.(css|js|jpg|jpeg|png|gif|webp|svg|woff2?)$ { expires 30d; access_log off; } } - Enable it on Ubuntu:
sudo ln -s /etc/nginx/sites-available/site1.com /etc/nginx/sites-enabled/. - Test and reload:
sudo nginx -t && sudo systemctl reload nginx.
The try_files line gives WordPress and most PHP frameworks pretty URLs. The dot-file rule blocks access to .git, .env and similar files.
Add HTTPS
sudo certbot --nginx -d site1.com -d www.site1.comSet a default server
Requests for unknown domains go to the first server block. Add a catch-all that closes the connection so random hostnames do not show one of your sites:
server {
listen 80 default_server;
server_name _;
return 444;
}What to check afterwards
- Each domain shows its own site.
ps aux | grep php-fpmshows workers running assite1andsite2when the sites are busy.https://site1.com/.envreturns 403.
Troubleshooting
- File not found / "Primary script unknown": wrong
rootorSCRIPT_FILENAME, or PHP-FPM cannot read the files. - 502 Bad Gateway: socket path mismatch or the pool failed to start – check
journalctl -u php8.3-fpm. - 403 on the site: Nginx's user cannot traverse the site folder; check group membership and folder permissions.
- SELinux (AlmaLinux): run
sudo restorecon -Rv /var/www; allow writes for uploads with thehttpd_sys_rw_content_tcontext.
FAQ: Nginx server blocks
Does Nginx support .htaccess?
No. Rewrite rules go into the server block, which is faster because Nginx doesn't read files on every request.
Why use a separate PHP-FPM pool per site?
Each site runs as its own user, so a hacked site cannot read or modify the others, and you can limit resources per site.
How many sites can one Nginx server host?
As many as RAM and CPU allow; Nginx itself handles thousands of server blocks.
