Installation
On Debian and Ubuntu, install Nginx from the official repositories and verify it starts cleanly:
apt update && apt install nginx
systemctl enable --now nginx
# Check that Nginx is listening
ss -tlnp | grep ':80'
# Verify the default page loads
curl -I http://localhost
Nginx's configuration lives under /etc/nginx/. The main file is nginx.conf, and per-site configs go in /etc/nginx/sites-available/, then symlinked into sites-enabled/. Disable the default site: rm /etc/nginx/sites-enabled/default.
Basic Reverse Proxy Configuration
A reverse proxy configuration tells Nginx to forward incoming requests to a backend process - typically listening on a local port. Create /etc/nginx/sites-available/myapp:
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
# Forward the original host and IP to the backend
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;
# Required for WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
Enable the site and test the configuration:
ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Free SSL with Let's Encrypt (Certbot)
Let's Encrypt issues free 90-day TLS certificates. Certbot handles both the issuance and renewal automatically. The Nginx plugin edits your config to add SSL directives and sets up an HTTP-to-HTTPS redirect.
apt install certbot python3-certbot-nginx
# Issue a certificate and auto-configure Nginx
certbot --nginx -d app.example.com
# Test automatic renewal
certbot renew --dry-run
Certbot installs a systemd timer that runs twice daily to check for expiring certificates. Your certificates live in /etc/letsencrypt/live/app.example.com/.
certbot command.Proxying Multiple Services
A single Nginx server can proxy many different backends simultaneously. Use separate server blocks per domain, or path-based routing within a single block:
# Path-based routing to different backends
server {
listen 443 ssl;
server_name platform.example.com;
# API service on port 4000
location /api/ {
proxy_pass http://127.0.0.1:4000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# Frontend on port 3000
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Security Headers
Add these response headers to all sites. Put them in a shared snippet at /etc/nginx/snippets/security-headers.conf and include it in each server block:
# /etc/nginx/snippets/security-headers.conf
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
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 in your server block: include snippets/security-headers.conf;
Then verify with securityheaders.com - aim for an A+ rating.
Enable Gzip Compression
Enable gzip in /etc/nginx/nginx.conf inside the http block to significantly reduce response sizes for text-based content:
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain text/css text/xml
application/json application/javascript application/xml+rss
application/atom+xml image/svg+xml;
After every configuration change, always run nginx -t before reloading. A failed nginx -t means the reload won't happen - your old config stays active - but it also means you won't accidentally take down a running service with a typo.