I have configured nginx more times than I care to count. Every new server, every new project, every time I think I remember the syntax ( I don’t ).
nginx is one of those tools that does everything but the docs feel written for people who already know nginx. So here is what I actually use, in production, on every server I manage.

1. One server block per file
This is the single thing that saves me the most headache. One file per site in /etc/nginx/sites-available/, symlink to sites-enabled/. No 500-line monolith.
# /etc/nginx/sites-available/example.com
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/ghost;
index index.html index.htm;
location / {
proxy_pass http://127.0.0.1:2368;
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;
}
}
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}Enable it:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/That is it. One file. One site. Easy to debug, easy to remove, easy to copy when I need another one.
2. SSL settings that actually work
Mozilla SSL Configuration Generator is fine but I keep forgetting the URL. Here is what I actually put in every config:
# /etc/nginx/snippets/ssl.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128.GCM-SHA256:ECDHE-ECDSA-AES256.GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;Then in each server block:
include snippets/ssl.conf;No, I don’t change the ciphers. No, I don’t enable TLSv1.0 for that one old client. The defaults are fine for everything I run.
3. Security headers in one place
Same deal ( one snippet, included everywhere ):
# /etc/nginx/snippets/security.conf
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;Yes, unsafe-inline and unsafe-eval. My blog is not a bank. If you run something more sensitive, tighten the CSP. For most of my stuff this is fine.
Important: add_header inside a location block overrides headers from the server block. Either put all headers in one place or use the always flag. I learned this the hard way.

4. Gzip is not optional
Every server I set up gets this in /etc/nginx/nginx.conf:
gzip on;
gzip_vary on;gzip_proxied any;
gzip_comp_level 6;gzip_min_length 256;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml
application/rss+xml
image/svg+xml;I used to skip gzip because ( the server has fast bandwidth ). Then I checked the logs and realized half my traffic is mobile on shaky connections. Just enable it.
5. Test before reload, always
This is the one rule I break and then immediately regret:
# Do this
sudo nginx -t && sudo systemctl reload nginx
# Not this
sudo systemctl reload nginx # and pray
nginx -t takes 0.2 seconds and tells you if the config is broken before you take the site down. There is no excuse to skip it. I still skip it sometimes. I still regret it.

The config I copy everywhere
Here is my full /etc/nginx/nginx.conf, the one I copy to every new server:
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
}
http {
sendfile on;
tcp_opush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 256;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml
application/rss+xml image/svg+xml;
include /etc/nginx/sites-enabled/*;
}That is it. No magic, no tuning worker_processes to CPU cores ( auto does that ), no custom buffer sizes I don’t understand. It works.
Conclusion
nginx configuration is not hard. It is just badly documented and everyone copies the wrong Stack Overflow answer. One file per site, snippets for shared config, test before reload. Done.
The rest ( rate limiting, caching, load balancing ) is for when you actually need it. Start simple.