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_size in 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 server block with a matching server_name wins. 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:3000 first.

Rate limit affects legitimate users

  • Lower the rate; use burst=X nodelay to 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 missing index.php from the index directive.
  • invalid host in upstream = your proxy_pass uses 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