On a VDS, a ready PHP script is more often behind Nginx + PHP-FPM than behind "pure Apache from the mid-2000s". Nginx serves static files, and PHP goes to an FPM pool through FastCGI. Shared hosting may hide this behind a panel; on your own server, you see the config.
Below is a framework sufficient to launch a Microscript script. Fine tuning for a thousand RPS is not included here; first make it work and log properly.
Packages
On Debian/Ubuntu, roughly:
nginx
php8.2-fpm php8.2-mysql php8.2-mbstring php8.2-xml php8.2-gd php8.2-curl
Use your own 8.x version. Module requirements: PHP/MySQL. MySQL/MariaDB is separate. Machine choice: VDS, root: VPS.
server block: the idea
server_nameis your domain;rootis the script directory withindex.php;index index.php;- PHP location ->
fastcgi_passto the pool socket or port; - Friendly URLs: try_files to index.php if the script works that way;
client_max_body_sizefor uploads, often 32m-64m.
The exact snippet depends on the product: whether there is a public/ directory and what rewrite rules are needed. Do not blindly copy a WordPress config if the structure is different.
PHP-FPM pool
A separate pool for the site user is better than mixing everything under www-data. A socket might be /run/php/php8.2-fpm-site.sock. In Nginx:
fastcgi_pass unix:/run/php/php8.2-fpm-site.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
After changing the pool, set upload directory permissions for that user: chmod.
.htaccess does not work
Apache rules from the package are not applied automatically. Move rewrite rules to Nginx. A symptom after moving from shared Apache: the home page works, internal URLs return 404. That is not "broken PHP".
A temporary workaround is to install Apache if it closes launch faster. Long term, Nginx+FPM is normal.
Logs
Watch both:
- nginx access/error;
- PHP-FPM log or
php_admin_value[error_log].
502 Bad Gateway means FPM is not running or the socket is wrong. 500 is already PHP or the script: 500 error. Database errors: MySQL.
client_max_body_size is smaller than the file, and nginx cuts the request before the script. The nginx error.log will mention the body. Also check upload_max_filesize in the pool php.ini.SSL
Certbot can add ssl to the server block: SSL on a website. After issuance, check redirects and mixed content in the admin panel.
Brief security
- Disable PHP execution in the upload directory.
- Do not serve
.env, configs, or.git. - Firewall: ports.
Script deployment
- root points to the directory, files are uploaded.
- Database is created, config is filled.
nginx -t && systemctl reload nginx.- Installer or admin login: installation.
- Cron is separate: cron.
Moving from old Apache hosting: migration. Locally on Windows, Nginx is not required; Apache in MicroServer is used there.
Typical PHP location
A base used on most VDS setups:
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.2-fpm-site.sock;
fastcgi_read_timeout 120s;
}
If the script is not in the domain root but in a subdirectory, root and try_files must point to the same place. Confusing alias and root on subfolders gives an empty response or "No input file specified". Then the FPM log path does not match the real file on disk.
fastcgi_read_timeout is raised when the admin panel runs a long import. Otherwise nginx returns 504 while PHP keeps working, and the database gets a half import.
Static files and blocking extra access
Nginx serves images, css, and js itself; they should not reach PHP. Separately block service files:
location ~ /\.(git|env|ht) {
deny all;
}
In the upload directory, it is better to forbid PHP execution. Otherwise an uploaded file with a .php extension becomes a backdoor. On Apache this was often done through .htaccess; on Nginx use a separate location such as location ~* /upload/.*\.php$ with deny, or move uploads to another root without PHP handling.
Several sites on one VDS
Each domain needs its own server block and preferably its own FPM pool. Otherwise one site fills pm.max_children and a neighboring shop starts returning 502. In the pool, check pm, pm.max_children, and pm.max_requests. On 2 GB RAM, being greedy with max_children is dangerous: PHP-FPM and MySQL will fall together.
Separate logs per site are better: own access/error for each vhost. When a ticket says "500 yesterday afternoon", without separated logs you are looking for a needle in a shared error.log across five domains.
Connection with script installation
Until nginx -t passes, the browser installer will not open. First get green nginx -t, reload, and check curl -I http://127.0.0.1 -H 'Host: example.com'. Only then move to database and installation form.
If document root points to an empty directory while files are one level higher, you get the Nginx welcome page or 403. That is not a Microscript bug. Set root to the directory with index.php.
After successful launch, run cron as the site user, not root unless needed. Certbot will add listen 443; check that a second server block did not overwrite your root. Sometimes Certbot creates a duplicate and the site suddenly points to /var/www/html.
Diagnosing 502 and 504
- 502: dead socket, pool did not start, wrong sock name, SELinux/socket permissions.
- 504: timeout; a long script or FPM stuck waiting for MySQL.
- An nginx error.log line
connect() to unix:... failedmeans check the sock path and php-fpm status.
systemctl status php8.2-fpm and the pool log usually answer faster than rebooting the whole server "just in case".
When PHP answers but the script screams about the database, it is not Nginx. Go to MySQL error. When there is a white screen with 200, use display_errors and the application log: 500 error.
Panels
ISPmanager and similar panels generate nginx config themselves. You can edit it manually, but after "resaving the domain" in the panel, your changes may be rolled back. Either edit through panel templates or keep an include file that the panel does not overwrite. On shared hosting with Apache, this section matters less because there is .htaccess. On your own VDS, without understanding fastcgi_pass, the script simply "does not open" even though files are uploaded.
Back up nginx configs together with the site: backup. After migration to another VDS, do not copy config blindly: paths and sockets differ. Database dump is separate from vhost config.
FAQ
Configure nginx php-fpm for a script?
On a VDS, scripts usually run behind Nginx+PHP-FPM, not shared hosting.
How long does “Nginx + PHP-FPM for a script” take?
About 6 minutes to read. In practice it depends on your hosting and database setup.
Do I need a dedicated server?
For most scripts, shared hosting or a VDS with PHP and MySQL is enough. See the VDS section and PHP/MySQL requirements.
Vds for a php site with mysql?
See the related manual for this query. vds for a php site with mysql
Vps with root for a php script?
See the related manual for this query. vps with root for a php script