Deploy PHP-FPM Behind Nginx on Hostperl VPS

Start with a fresh VPS and the right login
PHP-FPM behind Nginx is a good fit for WordPress, Laravel, and other PHP sites that need steady VPS performance. On Hostperl, many customers choose this setup because it uses less memory than Apache and still leaves room to grow.
On your local computer:
ssh root@203.0.113.10203.0.113.10 is a reserved documentation example. Replace it with the public IP assigned to your Hostperl VPS. If you use a custom SSH port, the connection command becomes ssh -p 2222 root@203.0.113.10.
Once you are in, check the operating system before you install anything:
cat /etc/os-releaseThe rest of this guide splits into Ubuntu/Debian and AlmaLinux/Rocky Linux where commands differ. Keep the root session open while you create and test the non-root admin account.
Create a non-root admin before you change SSH
Running web services as root is unnecessary and risky. Create a sudo user named deploy, add your SSH key, and verify sudo access in a second terminal before you touch root login settings.
On the VPS as root: Ubuntu and Debian
adduser deploy
usermod -aG sudo deploy
mkdir -p /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keysThis creates the account, grants sudo, and copies the existing key so you can test a clean login. If you do not already use SSH keys, add your public key with ssh-copy-id deploy@203.0.113.10 from your local machine instead of enabling password logins.
On the VPS as root: AlmaLinux and Rocky Linux
useradd -m deploy
passwd deploy
usermod -aG wheel deploy
mkdir -p /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keysHere the wheel group grants sudo. If your server is key-only, you can lock the password later with passwd -l deploy after SSH login is verified.
On your local computer, open a second terminal and test the new login:
ssh deploy@203.0.113.10
sudo -v
pwdYou should land in the deploy user’s home directory. Sudo should prompt for your password or key-backed authorization. Do not disable root access until this works.
Install Nginx, PHP-FPM, and the required PHP modules
In this stack, Nginx handles HTTP requests and passes PHP files to PHP-FPM through a local socket. On Hostperl VPS plans, this is a practical choice for small-business sites because it keeps memory use in check and leaves headroom for caching, backups, and database workloads.
On the VPS as the non-root sudo user: Ubuntu and Debian
sudo apt update
sudo apt install -y nginx php-fpm php-cli php-mysql php-curl php-xml php-mbstring php-zip php-gd
php -v
nginx -vOn the VPS as the non-root sudo user: AlmaLinux and Rocky Linux
sudo dnf -y update
sudo dnf -y install nginx php-fpm php-cli php-mysqlnd php-curl php-xml php-mbstring php-zip php-gd
php -v
nginx -vYou should see current package versions and no missing repository errors. If your site later needs a database backend, Hostperl’s VPS hosting gives you enough control to tune PHP and the database together instead of working around shared-hosting limits.
Set up the site directory and a basic PHP app
Use a simple document root first so you can confirm the stack works before you add WordPress or a framework. The example below places files in /opt/myapp and creates a plain health check page.
On the VPS as the non-root sudo user
sudo mkdir -p /opt/myapp/public
sudo chown -R deploy:deploy /opt/myappNow create the test file:
cat > /opt/myapp/public/index.php <<'PHP'
<?php
phpinfo();
PHPThis gives you a visible PHP response for the first verification pass. After the stack is live, replace it with your real application.
Configure PHP-FPM for the web server
Nginx needs a socket path that matches the active PHP-FPM version. On 2026-era packages, service names vary a bit by distribution, so check them instead of guessing.
On the VPS as the non-root sudo user: Ubuntu and Debian
systemctl status php8.3-fpm --no-pagerIf your distribution ships a different version, list matching services with systemctl list-units | grep fpm. Then edit the pool file:
sudo nano /etc/php/8.3/fpm/pool.d/www.confUse this socket-based pool configuration, adjusting the version number if needed:
[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 = 12
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3Save and exit, then test and restart:
sudo php-fpm8.3 -t
sudo systemctl restart php8.3-fpm
sudo systemctl enable php8.3-fpmOn the VPS as the non-root sudo user: AlmaLinux and Rocky Linux
systemctl status php-fpm --no-pagerEdit the pool file:
sudo nano /etc/php-fpm.d/www.confUse this configuration:
[www]
user = apache
group = apache
listen = /run/php-fpm/www.sock
listen.owner = nginx
listen.group = nginx
pm = dynamic
pm.max_children = 12
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3On SELinux-enabled systems, label the socket so Nginx can connect cleanly:
sudo setsebool -P httpd_can_network_connect 1
sudo restorecon -Rv /run/php-fpmThen test and enable the service:
sudo php-fpm -t
sudo systemctl restart php-fpm
sudo systemctl enable php-fpmWrite the Nginx server block
Nginx needs to send .php requests to the PHP-FPM socket and serve static files directly. Open the virtual host file, write the config, then test syntax before reloading.
On the VPS as the non-root sudo user: Ubuntu and Debian
sudo nano /etc/nginx/sites-available/example.comUse this server block:
server {
listen 80;
server_name example.com www.example.com;
root /opt/myapp/public;
index index.php index.html;
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
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;
}
location ~* \.(css|js|png|jpg|jpeg|gif|svg|ico)$ {
expires 7d;
access_log off;
}
}Enable it and test:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com
sudo nginx -t
sudo systemctl restart nginx
sudo systemctl enable nginxOn the VPS as the non-root sudo user: AlmaLinux and Rocky Linux
sudo nano /etc/nginx/conf.d/example.com.confUse this server block:
server {
listen 80;
server_name example.com www.example.com;
root /opt/myapp/public;
index index.php index.html;
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
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-fpm/www.sock;
}
}Then test and reload:
sudo nginx -t
sudo systemctl restart nginx
sudo systemctl enable nginxThis is where a lot of migrations fail, usually because the socket path is wrong. If your stack was previously Apache-based, review Nginx vs Apache for VPS before you cut over a live site.
Open the firewall without locking yourself out
Allow SSH and web traffic before you close any old access paths. Add the rule first, test from a second terminal, then remove anything you no longer need.
On the VPS as the non-root sudo user: Ubuntu and Debian
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status verboseOn the VPS as the non-root sudo user: AlmaLinux and Rocky Linux
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-allOn RHEL-compatible systems, check SELinux too:
getenforce
sudo ausearch -m AVC -ts recent | tail -n 20If SELinux blocks Nginx from reaching PHP-FPM, you will see AVC denials. The usual fix is to correct labels or boolean settings, not to disable SELinux permanently.
Test PHP-FPM behind Nginx from server and client
Use both local and remote checks. That catches socket issues, firewall mistakes, and DNS delays before your visitors do.
On the VPS as the non-root sudo user
curl -I http://127.0.0.1
curl http://127.0.0.1 | grep -m1 'PHP Version'
systemctl status nginx php8.3-fpm --no-pagerOn AlmaLinux and Rocky Linux, replace php8.3-fpm with php-fpm if that is the service name on your box. You want an HTTP 200 response and a visible PHP info page.
On your local computer
curl -I http://203.0.113.10
curl http://203.0.113.10 | headReplace 203.0.113.10 with your VPS IP. A successful response means Nginx is listening publicly and forwarding to PHP-FPM.
Harden the stack after it works
Once the site responds, tighten the basics. This is where Hostperl customers usually save time later, because a clean initial setup reduces support tickets after launch.
On the VPS as the non-root sudo user
sudo apt update && sudo apt -y upgradeOn AlmaLinux and Rocky Linux:
sudo dnf -y upgradeCheck for suspicious changes and failed logins:
sudo journalctl -u nginx -u php8.3-fpm --since today
sudo last -a | head
sudo tail -n 50 /var/log/auth.logOn RHEL-compatible systems, use /var/log/secure instead of /var/log/auth.log. If you plan to run WordPress, this is a good time to read WordPress staging checklist for safe launches so your production cutover stays predictable.
Add HTTPS with Let’s Encrypt
Do not publish a production site on plain HTTP unless it is only for a short test. For a real domain, point DNS to the VPS first, then request a certificate.
On the VPS as the non-root sudo user: Ubuntu and Debian
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.comOn the VPS as the non-root sudo user: AlmaLinux and Rocky Linux
sudo dnf -y install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.comCertbot should edit the Nginx configuration and set up renewal. Confirm the timer:
systemctl list-timers | grep certbot
sudo certbot renew --dry-runIf renewal fails, fix the certificate path or DNS record first. For domain and certificate troubleshooting tied to hosting, Hostperl VPS plans are usually easier to manage than a crowded shared environment, especially when you need to coordinate DNS, SSL, and application changes quickly.
If you are setting up a new PHP site, Hostperl’s VPS hosting gives you the control to run Nginx, PHP-FPM, and TLS cleanly without overbuying. For teams that expect more traffic or need a migration window, a managed server option can keep the launch calmer and the support path shorter.
Our customers often start here, then add backups, database tuning, or a staging copy once the first release is stable.
Common failures and how to check them
Nginx returns 502 Bad Gateway. Run sudo journalctl -u nginx -n 50 --no-pager and sudo systemctl status php8.3-fpm --no-pager. A missing socket path or a stopped PHP-FPM service is the usual clue. Fix the socket reference in the Nginx config, then rerun sudo nginx -t and reload Nginx.
The browser downloads PHP files instead of executing them. Check the server block with sudo nginx -T | less. If the location ~ \.php$ block is missing, Nginx is serving the file directly. Restore the fastcgi section and restart Nginx.
SELinux blocks the site on AlmaLinux or Rocky Linux. Use sudo ausearch -m AVC -ts recent | tail to see denials. Then run sudo restorecon -Rv /opt/myapp /run/php-fpm and confirm getenforce still returns Enforcing.
HTTPS does not issue. Confirm that dig example.com points to the VPS IP and that port 80 is reachable. Let’s Encrypt will not complete the challenge if DNS still points somewhere else.
Final verification before you hand over the site
Finish with a simple smoke test and a reboot check. That tells you whether the stack survives the next maintenance window.
On the VPS as the non-root sudo user
sudo rebootAfter the server comes back, reconnect and run:
systemctl status nginx php8.3-fpm --no-pager
ss -lntp | grep -E ':80|:443'
curl -I https://example.com
curl https://example.com | headReplace example.com with your real domain. You should see both services active, Nginx listening on web ports, and a working HTTPS response.
If you are still choosing the right platform for a PHP site, compare this setup against Hostperl shared hosting and managed VPS hosting before you launch. For sites that need custom Nginx rules, PHP tuning, or room for later migration, PHP-FPM behind Nginx is usually the cleaner long-term fit.
FAQ
Can I use Apache instead of Nginx?
Yes, but this tutorial is built for PHP-FPM behind Nginx. If you already host Apache apps, keep them separate unless you have a reason to standardize on Nginx.
Which PHP version should I install?
Use the PHP version shipped with your supported OS repository unless your application requires a specific version. Check compatibility before you deploy production code.
Do I need Redis for this setup?
No. Add Redis only if your application benefits from object caching or queue jobs. For a first launch, keep the stack simple and prove the web path first.
Why do I need a non-root user?
It reduces the blast radius of mistakes. It also makes support easier when you later hand access to a developer, agency, or operations contact.
