KBeezie

There's no place like ::1

Menu
  • Home
  • Start Here
  • Security Series
  • About

Serving Static Sites with Nginx

2026/05/11 in Nginx

The Full Configuration

Here's everything together:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name mysite.com;

    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/mysite.com/chain.pem;

    root /var/www/mysite.com/public_html;
    index index.html;

    access_log /var/log/nginx/mysite.access.log;
    error_log /var/log/nginx/mysite.error.log;

    server_tokens off;

    # Security headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header Content-Security-Policy "default-src 'self'; "
        "img-src 'self' https://*; "
        "style-src 'self' 'unsafe-inline'; "
        "script-src 'self'; "
        "font-src 'self'; "
        "frame-ancestors 'none'; "
        "base-uri 'self'; "
        "form-action 'self';" always;

    # Pre-compressed assets
    gzip_static on;

    # Let's Encrypt
    location ~ /.well-known/acme-challenge {
        root /usr/share/nginx/html;
        allow all;
    }

    # Clean URLs — drop .html extension
    location / {
        if ($request_method !~ ^(GET|HEAD)$) {
            return 405;
        }
        try_files $uri $uri.html $uri/ =404;
    }

    # Hashed assets — immutable
    location ~* \.[a-f0-9]{8,}\.(js|css|png|jpg|jpeg|gif|ico|webp|svg|woff2|woff)$ {
        expires max;
        add_header Cache-Control "public, immutable";
    }

    # Non-hashed static — cache with revalidation
    location ~* \.(png|jpg|jpeg|gif|ico|webp|woff2|woff|ttf|svg)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }

    location ~* \.(css|js|xml|txt)$ {
        expires 7d;
        add_header Cache-Control "public, must-revalidate";
    }

    # Housekeeping
    location = /favicon.ico {
        access_log off;
        log_not_found off;
        try_files $uri =204;
    }

    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }

    location ~ ~$ {
        deny all;
        access_log off;
        log_not_found off;
    }

    location ~* \.(yml|yaml|env|bak|swp|dist|config)$ {
        deny all;
        access_log off;
        log_not_found off;
    }

    # Custom error pages
    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;
}

No PHP. No database. No CMS updates. Just Nginx doing what it's always been best at — serving files fast, securely, and without drama.

Technical Audit Summary

This guide is maintained as part of a modular, SSL-first framework. Each configuration is audited for production stability and modern security standards.

Last Audit: May 2026
Nginx: 1.30.0

Compatibility: Tested against current stable releases. While optimized for the stack above, core logic remains relevant for Nginx 1.26+.

  • ← Previous
  • 1
  • 2
  • 3
  • Next →
Tags: nginx, static, hugo, jekyll, eleventy, astro, caching, gzip, brotli
©2026 KBeezie | Disclaimer | Privacy Notice