DEV Community

Michael Lip
Michael Lip

Posted on • Originally published at zovo.one

Nginx Configuration Is Not Programming. It Is Incantation.

Nginx config syntax looks like it should be intuitive. It is not. Directives nest in blocks. Order matters in ways the documentation does not emphasize. A misplaced semicolon does not produce an error. It produces unexpected behavior.

The basic server block

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}
Enter fullscreen mode Exit fullscreen mode

This is the minimum viable Nginx configuration for a static site. Every line matters.

listen 80 tells Nginx to accept HTTP connections on port 80.
server_name determines which requests this block handles based on the Host header.
root sets the filesystem path for serving files.
try_files attempts to serve the URI as a file, then as a directory, then returns 404.

HTTPS configuration

server {
    listen 443 ssl http2;
    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_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}
Enter fullscreen mode Exit fullscreen mode

The HTTP-to-HTTPS redirect is a separate server block. This is the part that confuses newcomers. You need two blocks: one for 443 (serving content) and one for 80 (redirecting).

Reverse proxy to a backend

location /api/ {
    proxy_pass http://localhost: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;
}
Enter fullscreen mode Exit fullscreen mode

The trailing slash on proxy_pass matters enormously. With the slash, /api/users proxies to http://localhost:3000/users. Without the slash, it proxies to http://localhost:3000/api/users. This single character causes more Nginx debugging sessions than any other mistake.

Caching headers

location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}
Enter fullscreen mode Exit fullscreen mode

Static assets with cache-busting filenames (like app.abc123.js) should be cached aggressively. HTML files should not be cached or should have short cache times so users get updates.

Common pitfalls

Missing semicolons do not cause syntax errors. They cause the next directive to be interpreted as an argument to the previous one, producing baffling behavior.

Location block order matters. Regex locations are evaluated in order of appearance. Prefix locations use longest-match. Understanding the evaluation order is essential for correct routing.

For generating Nginx configurations with proper syntax for common patterns like static sites, reverse proxies, and SSL, I built a generator at zovo.one/free-tools/nginx-config-generator. Select your use case, fill in the details, and copy the config.


I'm Michael Lip. I build free developer tools at zovo.one. 500+ tools, all private, all free.

Top comments (0)