What is it? #
Nginx is the web server most commonly placed in front of application servers. It handles TLS, serves static files, and forwards everything else to your application.
Its configuration is a tree of blocks. The http block holds global settings, server blocks handle specific domains, and location blocks handle paths within them.
Configuration files live in /etc/nginx, with sites usually defined in sites-available and activated by a symlink in sites-enabled.
The habit that prevents most incidents is testing the configuration before reloading. nginx -t catches syntax errors while the current configuration keeps running.
Think of it like this #
A receptionist with a set of rules pinned behind the desk: visitors for this company go to floor three, deliveries go to the loading bay, and anyone else gets a polite refusal.
The rules are read top to bottom and the most specific match wins. Changing them mid-shift is fine, as long as someone checks they make sense first.
Simple example #
One server hosts a marketing site and an application. Nginx serves static files directly, proxies the application, redirects HTTP to HTTPS, and handles a large file upload limit.
Code #
/etc/nginx/
├── nginx.conf global settings
├── conf.d/*.conf included globally
├── sites-available/ all site configurations
└── sites-enabled/ symlinks to the active ones
# /etc/nginx/sites-available/example.com
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri; # redirect to HTTPS + canonical
}
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;
root /var/www/example.com/public;
index index.html;
client_max_body_size 20M;
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
access_log /var/log/nginx/example.access.log;
error_log /var/log/nginx/example.error.log warn;
# Static assets: served directly, cached hard
location /static/ {
alias /srv/app/static/;
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# Application
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
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;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
}
# Do not serve hidden files
location ~ /\. { deny all; }
}
# Enable, test, reload — in that order
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t # ALWAYS test first
sudo systemctl reload nginx # no dropped connections
# systemctl restart nginx # only if reload is not enough
# Troubleshooting
sudo tail -f /var/log/nginx/error.log
sudo nginx -T | less # the full effective configuration
curl -I https://example.com
Location matching order
= /exact exact match, highest priority
^~ /prefix prefix match, stops regex evaluation
~ /regex/ case-sensitive regex
~* /regex/ case-insensitive regex
/prefix plain prefix, lowest priority
Nginx picks the most specific match, not the first one written.
How it works #
The first server block handles port 80 and does nothing except redirect. Separating it keeps the HTTPS block focused and guarantees no content is ever served unencrypted.
root sets the base directory for static content; alias in a location replaces the matched prefix with a different path, which is why /static/ maps to /srv/app/static/. Mixing up root and alias is a very common source of 404s.
The proxy block forwards to the application on localhost and passes the headers the application needs to know the real client and protocol. Without X-Forwarded-Proto, an application behind TLS termination builds HTTP redirect URLs.
client_max_body_size defaults to 1 MB, which rejects most file uploads with a confusing 413 error. Setting it explicitly is almost always required.
nginx -t parses the configuration and reports errors without touching the running server. Reloading a broken configuration fails, and on some setups leaves the old one running while you believe the new one is active.
reload applies changes to new connections while existing ones finish, so there is no downtime. restart drops connections and should be reserved for changes that require it.
nginx -T prints the entire effective configuration including all includes, which is how you find where an unexpected setting is coming from.
Real-world use #
Nginx sits in front of most Python, Node and PHP applications, and the configuration above is close to what a real deployment uses.
Hosting several applications on one machine is straightforward: one server block per domain, each proxying to a different local port.
The most frequent problems are permission-related 403 errors, where a directory in the path lacks execute permission for the Nginx user, and 502 errors, which mean Nginx could not reach the application.
Access log analysis is a daily tool, as covered in the logs lesson. Counting status codes and finding slow paths starts there.
For rate limiting, caching and load balancing across several upstreams, Nginx has built-in directives that avoid needing a separate component until the system is considerably larger.
Common mistakes #
- Reloading without running
nginx -tfirst. - Confusing
rootandaliasin location blocks, producing 404s. - Leaving client_max_body_size at the 1 MB default and rejecting uploads.
- Omitting X-Forwarded-Proto, breaking HTTPS-aware redirects in the application.
- Using restart where reload would apply changes without dropping connections.
Practice #
Write a server block that redirects HTTP to HTTPS, serves a static directory with long cache headers, proxies everything else to a local port, and allows 20 MB uploads. Test it with nginx -t, reload, then deliberately break the syntax and confirm the test catches it before any reload.