A minimal nginx config for a static site
Every nginx tutorial I found either stops at "it works" or adds fifty lines of cargo cult. This is what a static site on one domain actually needs: an HTTP block that redirects, an HTTPS block that serves files, a handful of headers, and nothing else.
Layout
/var/www/example.com/ # the site, owned by root, world-readable
/etc/nginx/sites-available/example.com
/etc/nginx/sites-enabled/example.com -> ../sites-available/example.com
Delete sites-enabled/default; it answers on port 80 for any name and will confuse you later.
The config
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# ACME challenges are served here so renewals keep working
location /.well-known/acme-challenge/ {
root /var/www/acme;
}
location / {
return 301 https://example.com$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/privkey.pem;
root /var/www/example.com;
index index.html;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=31536000" always;
location / {
try_files $uri $uri/ =404;
}
error_page 404 /404.html;
}
Two notes on that. http2 on; is the modern form (nginx 1.25+); older versions want listen 443 ssl http2;. And the www name only appears in the port-80 block on purpose: it redirects to the bare domain, so there is exactly one canonical URL for every page.
Headers I did not add
A Content-Security-Policy is worth it once there is JavaScript to constrain. For plain HTML and one stylesheet it adds nothing but a header to get wrong. Same for X-Frame-Options: nobody is framing a notes site, and if they do, fine.
Checking it
sudo nginx -t && sudo systemctl reload nginx
curl -sI http://example.com/ | head -1 # HTTP/1.1 301 Moved Permanently
curl -sI https://example.com/ | head -1 # HTTP/2 200
curl -sI https://example.com/missing | head -1 # HTTP/2 404
Also server_tokens off; in nginx.conf, purely so the version number is not advertised in every response. It is not security; it is just tidy.