Disclosure: we may earn a commission or referral reward from CloudCone, Contabo, DigitalOcean (account credit, not cash), Hostwinds, InterServer, RackNerd, VPSDime and Vultr if you sign up through our links. It does not change the price you pay. Every other provider pays us nothing. Prices in this guide are as of the date shown and may have changed; see the comparison pages for the latest checked prices.
Guides · BLOG
How to install Nginx on any Linux VPS in 2026
Some prices quoted in this guide are out of date. It is being rebuilt on our price index; for current prices use the comparison pages, which show when each price was checked.
Nginx is the default web server for anything you're likely to build in 2026 — reverse proxy for Node/Rails/Django apps, TLS terminator, static file server, load balancer. This guide walks through the correct installation and the essential first configuration on the four common VPS distros.
Ubuntu / Debian
```bash
apt update
apt install nginx
systemctl enable --now nginx
```
Verify: curl http://localhost/ should return the Nginx welcome page.
CentOS / Rocky / RHEL
```bash
dnf install -y nginx
systemctl enable --now nginx
```
Same verification.
Basic firewall
Open ports 80 (HTTP) and 443 (HTTPS) in your firewall:
```bash
ufw allow 80
ufw allow 443
```
For CentOS/Rocky using firewalld:
```bash
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
```
The default configuration
Nginx's configuration lives at:
/etc/nginx/nginx.conf— main config./etc/nginx/sites-available/— per-site configs (Ubuntu/Debian pattern)./etc/nginx/sites-enabled/— symlinks to enabled sites./etc/nginx/conf.d/— additional config fragments (CentOS pattern; also used on Debian).
For a first site on Ubuntu:
Create /etc/nginx/sites-available/yourdomain.com:
```nginx
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/yourdomain.com;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
```
Enable it:
```bash
ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx
```
Create a test page:
```bash
mkdir -p /var/www/yourdomain.com
echo "Hello Nginx!" > /var/www/yourdomain.com/index.html
```
Visit http://yourdomain.com/ and confirm.
Reverse proxy for a Node app
The common pattern: Nginx on port 80/443 proxies to your app on port 3000 (or wherever).
```nginx
server {
listen 80;
server_name api.yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
```
The Upgrade and Connection headers make WebSocket connections work through the proxy.
Serving PHP via PHP-FPM
For WordPress or any PHP app:
```bash
apt install php-fpm php-mysql
```
Nginx config:
```nginx
server {
listen 80;
server_name yourdomain.com;
root /var/www/yourdomain.com;
index index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
}
location ~ /\.ht { deny all; }
}
```
Add TLS with Let's Encrypt
```bash
apt install certbot python3-certbot-nginx
certbot --nginx -d yourdomain.com -d www.yourdomain.com
```
Certbot edits your Nginx config to add HTTPS + redirect from HTTP. Auto-renewal is set up. See Let's Encrypt SSL guide.
Essential Nginx tuning
Edit /etc/nginx/nginx.conf:
```nginx
worker_processes auto;
worker_connections 1024;
http {
# Basic security
server_tokens off;
# Compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml;
# Timeouts
client_body_timeout 12;
client_header_timeout 12;
keepalive_timeout 15;
send_timeout 10;
# Client body size (for uploads)
client_max_body_size 32M;
}
```
Test and reload:
```bash
nginx -t
systemctl reload nginx
```
Rate limiting
For login pages, API endpoints, or anywhere brute-force protection matters:
At http {} level:
```nginx
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
```
At server {} level:
```nginx
location /login {
limit_req zone=login burst=3;
# ... your normal location config
}
```
Requests exceeding 5 per minute per IP get a 503. Legitimate traffic isn't affected.
Static asset caching
For static file locations:
```nginx
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
```
Browsers cache these for 1 year; Cloudflare/CDN caches them at the edge.
Common gotchas
"nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)"
- Apache or another server is using port 80. Stop it:
systemctl stop apache2.
"nginx: [error] ... server name too long"
- Increase
server_names_hash_bucket_sizein nginx.conf.
PHP requests return blank pages
- Check
/var/log/nginx/error.log— usually a PHP-FPM socket path mismatch.
Nginx serves the wrong site
- The first
serverblock with a matchingserver_namewins. Make sure your default vhost is not eating your traffic.
502 Bad Gateway on reverse proxy
- Backend app is down or not listening on the expected port. Test with
curl http://localhost:3000first.
Rate limit affects legitimate users
- Lower the rate; use
burst=X nodelayto allow bursts.
Multiple sites on one Nginx (virtual hosts)
Nginx handles many domains on the same box. Each domain gets its own file under /etc/nginx/sites-available/, symlinked into /etc/nginx/sites-enabled/. Nginx routes incoming requests by the Host header. This is how a $4.60/mo Hetzner CX22 can host a personal blog, a portfolio site, and a small SaaS at the same time.
The key rule: every site needs its own server_name line. The first server block Nginx finds with a matching name serves the request. If nothing matches, the default server block does — so make sure your default vhost either returns a friendly 404 or your intended fallback site, not something you'd rather visitors not see.
For a small VPS running 5-10 sites, the overhead of Nginx itself is negligible; each additional site adds essentially nothing to memory usage. What can bite is PHP-FPM: each site with its own FPM pool consumes real memory. Share a pool across low-traffic sites, or use a single pool with per-site directory separation.
The Nginx error log discipline
Every Nginx problem you'll ever have shows up in the error log. Read it before Googling:
```bash
tail -f /var/log/nginx/error.log
```
Common patterns:
connect() failed= your upstream (PHP-FPM, Node app, whatever you're proxying to) is down.Permission denied= SELinux or file permissions problem, usually on the document root.directory index of ... is forbidden= you're missingindex.phpfrom theindexdirective.invalid host in upstream= yourproxy_passuses a hostname that doesn't resolve.
Access log is at /var/log/nginx/access.log. Rotate it — Nginx doesn't do this by default. Ubuntu ships with a logrotate rule (/etc/logrotate.d/nginx); verify it's present and working. Otherwise the access log will eat your disk within months on a busy site.
Related guides
- How to add Let's Encrypt SSL to any VPS — the TLS pairing.
- How to speed up WordPress on any VPS — deep Nginx tuning for WP.
- How to host a Next.js app on a $5 VPS — Nginx as reverse proxy for Node.
- Shared hosting to plain VPS migration — end-to-end pattern.