Nginx Cheatsheet: Commands, Config Snippets and Error Fixes

10 min read·Matthieu

A quick reference for daily Nginx operations on Debian 12 and Ubuntu 24.04. Organized by task so you find what you need fast. For a full walkthrough, see Nginx Administration on a VPS.

How do I manage the Nginx service?

Use systemctl to control Nginx through systemd, or send signals directly with nginx -s. Systemctl is the standard on modern Debian and Ubuntu. The native nginx -s commands talk directly to the master process via its PID file. Both work. Systemctl is better for automation and boot persistence.

Signal and command mapping

Action systemctl command nginx -s equivalent Unix signal What happens to workers
Start sudo systemctl start nginx (not applicable) - Master starts, spawns workers
Stop (graceful) sudo systemctl stop nginx sudo nginx -s quit SIGQUIT Workers finish current requests, then exit
Stop (immediate) sudo systemctl kill nginx sudo nginx -s stop SIGTERM Workers drop connections and exit
Reload config sudo systemctl reload nginx sudo nginx -s reload SIGHUP New workers spawn with new config. Old workers finish their requests, then exit. No dropped connections.
Reopen logs (not built-in) sudo nginx -s reopen SIGUSR1 Workers reopen log file descriptors. Use after log rotation.
Enable at boot + start now sudo systemctl enable --now nginx (not applicable) - Creates symlink for boot, starts immediately
Disable + stop sudo systemctl disable --now nginx (not applicable) - Removes boot symlink, stops immediately

enable --now makes Nginx survive reboots and starts it immediately. Always prefer this over plain start.

Reload vs restart

reload sends SIGHUP. The master process reads the new config, spawns new workers, and lets old workers drain their active connections. Zero downtime.

restart sends SIGTERM (stop), then starts fresh. All active connections drop. Use restart only when changing listen ports, loading new modules, or upgrading the Nginx binary.

Always test before reloading:

sudo nginx -t && sudo systemctl reload nginx

If nginx -t fails, the reload never fires. Your live config stays untouched.

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

After the reload:

sudo systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since Thu 2026-03-20 10:15:32 UTC; 2s ago

The enabled in the Loaded line means it will start on boot. Install Nginx on Debian 12 and Ubuntu 24.04 from the Official Repository

How do I test and inspect the Nginx configuration?

Run nginx -t to validate syntax without touching the running server. Run nginx -T to validate and dump the full parsed config to stdout. Run nginx -V to see compile-time modules and flags.

Command Purpose
sudo nginx -t Test config syntax, check file references exist
sudo nginx -t -q Same test, suppress non-error output (useful in scripts)
sudo nginx -T Test + dump the entire parsed config to stdout
sudo nginx -V Show version, compiler, configure arguments, built-in modules
sudo nginx -v Show version number only

Dump and search the running config

sudo nginx -T 2>/dev/null | grep -A5 "server_name example.com"

This outputs the full config (all included files merged), then filters for a specific server block. Faster than opening files manually when you have dozens of includes.

Check which modules are compiled in

sudo nginx -V 2>&1 | tr ' ' '\n' | grep module
--with-http_ssl_module
--with-http_v2_module
--with-http_realip_module
--with-http_gzip_static_module
--with-http_stub_status_module

You cannot use a directive if its module is not compiled in. This is the first thing to check when a directive causes "unknown directive" errors.

Where are the Nginx config and log files?

On Debian 12 and Ubuntu 24.04, the package manager installs everything under /etc/nginx/. Logs go to /var/log/nginx/. Here is the full layout.

Path Purpose
/etc/nginx/nginx.conf Main config. Sets worker count, events, http block, includes
/etc/nginx/sites-available/ Server block files (available but not necessarily active)
/etc/nginx/sites-enabled/ Symlinks to sites-available. Nginx loads these.
/etc/nginx/conf.d/ Drop-in config fragments. Loaded by default include in nginx.conf
/etc/nginx/snippets/ Reusable config snippets (SSL params, security headers)
/etc/nginx/mime.types MIME type mappings
/var/log/nginx/access.log Request log
/var/log/nginx/error.log Error log
/run/nginx.pid PID file for the master process

To enable a site:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

To disable a site:

sudo rm /etc/nginx/sites-enabled/example.com
sudo nginx -t && sudo systemctl reload nginx

For a deep dive into the directory structure, see Nginx Config File Structure Explained.

What are the most common Nginx config snippets?

Each snippet below is a minimal working example. Copy, adapt the values, test with nginx -t, reload. For full guides on each topic, follow the internal links.

How do I set up a basic server block?

A server block (virtual host) ties a domain to a document root. Place this in /etc/nginx/sites-available/example.com.

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/example.com/html;
    index index.html;

    server_tokens off;

    location / {
        try_files $uri $uri/ =404;
    }
}

server_tokens off hides the Nginx version from response headers. Version disclosure helps attackers target known vulnerabilities.

Symlink it into sites-enabled and reload. Nginx Server Blocks: Host Multiple Domains on One VPS

How do I redirect HTTP to HTTPS?

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    return 301 https://$host$request_uri;
}

return 301 is faster than rewrite for full-URL redirects. Nginx processes return before touching the filesystem.

How do I configure Nginx as a reverse proxy?

Forward requests to a backend running on port 3000. Place this in the HTTPS server block.

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;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Trailing slash matters. proxy_pass http://127.0.0.1:3000; (no trailing slash) passes the full original URI. proxy_pass http://127.0.0.1:3000/; (with trailing slash) strips the matched location prefix. This is the source of many broken proxy setups.

How to Configure Nginx as a Reverse Proxy

How do I enable gzip compression?

Add to the http {} block in /etc/nginx/nginx.conf or a snippet file:

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_min_length 1024;
gzip_comp_level 5;
gzip_types
    text/plain
    text/css
    text/javascript
    application/json
    application/javascript
    application/xml
    image/svg+xml;

gzip_min_length 1024 skips files under 1 KB. Compressing tiny files adds CPU overhead without meaningful size reduction. gzip_comp_level 5 is a good balance between compression ratio and CPU cost. Going above 6 gives diminishing returns.

Nginx Performance Tuning on a VPS

How do I add rate limiting?

Define a zone in the http {} block, then apply it in a location or server block.

# In http {} block
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

# In server or location block
location /api/ {
    limit_req zone=api burst=20 nodelay;
    proxy_pass http://127.0.0.1:8080;
}

$binary_remote_addr uses 4 bytes per IPv4 address. A 10 MB zone holds about 160,000 addresses. burst=20 allows short spikes. nodelay serves burst requests immediately instead of queuing them.

Nginx Rate Limiting and DDoS Protection

How do I proxy WebSocket connections?

location /ws/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 86400s;
}

proxy_http_version 1.1 is required. HTTP/1.0 does not support the Upgrade header. proxy_read_timeout 86400s keeps idle WebSocket connections open for 24 hours instead of the 60-second default.

How do I set up custom error pages?

server {
    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;

    location = /404.html {
        root /var/www/errors;
        internal;
    }

    location = /50x.html {
        root /var/www/errors;
        internal;
    }
}

The internal directive prevents direct access to the error page URLs. Without it, users could browse to /404.html directly.

How do I add security headers?

Create /etc/nginx/snippets/security-headers.conf:

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

Include it in any server block:

include snippets/security-headers.conf;

The always parameter adds headers even on error responses (4xx, 5xx). Without it, Nginx only adds them on 2xx/3xx. Nginx Security Hardening on Ubuntu and Debian

Minimal TLS server block

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    server_tokens off;

    # ... location blocks
}

Do not enable TLSv1 or TLSv1.1. Both have known vulnerabilities and are rejected by modern browsers. Set Up Let's Encrypt SSL/TLS for Nginx on Debian 12 and Ubuntu 24.04

How do I read and debug Nginx logs?

Nginx writes two logs by default: access.log for every request and error.log for problems. Both live in /var/log/nginx/.

Tail logs in real time

sudo tail -f /var/log/nginx/error.log

Or through journald:

journalctl -u nginx -f

Error log severity levels

The error_log directive accepts a level. From most to least verbose:

debug > info > notice > warn > error > crit > alert > emerg

The default is error. To enable debug logging temporarily:

error_log /var/log/nginx/error.log debug;

Reload Nginx. Debug logging is extremely verbose. Disable it after diagnosing the issue or it will fill your disk.

JSON access log format

Structured logs are easier to parse with tools like jq, Loki, or OpenObserve.

log_format json_combined escape=json
    '{'
        '"time":"$time_iso8601",'
        '"remote_addr":"$remote_addr",'
        '"method":"$request_method",'
        '"uri":"$request_uri",'
        '"status":$status,'
        '"body_bytes_sent":$body_bytes_sent,'
        '"request_time":$request_time,'
        '"upstream_response_time":"$upstream_response_time",'
        '"http_user_agent":"$http_user_agent"'
    '}';

access_log /var/log/nginx/access.log json_combined;

Debugging toolkit

Command What it does
curl -I https://example.com Show response headers only. Check status code, server version, cache headers.
curl -v https://example.com 2>&1 | head -30 Verbose output: TLS handshake, request/response headers.
sudo nginx -T 2>/dev/null | grep server_name List all configured server names across all config files.
sudo ss -tlnp | grep nginx Show which ports/addresses Nginx is listening on.
sudo ls -la /var/log/nginx/ Check log file sizes and permissions.

Enable stub_status for monitoring

location /nginx_status {
    stub_status;
    allow 127.0.0.1;
    allow ::1;
    deny all;
}
curl http://127.0.0.1/nginx_status
Active connections: 3
server accepts handled requests
 1542 1542 4890
Reading: 0 Writing: 1 Waiting: 2

Restrict stub_status to localhost or your monitoring IP. It exposes server load information.

What do Nginx error codes mean and how do I fix them?

When Nginx returns an HTTP error, the error.log tells you what happened. Here are the most common codes, what they mean, and how to fix them.

Code Name Typical error.log message Common cause Fix
403 Forbidden directory index of "/var/www/html/" is forbidden Missing index file, wrong file permissions, autoindex off (default) Add an index.html, fix permissions (chmod 644 for files, 755 for dirs), or enable autoindex on
404 Not Found open() "/var/www/html/page" failed (2: No such file or directory) Wrong root path, wrong try_files, file does not exist Check the root directive, verify the file path on disk
413 Request Entity Too Large client intended to send too large body Upload exceeds client_max_body_size (default: 1 MB) Set client_max_body_size 50m; in the server or location block
502 Bad Gateway connect() failed (111: Connection refused) while connecting to upstream Backend not running, wrong port/socket in proxy_pass Start the backend, check the port matches proxy_pass
503 Service Unavailable no live upstreams while connecting to upstream All backends in an upstream block are down or marked as down Start at least one backend, check health check config
504 Gateway Timeout upstream timed out (110: Connection timed out) while reading response header Backend is too slow to respond Increase proxy_read_timeout, optimize the backend, check backend logs

Diagnosing a 502

The 502 is the most frequent proxy error. Step through this:

# 1. Is the backend running?
sudo ss -tlnp | grep 3000

# 2. Can Nginx reach it?
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/

# 3. What does the error log say?
sudo tail -20 /var/log/nginx/error.log

If ss shows nothing on port 3000, the backend is down. If curl returns a response but Nginx returns 502, check for socket permission issues (common with PHP-FPM or Gunicorn Unix sockets).

What are the most common Nginx mistakes?

These cause most of the "I changed the config and now it's broken" moments.

Missing semicolons

Every directive must end with a semicolon. Nginx gives a clear error:

nginx: [emerg] unexpected "}" in /etc/nginx/sites-enabled/example.com:12

The error points to the line after the missing semicolon, not the line with the problem. Look one line up.

Confusing root and alias

# root: appends the location to the path
location /images/ {
    root /var/www;
    # serves /var/www/images/photo.jpg
}

# alias: replaces the location with the path
location /images/ {
    alias /var/www/media/;
    # serves /var/www/media/photo.jpg
}

With alias, the trailing slash is required on both the location and the alias path. Missing it causes 404s with no obvious reason in the error log.

Location match order confusion

Nginx evaluates locations in this order, regardless of where they appear in the config file:

  1. = /exact - Exact match. Checked first. If matched, stops immediately.
  2. ^~ /prefix - Preferential prefix. Longest match wins. If matched, skips all regex.
  3. ~ regex - Case-sensitive regex. Evaluated top to bottom. First match wins.
  4. ~* regex - Case-insensitive regex. Same top-to-bottom order.
  5. /prefix - Standard prefix. Longest match wins. Only used if no regex matched.

Prefix match length matters, not config file order. Regex config file order matters, not match length. Mixing them without understanding this creates unpredictable routing.

Trailing slash in proxy_pass

# No trailing slash: passes /app/foo to backend as /app/foo
location /app/ {
    proxy_pass http://127.0.0.1:3000;
}

# Trailing slash: strips /app/ and passes /foo to backend
location /app/ {
    proxy_pass http://127.0.0.1:3000/;
}

Pick one and be consistent. Most backends expect the full path (no trailing slash on proxy_pass).

Forgetting nginx -t before reload

If you reload with a broken config, Nginx keeps running with the old config and logs an error. It does not crash. But you now have a config on disk that does not match the running config. This causes confusion later.

Build the habit: sudo nginx -t && sudo systemctl reload nginx. The && ensures reload only runs if the test passes.

Editing sites-available but not symlinking

Files in /etc/nginx/sites-available/ are not loaded automatically. You must symlink them to /etc/nginx/sites-enabled/. A direct copy works too, but symlinks keep a single source of truth.

Something went wrong?

Quick diagnostic sequence when Nginx is misbehaving:

# Check if Nginx is running
sudo systemctl status nginx

# Test the config
sudo nginx -t

# Check which config is actually loaded
sudo nginx -T 2>/dev/null | head -50

# Check the last 30 error log entries
sudo tail -30 /var/log/nginx/error.log

# Check what ports Nginx is listening on
sudo ss -tlnp | grep nginx

# Check file permissions on the web root
sudo ls -la /var/www/example.com/html/

If the service is failed, journalctl -u nginx --no-pager -n 50 gives the full story. Look for [emerg] entries.