# nginx server blocks for the site. # Put the file in # /etc/nginx/sites-available/, link it from sites-enabled, then: # nginx -t && systemctl reload nginx # Logs: the usual files, plus every request as JSON in the journal, from where home fetches them. # Outside any server block, so it covers all of them, including the catch-alls that scanners hit. log_format json_www escape=json '{"time":"$time_iso8601","client":"$remote_addr","host":"$host","method":"$request_method","uri":"$request_uri","protocol":"$server_protocol","status":$status,"bytes":$body_bytes_sent,"referer":"$http_referer","agent":"$http_user_agent","tls":"$ssl_protocol","duration":$request_time}'; access_log syslog:server=unix:/dev/log,tag=nginx_access,nohostname json_www; # Catch-all: requests by IP or with an unknown Host get nothing. server { listen 80 default_server; listen [::]:80 default_server; server_name _; return 444; } server { listen 443 ssl default_server; listen [::]:443 ssl default_server; ssl_reject_handshake on; ssl_protocols TLSv1.2 TLSv1.3; } server { listen 80; listen [::]:80; server_name www.azit.se azit.se; server_tokens off; location /.well-known/acme-challenge/ { root /var/www/letsencrypt; } location / { return 301 https://$host$request_uri; } } # Apex: HTTPS on the same host first (HSTS preload requirement), then to www. server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name azit.se; server_tokens off; ssl_certificate /etc/letsencrypt/live/www.azit.se/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/www.azit.se/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; ssl_session_cache shared:azit:10m; # resume TLS sessions instead of a full handshake ssl_session_timeout 1d; ssl_session_tickets off; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; return 301 https://www.azit.se$request_uri; } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name www.azit.se; root /var/www/homelab; index index.html; ssl_certificate /etc/letsencrypt/live/www.azit.se/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/www.azit.se/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; ssl_session_cache shared:azit:10m; # resume TLS sessions instead of a full handshake ssl_session_timeout 1d; ssl_session_tickets off; server_tokens off; # no version in headers or error pages absolute_redirect off; # /about -> /about/ without spelling out the host charset utf-8; # Compress text. nginx's default compresses only HTML; nothing here is secret, so BREACH does not apply. gzip on; gzip_vary on; gzip_types text/css application/javascript image/svg+xml application/json text/plain application/xml; # Note: an add_header inside a location block would replace all of these, # so the locations below use only "expires". add_header Content-Security-Policy "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; upgrade-insecure-requests" always; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()" always; add_header Cross-Origin-Opener-Policy "same-origin" always; add_header Cross-Origin-Resource-Policy "same-origin" always; # Only GET and HEAD if ($request_method !~ ^(GET|HEAD)$) { return 405; } error_page 404 /404.html; location = /404.html { internal; } # Hidden files stay hidden, except security.txt location = /.well-known/security.txt { default_type text/plain; } location ~ /\. { return 404; } # Stylesheet and script names carry a content hash, so they can be cached for good location /assets/ { expires max; try_files $uri =404; } location /img/ { expires 1d; try_files $uri =404; } # The guides' config files: shown as text, whatever their name location ~ ^/guides/[^/]+/[^/]+/files/ { types { } default_type text/plain; try_files $uri =404; } location / { try_files $uri $uri/ =404; } }