Nginx Web Server

This guide was written for Ubuntu Server 26.04 LTS (Noble Numbat), Nginx 1.28.3, OpenSSL 3.5.5, and Certbot 5.6.0.

Nginx

Nginx (pronounced “engine x”) is the world’s most popular web server, primarily used for serving static files or acting as a reverse proxy.

Installation

Check for package updates and install nginx:

Terminal window
sudo apt update && sudo apt install nginx

Enable a site

  1. Create a new configuration file for your site, replacing example.com with your domain name:

    Terminal window
    sudo nano /etc/nginx/sites-available/example.com
  2. Copy and paste the following configuration into the file, replacing example.com with your domain name:

    /etc/nginx/sites-available/example.com
    server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    root /var/www/example.com;
    index index.html;
    location / {
    try_files $uri $uri/ =404;
    }
    }

    Press Ctrl + O to save the file and Ctrl + X to exit.

  3. Enable a site by creating a symbolic link from sites-available to sites-enabled, replacing example.com with your domain name:

    Terminal window
    sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
  4. Check the configuration for syntax errors:

    Terminal window
    sudo nginx -t
  5. Reload nginx to apply the changes:

    Terminal window
    sudo systemctl reload nginx
  6. Check the status of nginx to ensure it’s running:

    Terminal window
    sudo systemctl status nginx
  7. Open your web browser and visit http://example.com to confirm that the site is working.

Allow traffic through the firewall

  1. List available application profiles:

    Terminal window
    sudo ufw app list
  2. Allow nginx through the firewall:

    nginx comes installed with pre-configured UFW application profiles that allow traffic on port 80 (Nginx HTTP), port 443 (Nginx HTTPS), or both (Nginx Full).

    Replace Nginx Full with Nginx HTTP or Nginx HTTPS as needed:

    Terminal window
    sudo ufw allow "Nginx Full"

Certbot

Certbot is a free, open-source command line tool that automates the process of obtaining, installing, and renewing SSL/TLS certificates from Let’s Encrypt to enable secure HTTPS connections.

Installation

  1. Install Certbot:

    Terminal window
    sudo snap install --classic certbot
  2. Enable the certbot command:

    Terminal window
    sudo ln -s /snap/bin/certbot /usr/bin/certbot
  3. Confirm the plugin containment level:

    Terminal window
    sudo snap set certbot trust-plugin-with-root=ok
  4. Install the appropriate DNS plugin, replacing cloudflare with the name of your DNS provider:

    Terminal window
    sudo snap install certbot-dns-cloudflare

Configuration

You’ll need to set up DNS credentials.

Follow the steps for the previously installed DNS plugin.

For example, if your DNS provider is Cloudflare, you’d run the following commands:

  1. Create a credentials file:

    Terminal window
    mkdir -p ~/.secrets/certbot
    touch ~/.secrets/certbot/cloudflare.ini
  2. Open the file in a text editor:

    Terminal window
    nano ~/.secrets/certbot/cloudflare.ini
  3. Copy and paste the following code into the text editor:

    ~/.secrets/certbot/cloudflare.ini
    # Cloudflare API token used by Certbot
    dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567
  4. Visit the Cloudflare API Tokens page and create a new User API Token using the API token template Edit zone DNS. Replace the example token with the generated token. If you accidentally leave the page, you’ll need to roll a new token.

  5. Press Ctrl + O to save the file and Ctrl + X to exit.

  6. Secure the new directories and file:

    Terminal window
    chmod 700 ~/.secrets ~/.secrets/certbot
    chmod 600 ~/.secrets/certbot/cloudflare.ini

Obtain a certificate

Run one of the following commands, replacing user with your Linux user and example.com with your domain name:

The official documentation gives ~/.secrets/certbot/cloudflare.ini as the example path to the credentials file, but I’ve found that leads to issues with automatic renewals.

Test automatic renewal

The Certbot packages on your system come with a cron job or systemd timer that will renew your certificates automatically before they expire. You will not need to run Certbot again, unless you change your configuration. You can test automatic renewal for your certificates by running this command:

Terminal window
sudo certbot renew --dry-run

The command to renew certbot is installed in one of the following locations:

Install the certificate

  1. Open the configuration file for your site, replacing example.com with your domain name:

    Terminal window
    sudo nano /etc/nginx/sites-available/example.com
  2. Enable HTTPS by changing the listen port to 443, add ssl parameter, and include the SSL certificate paths. This is also a good opportunity to enable HTTP/2.

    /etc/nginx/sites-available/example.com
    server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com;
    root /var/www/example.com;
    index index.html;
    location / {
    try_files $uri $uri/ =404;
    }
    # SSL certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    }

    Press Ctrl + O to save the file and Ctrl + X to exit.

  3. Check the configuration and reload nginx to apply the changes:

    Terminal window
    sudo nginx -t && sudo systemctl reload nginx

Confirm that Certbot worked

To confirm that your site is set up properly, visit https://example.com/ in your browser and look for the lock icon in the URL bar.

Harden SSL configuration

Global settings

The following settings will enhance SSL performance and protect Perfect Forward Secrecy (PFS).

  1. Open the configuration file:

    Terminal window
    sudo nano /etc/nginx/nginx.conf
  2. Add the following lines:

    /etc/nginx/nginx.conf
    ##
    # SSL Settings
    ##
    ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3 (POODLE), TLS 1.0, 1.1
    ssl_prefer_server_ciphers off; # Don't force server cipher order.
    # TLS session resumption settings (works with TLS 1.2 and 1.3)
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
  3. Press Ctrl + O to save the file and Ctrl + X to exit.

  4. Check the configuration and reload nginx to apply the changes:

    Terminal window
    sudo nginx -t && sudo systemctl reload nginx

Per-site settings

Use the Mozilla SSL Configuration Generator to generate a strong SSL configuration for your server. The following example uses the “Modern” configuration for Nginx 1.28.3 and OpenSSL 3.5.5.

Use the “Intermediate” configuration if you are not running behind a Cloudflare proxy to make sure you support all clients. Do not disable TLS 1.2 on Cloudflare. Things will break like social media platforms or other apps previewing links to your websites.

  1. Verify nginx and openssl versions:

    Terminal window
    nginx -v; openssl version
  2. Open the configuration file for your site, replacing example.com with your domain name:

    Terminal window
    sudo nano /etc/nginx/sites-available/example.com
  3. Add the following lines to the server block, replacing example.com with your domain name:

    /etc/nginx/sites-available/example.com
    server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com;
    root /var/www/example.com;
    index index.html;
    location / {
    try_files $uri $uri/ =404;
    }
    # SSL certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;
    # Modern configuration
    ssl_protocols TLSv1.3;
    ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1:secp384r1;
    ssl_prefer_server_ciphers off;
    }

    HSTS is a security feature that tells browsers to only connect to your site using HTTPS. Once you enable HSTS, you cannot disable it for 63072000 seconds (2 years). Make sure your site is fully functional over HTTPS before enabling HSTS.

  4. Press Ctrl + O to save the file and Ctrl + X to exit.

  5. Check the configuration and reload nginx to apply the changes:

    Terminal window
    sudo nginx -t && sudo systemctl reload nginx

Cloudflare

Restrict HTTPS traffic through the firewall

If you’re using Cloudflare as a proxy server for your website, you can restrict access to your origin server to only allow requests originating from Cloudflare’s IP addresses.

Remove existing rules

If you have existing rules that allow traffic from all IP addresses, you may want to remove them before adding the new rules.

  1. List the current UFW rules as a numbered list:

    Terminal window
    sudo ufw status numbered
  2. Either delete a rule by the rule name or number:

Manually allow Cloudflare IPs

Run the following command to allow traffic from Cloudflare’s IP addresses through the firewall. Replace 192.0.2.1 with each IP address listed on the Cloudflare IP Ranges page.

Terminal window
sudo ufw allow from 192.0.2.1 to any app "Nginx HTTPS" comment "Allow from specific IP"

Using a script

You may automate the process of adding Cloudflare’s IP addresses to UFW by using a script. The following script fetches the latest IP addresses from Cloudflare’s API and adds them to UFW.

  1. Create a file named add_cloudflare_ufw_rules.sh:

    Terminal window
    nano add_cloudflare_ufw_rules.sh
  2. Copy and paste the following code into the file:

    add_cloudflare_ufw_rules.sh
    #!/bin/bash
    # Generated by ChatGPT
    # Cloudflare API URL
    API_URL="https://api.cloudflare.com/client/v4/ips"
    # Function to fetch IPs from Cloudflare API and apply UFW rules
    apply_ufw_rules() {
    echo "Fetching IP list from Cloudflare API..."
    # Fetch the JSON response
    response=$(curl -s "$API_URL")
    # Extract IPv4 and IPv6 ranges using grep, sed, and awk
    ipv4_list=$(echo "$response" | grep -o '"ipv4_cidrs":\[[^]]*' | sed 's/"ipv4_cidrs":\[//' | tr -d '"' | tr ',' '\n')
    ipv6_list=$(echo "$response" | grep -o '"ipv6_cidrs":\[[^]]*' | sed 's/"ipv6_cidrs":\[//' | tr -d '"' | tr ',' '\n')
    # Apply UFW rules for IPv4
    echo "Applying IPv4 rules..."
    for ip in $ipv4_list; do
    if [[ ! -z "$ip" ]]; then
    echo "Adding UFW rule for $ip..."
    sudo ufw allow from "$ip" to any app "Nginx HTTPS" comment "Cloudflare"
    fi
    done
    # Apply UFW rules for IPv6
    echo "Applying IPv6 rules..."
    for ip in $ipv6_list; do
    if [[ ! -z "$ip" ]]; then
    echo "Adding UFW rule for $ip..."
    sudo ufw allow from "$ip" to any app "Nginx HTTPS" comment "Cloudflare"
    fi
    done
    }
    # Apply rules
    apply_ufw_rules
    echo "All UFW rules have been added."
  3. Press Ctrl + O to save the file and Ctrl + X to exit.

  4. Make the script executable and run it:

    Terminal window
    chmod +x add_cloudflare_ufw_rules.sh && ./add_cloudflare_ufw_rules.sh
  5. Verify that the rules were added successfully:

    Terminal window
    sudo ufw status

    You should see something similar to the following output:

    Terminal window
    Status: active
    To Action From
    -- ------ ----
    OpenSSH LIMIT Anywhere # SSH (Rate Limited)
    Nginx HTTPS ALLOW 173.245.48.0/20 # Cloudflare
    Nginx HTTPS ALLOW 103.21.244.0/22 # Cloudflare
    Nginx HTTPS ALLOW 103.22.200.0/22 # Cloudflare
    Nginx HTTPS ALLOW 103.31.4.0/22 # Cloudflare
    Nginx HTTPS ALLOW 141.101.64.0/18 # Cloudflare
    Nginx HTTPS ALLOW 108.162.192.0/18 # Cloudflare
    Nginx HTTPS ALLOW 190.93.240.0/20 # Cloudflare
    Nginx HTTPS ALLOW 188.114.96.0/20 # Cloudflare
    Nginx HTTPS ALLOW 197.234.240.0/22 # Cloudflare
    Nginx HTTPS ALLOW 198.41.128.0/17 # Cloudflare
    Nginx HTTPS ALLOW 162.158.0.0/15 # Cloudflare
    Nginx HTTPS ALLOW 104.16.0.0/13 # Cloudflare
    Nginx HTTPS ALLOW 104.24.0.0/14 # Cloudflare
    Nginx HTTPS ALLOW 172.64.0.0/13 # Cloudflare
    Nginx HTTPS ALLOW 131.0.72.0/22 # Cloudflare
    OpenSSH (v6) LIMIT Anywhere (v6) # SSH (Rate Limited)
    Nginx HTTPS (v6) ALLOW 2400:cb00::/32 # Cloudflare
    Nginx HTTPS (v6) ALLOW 2606:4700::/32 # Cloudflare
    Nginx HTTPS (v6) ALLOW 2803:f800::/32 # Cloudflare
    Nginx HTTPS (v6) ALLOW 2405:b500::/32 # Cloudflare
    Nginx HTTPS (v6) ALLOW 2405:8100::/32 # Cloudflare
    Nginx HTTPS (v6) ALLOW 2a06:98c0::/29 # Cloudflare
    Nginx HTTPS (v6) ALLOW 2c0f:f248::/32 # Cloudflare

Authenticated Origin Pulls

Authenticated Origin Pulls (AOP) helps ensure requests to your origin server come from the Cloudflare network, which provides an additional layer of security on top of Full or Full (strict) encryption modes.

This authentication becomes particularly important with the Cloudflare Web Application Firewall (WAF). Together with the WAF, you can make sure that all traffic is evaluated before receiving a response from your origin server.

  1. Download the Cloudflare certificate and move it to the /etc/nginx/certs directory:

    Terminal window
    wget https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem
    sudo mkdir -p /etc/nginx/certs
    sudo mv authenticated_origin_pull_ca.pem /etc/nginx/certs/cloudflare.crt
  2. Open your configuration file for your site, replacing example.com with your domain name:

    Terminal window
    sudo nano /etc/nginx/sites-available/example.com
  3. Add the following lines to the server block, replacing example.com with your domain name:

    /etc/nginx/sites-available/example.com
    server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com;
    root /var/www/example.com;
    index index.html;
    location / {
    try_files $uri $uri/ =404;
    }
    # SSL certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;
    # Modern configuration
    ssl_protocols TLSv1.3;
    ssl_ecdh_curve X25519:prime256v1:secp384r1;
    ssl_prefer_server_ciphers off;
    # Authenticated Origin Pulls (mTLS)
    ssl_verify_client on;
    ssl_client_certificate /etc/nginx/certs/cloudflare.crt;
    }
  4. Press Ctrl + O to save the file and Ctrl + X to exit.

  5. Check the configuration and reload nginx to apply the changes:

    Terminal window
    sudo nginx -t && sudo systemctl reload nginx

Restoring original visitor IPs

When your website traffic is routed through the Cloudflare network, they act as a reverse proxy. This allows Cloudflare to speed up page load time by routing packets more efficiently and caching static resources (images, JavaScript, CSS, etc.). As a result, when responding to requests and logging them, your origin server returns a Cloudflare IP address.

To restore the original visitor IP addresses in your Nginx logs, you can use the ngx_http_realip_module to set the correct client IP address based on the CF-Connecting-IP header added by Cloudflare.

  1. Open your Nginx configuration file:

    Terminal window
    sudo nano /etc/nginx/nginx.conf
  2. Add the following lines within the http block to set the real IP from Cloudflare:

    /etc/nginx/nginx.conf
    ##
    # Logging Settings
    ##
    set_real_ip_from 173.245.48.0/20;
    set_real_ip_from 103.21.244.0/22;
    set_real_ip_from 103.22.200.0/22;
    set_real_ip_from 103.31.4.0/22;
    set_real_ip_from 141.101.64.0/18;
    set_real_ip_from 108.162.192.0/18;
    set_real_ip_from 190.93.240.0/20;
    set_real_ip_from 188.114.96.0/20;
    set_real_ip_from 197.234.240.0/22;
    set_real_ip_from 198.41.128.0/17;
    set_real_ip_from 162.158.0.0/15;
    set_real_ip_from 104.16.0.0/13;
    set_real_ip_from 104.24.0.0/14;
    set_real_ip_from 172.64.0.0/13;
    set_real_ip_from 131.0.72.0/22;
    set_real_ip_from 2400:cb00::/32;
    set_real_ip_from 2606:4700::/32;
    set_real_ip_from 2803:f800::/32;
    set_real_ip_from 2405:b500::/32;
    set_real_ip_from 2405:8100::/32;
    set_real_ip_from 2a06:98c0::/29;
    set_real_ip_from 2c0f:f248::/32;
    real_ip_header CF-Connecting-IP;
    #real_ip_header X-Forwarded-For;
    log_format custom '$remote_addr - $remote_user [$time_local] '
    '"$request" $status $body_bytes_sent '
    '"$http_referer" "$http_user_agent" '
    '$http_cf_connecting_ip "$http_x_forwarded_for"';
    access_log /var/log/nginx/access.log;
    access_log /var/log/nginx/access.log custom;
    error_log /var/log/nginx/error.log warn;
  3. Press Ctrl + O to save the file and Ctrl + X to exit.

  4. Check the configuration and reload nginx to apply the changes:

    Terminal window
    sudo nginx -t && sudo systemctl reload nginx

Nginx, part 2

Prevent processing requests with undefined server names

When Nginx receives a request that doesn’t match any defined server names, it defaults to the first server block defined in the configuration. This can lead to unintended behavior, such as serving content from the wrong site or exposing sensitive information. To prevent this, you can create a catch-all server block that rejects requests with undefined server names.

If you’re using Cloudflare, you should block all traffic that doesn’t originate from Cloudflare IPs.

server {
# Match all HTTP requests on port 80 and close the connection
listen 80 default_server;
listen [::]:80 default_server;
# Catch-all server name
server_name _;
# Close the connection without sending a response
return 444;
}
server {
# Match all HTTPS requests on port 443 and reject the SSL handshake
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
http2 on;
# Catch-all server name
server_name _;
# Provide a dummy SSL certificate and key for the handshake
# These can be self-signed or a generic certificate.
include snippets/snakeoil.conf;
# Reject the SSL handshake immediately
ssl_reject_handshake on;
}

Disable server tokens

To enhance the security of your Nginx server, you can disable server tokens, which prevents Nginx from revealing its version number in error pages and HTTP headers.

  1. Open your Nginx configuration file:

    Terminal window
    sudo nano /etc/nginx/nginx.conf
  2. Find the server_tokens directive within the http block and set it to off:

    /etc/nginx/nginx.conf
    http {
    ##
    # Basic Settings
    ##
    sendfile on;
    tcp_nopush on;
    types_hash_max_size 2048;
    # server_tokens off;
    server_tokens off;
    ...
    }
  3. Press Ctrl + O to save the file and Ctrl + X to exit.

  4. Check the configuration and reload nginx to apply the changes:

    Terminal window
    sudo nginx -t && sudo systemctl reload nginx

Add common security headers

You can enhance the security of your web server by adding common security headers to your Nginx configuration.

Some of these may be added automatically if you use Cloudflare, but some of the ones added are depreciated and they’re still missing the more modern ones, so it’s still a good practice to include all of them in your server configuration.

  1. Open your configuration file for your site, replacing example.com with your domain name:

    Terminal window
    sudo nano /etc/nginx/sites-available/example.com
  2. Add the following lines to the server block, replacing example.com with your domain name:

    /etc/nginx/sites-available/example.com
    server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com;
    root /var/www/example.com;
    index index.html;
    location / {
    try_files $uri $uri/ =404;
    }
    # Common security headers
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
    add_header Cross-Origin-Opener-Policy "same-origin" always;
    add_header Cross-Origin-Embedder-Policy "require-corp" always;
    add_header Cross-Origin-Resource-Policy "same-origin" always;
    # SSL certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;
    # Modern configuration
    ssl_protocols TLSv1.3;
    ssl_ecdh_curve X25519:prime256v1:secp384r1;
    ssl_prefer_server_ciphers off;
    # Authenticated Origin Pulls (mTLS)
    ssl_verify_client on;
    ssl_client_certificate /etc/nginx/certs/cloudflare.crt;
    }

    Content-Security-Policy is not included in this list because it requires careful configuration based on your website’s specific needs. An incorrect CSP can break functionality on your website. Consider implementing CSP separately after thorough testing.

  3. Press Ctrl + O to save the file and Ctrl + X to exit.

  4. Check the configuration and reload nginx to apply the changes:

    Terminal window
    sudo nginx -t && sudo systemctl reload nginx

For more information on these headers, please visit HTTP headers - HTTP MDN.

Use as a reverse proxy

You may use Nginx as a reverse proxy to terminate HTTPS requests and forward to an insecure application server (e.g., a web application running on a different port or in a Docker container).

  1. Create a new configuration file for your site, replacing example.com with your domain name:

    Terminal window
    sudo nano /etc/nginx/sites-available/example.com
  2. Copy and paste the following configuration into the file, replacing example.com with your domain name and http://localhost:8080 with the address of your application server:

    /etc/nginx/sites-available/example.com
    server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com;
    location / {
    proxy_pass http://localhost:8080;
    # Preserve client information
    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-Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    # RFC7239 Forwarded header (for both IPv4 and IPv6)
    proxy_set_header Forwarded "for=$remote_addr;host=$host;proto=$scheme";
    # Optional WebSocket support
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    }
    # SSL certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;
    # Modern configuration
    ssl_protocols TLSv1.3;
    ssl_ecdh_curve X25519:prime256v1:secp384r1;
    ssl_prefer_server_ciphers off;
    # Authenticated Origin Pulls (mTLS)
    ssl_verify_client on;
    ssl_client_certificate /etc/nginx/certs/cloudflare.crt;
    }

    X-Forwarded-For, X-Forwarded-Host, and X-Forwarded-Proto will eventually be replaced by the Forwarded header as per RFC 7239, but many applications and frameworks still rely on the older headers for compatibility.

    X-Real-IP is non-standard, but widely used by many applications to obtain the client’s real IP address.

    Always consult your application server’s documentation for the recommended proxy settings.

  3. Press Ctrl + O to save the file and Ctrl + X to exit.

  4. Check the configuration and reload nginx to apply the changes:

    Terminal window
    sudo nginx -t && sudo systemctl reload nginx

For more information on these headers, please visit HTTP headers - HTTP MDN.

Have files downloaded as attachments

By default, most web browsers will attempt to open any plain text files as web pages. To have them download everything as an attachment instead:

  1. Open your configuration file for your site, replacing example.com with your domain name:

    Terminal window
    sudo nano /etc/nginx/sites-available/example.com
  2. Add the following lines to the server block, replacing example.com with your domain name:

    /etc/nginx/sites-available/example.com
    server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com;
    root /var/www/example.com;
    index index.html;
    location / {
    try_files $uri $uri/ =404;
    }
    location ^~ /downloads/ {
    # Your existing root / alias / try_files etc. stay the same
    add_header Content-Disposition 'attachment';
    # Optional but recommended – prevents some browsers from still trying to render it
    default_type application/octet-stream;
    }
    # SSL certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;
    # Modern configuration
    ssl_protocols TLSv1.3;
    ssl_ecdh_curve X25519:prime256v1:secp384r1;
    ssl_prefer_server_ciphers off;
    # Authenticated Origin Pulls (mTLS)
    ssl_verify_client on;
    ssl_client_certificate /etc/nginx/certs/cloudflare.crt;
    }
  3. Press Ctrl + O to save the file and Ctrl + X to exit.

  4. Check the configuration and reload nginx to apply the changes:

    Terminal window
    sudo nginx -t && sudo systemctl reload nginx

Use snippets

Nginx snippets are reusable configuration files that can be included in multiple server blocks or locations. They help to keep your configuration DRY (Don’t Repeat Yourself) and make it easier to manage.

Proxy headers snippet

  1. Create a new snippet file for proxy headers:

    Terminal window
    sudo nano /etc/nginx/snippets/proxy-headers.conf
  2. Copy and paste the following lines into the file:

    /etc/nginx/snippets/proxy-headers.conf
    # Preserve client information
    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-Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    # RFC7239 Forwarded header (for both IPv4 and IPv6)
    proxy_set_header Forwarded "for=$remote_addr;host=$host;proto=$scheme";
    # Optional WebSocket support
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  3. Press Ctrl + O to save the file and Ctrl + X to exit.

Security headers snippet

  1. Create a new snippet file for security headers:

    Terminal window
    sudo nano /etc/nginx/snippets/security-headers.conf
  2. Copy and paste the following lines into the file:

    /etc/nginx/snippets/security-headers.conf
    # Common security headers
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
    add_header Cross-Origin-Opener-Policy "same-origin" always;
    add_header Cross-Origin-Embedder-Policy "require-corp" always;
    add_header Cross-Origin-Resource-Policy "same-origin" always;
  3. Press Ctrl + O to save the file and Ctrl + X to exit.

SSL snippet

  1. Create a new snippet file for SSL configuration:

    Terminal window
    sudo nano /etc/nginx/snippets/ssl.conf
  2. Copy and paste the following lines into the file, replacing example.com with your domain name:

    /etc/nginx/snippets/ssl.conf
    # SSL certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;
    # Modern configuration
    ssl_protocols TLSv1.3;
    ssl_ecdh_curve X25519:prime256v1:secp384r1;
    ssl_prefer_server_ciphers off;
    # Authenticated Origin Pulls (mTLS)
    ssl_verify_client on;
    ssl_client_certificate /etc/nginx/certs/cloudflare.crt;
  3. Press Ctrl + O to save the file and Ctrl + X to exit.

Update site configuration

  1. Open your configuration file for your site, replacing example.com with your domain name:

    Terminal window
    sudo nano /etc/nginx/sites-available/example.com
  2. Make the following changes in the file, replacing example.com with your domain name:

    /etc/nginx/sites-available/example.com
    server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com;
    root /var/www/example.com;
    index index.html;
    location / {
    try_files $uri $uri/ =404;
    }
    # Common security headers
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
    add_header Cross-Origin-Opener-Policy "same-origin" always;
    add_header Cross-Origin-Embedder-Policy "require-corp" always;
    add_header Cross-Origin-Resource-Policy "same-origin" always;
    # Security headers snippet
    include snippets/security-headers.conf;
    # SSL certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;
    # Modern configuration
    ssl_protocols TLSv1.3;
    ssl_ecdh_curve X25519:prime256v1:secp384r1;
    ssl_prefer_server_ciphers off;
    # Authenticated Origin Pulls (mTLS)
    ssl_verify_client on;
    ssl_client_certificate /etc/nginx/certs/cloudflare.crt;
    # SSL snippet
    include snippets/ssl.conf;
    }
  3. Press Ctrl + O to save the file and Ctrl + X to exit.

  4. Check the configuration and reload nginx to apply the changes:

    Terminal window
    sudo nginx -t && sudo systemctl reload nginx
  5. Repeat these steps for any other sites using the same configuration.

Enable compression

Gzip

  1. Open your Nginx configuration file:

    Terminal window
    sudo nano /etc/nginx/nginx.conf
  2. Add the following line within the http block to serve pre-compressed gzip files:

    /etc/nginx/nginx.conf
    ##
    # Gzip Settings
    ##
    gzip on;
    gzip_static on;
  3. Uncomment and adjust the gzip_comp_level and gzip_types directives to optimize compression for your specific use case:

    /etc/nginx/nginx.conf
    ##
    # Gzip Settings
    ##
    gzip on;
    gzip_static on;
    # gzip_vary on;
    # gzip_proxied any;
    gzip_comp_level 6;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
  4. Press Ctrl + O to save the file and Ctrl + X to exit.

  5. Check the configuration and reload nginx to apply the changes:

    Terminal window
    sudo nginx -t && sudo systemctl reload nginx

Brotli

  1. Install the Brotli modules for Nginx and verify that they’re installed correctly:

    Terminal window
    sudo apt install libnginx-mod-http-brotli-filter libnginx-mod-http-brotli-static
    sudo ls /usr/lib/nginx/modules/ | grep brotli
    sudo nginx -t && sudo service nginx restart
  2. Open your Nginx configuration file:

    Terminal window
    sudo nano /etc/nginx/nginx.conf
  3. Add the following line within the http block to enable Brotli compression and serve pre-compressed Brotli files:

    /etc/nginx/nginx.conf
    ##
    # Gzip Settings
    ##
    gzip on;
    gzip_static on;
    # gzip_vary on;
    # gzip_proxied any;
    gzip_comp_level 6;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss>
    ##
    # Brotli Settings
    ##
    brotli on;
    brotli_static on;
  4. Press Ctrl + O to save the file and Ctrl + X to exit.

  5. Check the configuration and reload nginx to apply the changes:

    Terminal window
    sudo nginx -t && sudo systemctl reload nginx

References

The following sources were used while writing this guide: