Nginx basics: serving static content
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
Vim survival guide for Linux admins
The minimum vim knowledge you need to survive on a Linux server. Editing config files without wanting to throw your keyboard.
Linux memory management: free, vmstat, and /proc
Understanding how Linux uses memory. Reading free correctly, using vmstat, and what /proc/meminfo actually tells you.
tmux: terminal multiplexing made simple
Using tmux to manage terminal sessions. Panes, windows, and why your SSH sessions should survive disconnects.