Linux8 min read

Nginx basics: serving static content

linuxnginxweb-serverreverse-proxy

Nginx is known for handling high concurrency with low memory usage. It started as a static file server and reverse proxy, and it’s excellent at both. For new setups, Nginx is often the better choice over Apache for performance-sensitive workloads.

Installation

sudo apt install nginx
sudo systemctl enable nginx
sudo systemctl start nginx

Verify:

systemctl status nginx
curl -I localhost

Configuration structure

  • /etc/nginx/nginx.conf — main config
  • /etc/nginx/sites-available/ — site configs
  • /etc/nginx/sites-enabled/ — enabled sites (symlinks)
  • /etc/nginx/conf.d/ — additional config files
  • /var/www/html/ — default document root

Server blocks (virtual hosts)

Create a site config:

sudo nano /etc/nginx/sites-available/mysite
server {
    listen 80;
    server_name mysite.com www.mysite.com;
    root /var/www/mysite;
    index index.html;

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

    access_log /var/log/nginx/mysite-access.log;
    error_log /var/log/nginx/mysite-error.log;
}

Enable it:

sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/
sudo nginx -t                  # test config
sudo systemctl reload nginx

Disable the default site:

sudo rm /etc/nginx/sites-enabled/default

Location blocks

Location blocks match URL patterns:

location / {
    # matches everything
}

location /images/ {
    # matches /images/anything
}

location ~ \.php$ {
    # matches URLs ending in .php (regex)
}

location = /favicon.ico {
    # exact match for /favicon.ico
}

Nginx processes locations in order: exact match first, then prefix matches (longest wins), then regex matches (first match wins).

Reverse proxy

Proxy requests to a backend application:

server {
    listen 80;
    server_name app.mysite.com;

    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;
    }
}

This sends all requests to a Node.js (or any) app running on port 3000.

Serving static files efficiently

server {
    listen 80;
    server_name static.mysite.com;
    root /var/www/static;

    location / {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

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

expires sets the Cache-Control and Expires headers. Browser caches static assets and doesn’t re-request them.

HTTPS

server {
    listen 443 ssl;
    server_name mysite.com;
    
    ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem;
    
    # ... rest of config
}

server {
    listen 80;
    server_name mysite.com;
    return 301 https://$host$request_uri;
}

With certbot:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d mysite.com

Performance tuning

Worker processes (usually equal to CPU cores):

worker_processes auto;

Worker connections:

events {
    worker_connections 1024;
}

Gzip compression:

gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 1000;

Open file cache:

open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;

Common configuration snippets

Block specific IPs:

deny 192.168.1.100;
allow all;

Rate limiting:

limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

location /api/ {
    limit_req zone=api burst=20 nodelay;
    proxy_pass http://backend;
}

Custom error pages:

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

Common mistakes

Forgetting nginx -t. Always test the config before reloading. A syntax error takes down all sites.

Not enabling gzip. Text-based content compresses dramatically. It’s free performance.

Missing server_name. Without it, Nginx uses a default server block, which might serve the wrong site.

Proxy headers not set. Without proxy_set_header X-Real-IP, the backend sees 127.0.0.1 for every request.

Remarks

Nginx is fast, efficient, and straightforward to configure. The config syntax is clean and readable. For static files, it’s unbeatable. As a reverse proxy, it’s the standard choice. The biggest gotcha is the config test — always nginx -t before systemctl reload. A bad config takes everything down.

Related Posts