
Nginx is a strong fit for VPS hosting. It serves static files efficiently, handles TLS quickly, and stays stable during traffic spikes. This nginx setup guide tutorial walks through a clean production install on Ubuntu. You’ll add PHP-FPM, HTTPS, baseline hardening, and a few performance tweaks you can verify with real requests.
The steps below assume a fresh VPS (or new dedicated server) with root access. They also assume your domain already points to your server IP.
If you’d rather not manage OS updates, firewall rules, and routine maintenance, managed VPS hosting from HostMyCode keeps Nginx patched and stable while you focus on the site.
Prerequisites (so you don’t fight the basics later)
- Server: Ubuntu 24.04 LTS or Ubuntu 22.04 LTS (commands below target Ubuntu 24.04).
- Access: SSH as root or a sudo user.
- DNS: A/AAAA records for your domain pointing to the server.
- Firewall: Ports 22 (SSH), 80 (HTTP), 443 (HTTPS) open.
If you’re not sure your firewall rules are correct, follow this first: UFW firewall setup tutorial (2026).
It helps you avoid two common mistakes: breaking SSH access and blocking Let’s Encrypt validation.
Step 1: Update Ubuntu and install Nginx (from the official repo)
Update packages, then install Nginx. Ubuntu’s repository is usually the right choice for production.
It’s security-maintained and predictable.
sudo apt update
sudo apt -y upgrade
sudo apt -y install nginx
Check that the service is running. Then confirm the installed version:
systemctl status nginx --no-pager
nginx -v
On Ubuntu 24.04, you’ll typically see Nginx 1.24.x (packaged). That’s a solid baseline for most hosting workloads.
Step 2: Open HTTP/HTTPS and verify basic delivery
If you use UFW, allow web traffic:
sudo ufw allow 'Nginx Full'
sudo ufw status
Confirm the default site responds from the server IP:
curl -I http://YOUR_SERVER_IP/
You’re looking for a 200 or 301 and a Server: nginx header.
Step 3: Create a clean server block for your domain
On Ubuntu, site configs live in /etc/nginx/sites-available/.
You enable them via /etc/nginx/sites-enabled/.
Start by creating a web root and a simple test page:
sudo mkdir -p /var/www/example.com/public
sudo chown -R www-data:www-data /var/www/example.com
printf "<h1>OK: example.com</h1>" | sudo tee /var/www/example.com/public/index.html
Create the Nginx server block file:
sudo nano /etc/nginx/sites-available/example.com
Paste this (replace example.com and www.example.com):
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.html index.htm;
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
location / {
try_files $uri $uri/ =404;
}
}
Enable it.
If you don’t need the default site, remove it to avoid confusion later:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
Test the config and reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
Once DNS is pointing correctly, test by hostname:
curl -I http://example.com/
Step 4: Install PHP-FPM and wire it to Nginx (WordPress-ready)
WordPress and most PHP apps run best behind PHP-FPM.
Install PHP 8.3 FPM plus common extensions:
sudo apt -y install php8.3-fpm php8.3-cli php8.3-curl php8.3-gd php8.3-intl php8.3-mbstring php8.3-xml php8.3-zip php8.3-mysql
Create a quick PHP test file:
printf "<?php phpinfo();" | sudo tee /var/www/example.com/public/info.php
sudo chown www-data:www-data /var/www/example.com/public/info.php
Now update the server block to pass PHP to FPM. Edit:
sudo nano /etc/nginx/sites-available/example.com
Replace the index line and add the PHP location block:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/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?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
access_log off;
}
location ~ /\. {
deny all;
}
}
Test and reload:
sudo nginx -t
sudo systemctl reload nginx
Verify PHP execution:
curl -I http://example.com/info.php
Important: delete info.php after testing.
It leaks server details you don’t want public.
sudo rm -f /var/www/example.com/public/info.php
Step 5: HTTPS with Let’s Encrypt (Certbot) and safe redirects
Install Certbot for Nginx:
sudo apt -y install certbot python3-certbot-nginx
Request a certificate and let Certbot update your server block:
sudo certbot --nginx -d example.com -d www.example.com
Choose the redirect option so HTTP always upgrades to HTTPS.
Confirm renewal is scheduled and that a dry run succeeds:
sudo systemctl status certbot.timer --no-pager
sudo certbot renew --dry-run
If renewals fail later, this checklist will get you to the cause quickly: TLS certificate renewal troubleshooting tutorial (2026).
nginx setup guide tutorial: Hardening baseline (headers, TLS, and file access)
Keep hardening practical.
Remove easy wins for scanners, but don’t break WordPress, webmail, or a control panel.
Add security headers (minimal, low-risk set)
Create a reusable snippet:
sudo nano /etc/nginx/snippets/security-headers.conf
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
Include it inside your HTTPS server block(s):
include /etc/nginx/snippets/security-headers.conf;
If you want a stricter header profile (including CSP) and you’re running WordPress, use this guide to avoid common breakage: Nginx security headers configuration tutorial (2026).
Stop executing PHP in uploads (WordPress-specific safety net)
On WordPress, you generally never want PHP executing from wp-content/uploads.
Add this inside the site server block:
location ~* ^/wp-content/uploads/.*\.php$ {
deny all;
}
This blocks a common malware move: dropping a PHP file into uploads and calling it directly.
Basic rate limiting for login endpoints
Brute-force traffic wastes CPU even when every attempt fails.
Define a small limit zone in /etc/nginx/nginx.conf. Put it inside the http {} block:
limit_req_zone $binary_remote_addr zone=loginlimit:10m rate=10r/m;
Then apply it in your site config:
location = /wp-login.php {
limit_req zone=loginlimit burst=20 nodelay;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
If you need a fuller plan (bursts that don’t punish real users, plus bot patterns), follow: Nginx rate limiting tutorial (2026).
Step 6: Performance tuning you can feel (compression, files, and workers)
You don’t need a long tuning spree to see improvements.
A few defaults cut bandwidth and speed up rendering for most visitors.
Enable Brotli or gzip (choose one based on your stack)
Gzip is universally available and still effective.
Enable it in /etc/nginx/nginx.conf inside http {}:
gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_types
text/plain
text/css
application/json
application/javascript
text/xml
application/xml
application/xml+rss
text/javascript
image/svg+xml;
Reload Nginx. Then confirm the response includes compression headers:
sudo nginx -t && sudo systemctl reload nginx
curl -I -H 'Accept-Encoding: gzip' https://example.com/
For text assets, you should see Content-Encoding: gzip.
Set sane worker settings (don’t overthink it)
In /etc/nginx/nginx.conf, these defaults are usually a good fit for VPS hosting:
worker_processes auto;
worker_connections 2048;
On a 2–4 vCPU VPS, auto maps workers to cores.
The bigger win is avoiding a too-low worker_connections ceiling on busy sites.
Turn on HTTP/2 and keep-alive (HTTPS site)
Certbot often enables HTTP/2 on recent templates.
Still, confirm the TLS listener includes http2:
listen 443 ssl http2;
Then set keep-alive in http {}:
keepalive_timeout 65;
Step 7: Add microcaching for WordPress (optional, big impact)
If your WordPress site sees bursts (social posts, crawler storms, product launches), microcaching can absorb the spike.
It does this by caching responses for a few seconds.
Done well, microcaching reduces PHP-FPM load sharply while keeping most pages effectively “fresh.”
Microcache configs are easy to get subtly wrong.
Watch cookie handling, logged-in sessions, and purge behavior. Use the dedicated guide here: Nginx caching tutorial (2026).
Step 8: Fix real visitor IPs behind Cloudflare or a load balancer
If you proxy traffic through Cloudflare, a DDoS provider, or an L7 load balancer, Nginx will log the proxy IP.
That happens unless you configure Real IP properly.
Incorrect client IPs also break rate limiting, geolocation, and incident investigations.
Follow: Nginx real IP configuration tutorial (2026). It covers the correct directives and shows how to validate the result in access logs.
Step 9: Troubleshooting checklist (fast diagnostics that save hours)
Most Nginx problems on a VPS come down to a short list: config typos, permissions mismatches, PHP-FPM upstream issues, or firewall/DNS mistakes.
Run these checks in order. You’ll usually find the culprit quickly.
1) Nginx won’t reload
- Run:
sudo nginx -tand fix the exact file/line it reports. - Check the last errors:
sudo tail -n 80 /var/log/nginx/error.log
2) 502 Bad Gateway (PHP-FPM)
- Confirm PHP-FPM is running:
systemctl status php8.3-fpm --no-pager - Confirm socket path exists:
ls -l /run/php/php8.3-fpm.sock - Look for pool errors:
sudo tail -n 120 /var/log/php8.3-fpm.log
If the socket is missing, PHP-FPM may have failed to start due to a bad config.
Start with: php-fpm8.3 -t.
3) 403 Forbidden on static files
- Check ownership:
sudo ls -ld /var/www/example.com /var/www/example.com/public - On Ubuntu,
www-datashould at least have read+execute on directories.
4) Let’s Encrypt fails HTTP-01 validation
- Verify DNS A record points to the server IP (not the old host).
- Confirm port 80 is open:
sudo ss -lntp | grep ':80' - Check firewall rules.
For safe firewall debugging (without breaking SSH), use: VPS firewall troubleshooting tutorial (2026).
5) You need to access an admin panel without opening more ports
If you’re tightening exposure, SSH port forwarding is a clean way to reach internal dashboards temporarily:
Step 10: Production checklist before you put traffic on it
- Config syntax:
nginx -tpasses. - HTTPS: A valid certificate, redirect from HTTP to HTTPS, and renewal timer active.
- Logs: Per-site access/error logs enabled and rotating (Ubuntu logrotate defaults usually cover Nginx).
- Backups: You can restore your site content and configs (
/etc/nginx, app files, and secrets). - Monitoring: Basic uptime + CPU/RAM/disk alerts so you see issues before clients do.
If monitoring isn’t set up yet, this is a solid starting point: Server monitoring tutorial (2026).
Summary: a clean Nginx stack you can maintain
You now have a maintainable stack.
Nginx serves your domain, PHP-FPM handles dynamic requests, and Let’s Encrypt provides HTTPS with automated renewals.
You also added a small set of security and performance settings that hold up on real VPS hosting.
Keep configs modular. Run nginx -t before every reload.
Treat backups and monitoring as part of the build, not optional extras.
If you want predictable performance for WordPress or PHP apps without overspending, start with a HostMyCode VPS.
If you’d rather hand off patching, hardening, and incident response, managed VPS hosting is the straightforward upgrade.
If you’re putting together a production Nginx stack for WordPress, landing pages, or client sites, HostMyCode gives you a clean starting point: predictable CPU, fast storage, and up-to-date Linux images. Pick a HostMyCode VPS if you want hands-on control, or choose managed VPS hosting if you want patching, security checks, and operational help included.
FAQ
Should I use Nginx or Apache for WordPress in 2026?
Both work well. Nginx typically uses less memory per connection and serves static assets efficiently. Apache can be simpler if your setup depends heavily on .htaccess. If you’re starting fresh on a VPS, Nginx + PHP-FPM is a solid default.
Where are the main Nginx config files on Ubuntu?
The global config is /etc/nginx/nginx.conf. Sites typically live in /etc/nginx/sites-available/ and are enabled via symlinks in /etc/nginx/sites-enabled/. Snippets are often kept in /etc/nginx/snippets/.
What’s the fastest way to spot why I’m getting 502 errors?
Start with PHP-FPM: systemctl status php8.3-fpm, then check /var/log/nginx/example.com.error.log. A missing FPM socket or a crashed pool is the most common cause.
Do I need caching if my site is small?
Not always. Even small WordPress sites can get hammered by bots and crawlers, though. A basic static asset cache plus optional microcaching can reduce CPU spikes and keep the site responsive on a smaller VPS.
Can I run Nginx alongside a control panel?
Yes, but plan it carefully. Some panels assume Apache owns ports 80/443. If you need Nginx in front of panel-managed Apache, use a reverse proxy setup and verify SSL behavior and real client IP handling.