Harden NGINX Security and Boost Performance: Step-by-Step Guide

Leaving NGINX at its default settings exposes your website to unnecessary security risks and performance problems. Protecting your server is crucial to safeguard your data and ensure a reliable experience for your visitors.

This guide walks you through practical, easy-to-follow steps to harden your NGINX configuration, making your website both safer and faster.

Prerequisites #

Before you begin, ensure you have:

  • A domain name registered and managed through Cloudflare.
  • A deployed website.
  • Website files located under /var/www/<name>/public_html (adjust path as needed).
  • Basic familiarity with the command line.

Step 1: Modify Main Configuration #

Backup current configuration:

Creating a backup ensures you can quickly restore the original settings if needed.

bash
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak

Open configuration file:

bash
sudo vim /etc/nginx/nginx.conf

Modify:

Snippets make managing NGINX easier by breaking settings into smaller files. This helps you find and change settings faster while keeping your configuration file clean.

/etc/nginx/nginx.conf
# Main NGINX Configuration

user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;

include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 4096;
    multi_accept on;
}

http {
    # Core Settings
    include /etc/nginx/snippets/core.conf;

    # Compression Settings
    include /etc/nginx/snippets/compression.conf;

    # SSL/TLS Settings
    include /etc/nginx/snippets/ssl.conf;

    # Security Headers and Policies
    include /etc/nginx/snippets/security-headers.conf;

    # Rate Limiting and DDoS Mitigation
    include /etc/nginx/snippets/rate-limiting.conf;

    # Logging
    access_log /var/log/nginx/access.log;

    # Virtual Hosts
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Snippet: core.conf #

Create snippet:

bash
sudo vim /etc/nginx/snippets/core.conf

Add:

/etc/nginx/snippets/core.conf
# HTTP Core Behaviour

sendfile on;
tcp_nopush on;
tcp_nodelay on;
types_hash_max_size 2048;
server_tokens off;

# MIME Types
include /etc/nginx/mime.types;
default_type text/plain;

# Client Body and Header Limits
client_max_body_size 10M;
client_body_buffer_size 128K;
client_header_buffer_size 4k;
large_client_header_buffers 4 8k;

# Timeouts
client_body_timeout 10s;
client_header_timeout 10s;
send_timeout 10s;
keepalive_timeout 10s;

Explanation

File Handling and Performance

/etc/nginx/snippets/core.conf
sendfile on;                # Enable efficient file transfer
tcp_nopush on;              # Delay packet transmission to optimise file delivery
tcp_nodelay on;             # Transmit data immediately without waiting
types_hash_max_size 2048;   # Increases MIME type lookup performance
  • What it does: These settings control how files are sent from the server to a visitor.
  • Why it matters: They help send data faster and more efficiently. Think of it as the difference between delivering one package at a time versus bundling them smartly. This reduces delay and makes the website feel faster.

Security by Hiding Server Info

/etc/nginx/snippets/core.conf
server_tokens off;   # Remove Nginx version info from error pages and headers
  • What it does: Hides the version of NGINX being used.
  • Why it matters: Prevents attackers from knowing which version you're running, making it harder for them to find and use known security weaknesses.

File Type Handling

/etc/nginx/snippets/core.conf
include /etc/nginx/mime.types;   # Load list of known file extensions and types
default_type text/plain;         # Use plain text when type is unknown (safe default)
  • What it does: Tells the server what kind of content it's dealing with (images, text, video).
  • Why it matters: Helps the browser display things correctly. If the type isn't known, it defaults to safe plain text, avoiding risks like unintended downloads or script execution.

Limiting Request Size

/etc/nginx/snippets/core.conf
client_max_body_size 10M;           # Set maximum upload size to 10MB
client_body_buffer_size 128K;       # Define buffer size for incoming request body
client_header_buffer_size 4k;       # Define size limit for a single header line
large_client_header_buffers 4 8k;   # Allow four large headers, each up to 8KB
  • What it does: Limits how much data a visitor can send to your site in one go (like file uploads or form data).
  • Why it matters: Prevents someone from overloading the server by sending huge or malformed data. It's like not letting someone bring a shipping container to your front desk.

Timeout Settings

/etc/nginx/snippets/core.conf
client_body_timeout 10s;     # Wait up to 10 seconds for full body from client
client_header_timeout 10s;   # Wait up to 10 seconds for full header from client
send_timeout 10s;            # Wait up to 10 seconds to transmit response to client
keepalive_timeout 10s;       # Hold connection open for reuse for up to 10 seconds
  • What it does: These set how long the server waits for data before giving up.
  • Why it matters: Stops slow or abusive connections from wasting server resources. This keeps things responsive for legitimate users.

Summary

Setting Area Purpose Benefit
File Handling Speeds up data delivery Faster websites
Server Info Hiding Removes version numbers Increases security
MIME Types Defines content types Prevents display errors or exploits
Size Limits Caps data sent by users Blocks abusive uploads
Timeouts Cuts off slow users Frees up server for real visitors

Snippet: compression.conf #

Create snippet:

bash
sudo vim /etc/nginx/snippets/compression.conf

Add:

/etc/nginx/snippets/compression.conf
# Gzip Compression

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_disable "msie6";

gzip_types
    text/plain
    text/css
    image/avif
    image/svg+xml
    application/json
    application/javascript
    application/xml
    application/xml+rss
    text/javascript
    application/font-woff
    application/font-woff2;

Explanation

Enable Compression

/etc/nginx/snippets/compression.conf
gzip on;   # Enable gzip compression
  • What it does: Activates gzip compression on the server.
  • Why it matters: Makes websites load faster by reducing file size.

Adjust Behaviour for Proxies and Clients

/etc/nginx/snippets/compression.conf
gzip_vary on;           # Add 'Vary: Accept-Encoding' for proxy caching
gzip_proxied any;       # Allow compression for all proxied requests
gzip_disable "msie6";   # Disable gzip for Internet Explorer 6 (incompatible)
  • gzip_vary on: Informs proxies and browsers that both compressed and uncompressed versions are available.
  • gzip_proxied any: Allow compression even when requests come through a proxy (e.g. content delivery networks).
  • gzip_disable "msie6": Disable gzip for Internet Explorer 6, which cannot handle it properly.

Set Compression Efficiency and Buffering

/etc/nginx/snippets/compression.conf
gzip_comp_level 6;       # Set moderate compression level (balanced speed and efficiency)
gzip_buffers 16 8k;      # Allocate 16 buffers of 8KB for compressed output
gzip_http_version 1.1;   # Enable gzip only for HTTP/1.1 clients
  • gzip_comp_level 6: Balance between compression strength and CPU usage. Level 6 gives good compression without high cost.
  • gzip_buffers 16 8k: Allocate buffers for compressed data. Helps handle larger responses efficiently.
  • gzip_http_version 1.1: Only apply compression for modern browsers that support HTTP/1.1.

Choose File Types to Compress

/etc/nginx/snippets/compression.conf
gzip_types                    # List file types to compress (text, fonts, scripts, etc.)
    text/plain
    text/css
    image/avif
    image/svg+xml
    application/json
    application/javascript
    application/xml
    application/xml+rss
    text/javascript
    application/font-woff
    application/font-woff2;
  • What it does: Specifies the types of content to compress.
  • Why it matters: Focuses on types that benefit most from compression, like text, code, fonts, and vector images.

Summary

Setting Area Purpose Benefit
Compression On Reduces data size Speeds up site loading
Proxy Handling Ensures compatibility Works with CDNs and older clients
Compression Level Adjusts CPU vs size savings Efficient performance
MIME Types Targets specific content types Optimises what gets compressed

Snippet: ssl.conf #

Create snippet:

bash
sudo vim /etc/nginx/snippets/ssl.conf

Add:

/etc/nginx/snippets/ssl.conf
# TLS Configuration

ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';

ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;

Explanation

TLS Protocol Version

/etc/nginx/snippets/ssl.conf
ssl_protocols TLSv1.3;   # Allow only TLS version 1.3 for stronger security and better speed
  • What it does: Only allows the latest, most secure version of TLS.
  • Why it matters: Older versions have known weaknesses. Using only TLS 1.3 protects against many attacks and improves speed.

Cipher Suites and Preferences

/etc/nginx/snippets/ssl.conf
ssl_prefer_server_ciphers on;   # Force server to choose preferred strong ciphers over client preferences
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';   # Use strong encryption algorithms ensuring confidentiality and integrity
  • What it does: The server chooses the best encryption methods (ciphers) from a secure list.
  • Why it matters: Ensures strong encryption that is hard to break, even if the client supports weaker options.

Session Management

/etc/nginx/snippets/ssl.conf
ssl_session_timeout 1d;             # Set session timeout to one day to balance security and performance
ssl_session_cache shared:SSL:10m;   # Allocate 10MB of shared memory to cache SSL sessions for faster reconnects
ssl_session_tickets off;            # Disable session tickets to prevent replay attacks
  • What it does: Controls how long encrypted sessions last and how session data is stored.
  • Why it matters: Proper session handling improves performance and prevents certain attacks, like replay attacks.

Summary

Setting Area Purpose Benefit
TLS Version Use only TLS 1.3 Maximum security and protection
Cipher Suites Specify strong ciphers and preferences Ensure strong encryption
Session Management Control session timeout and caching Balance speed and security

Snippet: security-headers.conf #

Create snippet:

bash
sudo vim /etc/nginx/snippets/security-headers.conf

Add:

/etc/nginx/snippets/security-headers.conf
# Hardened Security Headers

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
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 Permissions-Policy "camera=(), geolocation=(), microphone=(), payment=()" always;
add_header Cache-Control "public, max-age=3600, must-revalidate";

# Cross-Origin Isolation
add_header Cross-Origin-Resource-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;

Explanation

Strict Transport Security

/etc/nginx/snippets/security-headers.conf
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
  • What it does: Forces browsers to use secure HTTPS connections only.
  • Why it matters: Protects users from attackers trying to intercept or change data.

Frame Options

/etc/nginx/snippets/security-headers.conf
add_header X-Frame-Options "SAMEORIGIN" always;
  • What it does: Prevents your site from being displayed inside a frame on other sites.
  • Why it matters: Stops clickjacking attacks where users are tricked into clicking hidden buttons.

Content Type Options

/etc/nginx/snippets/security-headers.conf
add_header X-Content-Type-Options "nosniff" always;
  • What it does: Stops browsers from guessing content types.
  • Why it matters: Prevents execution of malicious scripts disguised as safe files.

Cross-Site Scripting (XSS) Protection

/etc/nginx/snippets/security-headers.conf
add_header X-XSS-Protection "1; mode=block" always;
  • What it does: Enables basic browser protection against some types of XSS attacks.
  • Why it matters: Blocks detected malicious scripts from running.

Referrer Policy

/etc/nginx/snippets/security-headers.conf
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
  • What it does: Controls how much information about your site is sent when users click links.
  • Why it matters: Protects user privacy by limiting sensitive data exposure.

Permissions Policy

/etc/nginx/snippets/security-headers.conf
add_header Permissions-Policy "camera=(), geolocation=(), microphone=(), payment=()" always;
  • What it does: Restricts access to device features like camera and microphone.
  • Why it matters: Prevents websites from requesting unnecessary or risky permissions.

Cache Control

/etc/nginx/snippets/security-headers.conf
add_header Cache-Control "public, max-age=3600, must-revalidate";
  • What it does: Controls how browsers and proxies cache content.
  • Why it matters: Ensures users get fresh content while optimising load speed.

Cross-Origin Isolation Headers

/etc/nginx/snippets/security-headers.conf
add_header Cross-Origin-Resource-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
  • What it does: Protect against certain web attacks by isolating your site’s resources.
  • Why it matters: Enable advanced browser features safely, like improved security and performance.

Summary

Header Purpose Benefit
Strict-Transport-Security Enforce HTTPS Protects data from interception
X-Frame-Options Prevent framing Stops clickjacking attacks
X-Content-Type-Options Disable content sniffing Avoid execution of malicious files
X-XSS-Protection Enable browser XSS filter Block some cross-site scripting attacks
Referrer-Policy Control referrer info Protect user privacy
Permissions-Policy Restrict device API access Prevent unauthorised access to sensitive features
Cache-Control Manage caching behaviour Balance speed and content freshness
Cross-Origin Policies Enforce resource isolation Improve security and enable modern web features

Snippet: rate-limiting.conf #

Create snippet:

bash
sudo vim /etc/nginx/snippets/rate-limiting.conf

Add:

/etc/nginx/snippets/rate-limiting.conf
# Rate Limiting to Protect Against Abuse

# Define Rate Limit Zone (10 requests/sec per IP)
limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=10r/s;

Explanation

Rate Limiting

/etc/nginx/snippets/rate-limiting.conf
limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=10r/s;
  • What it does: Limits each visitor (identified by their IP address) to a maximum of 10 requests per second.
  • Why it matters: Prevents abusive or excessive use that could slow down or crash the website, like stopping someone from making too many phone calls at once.

Summary

Feature Purpose Benefit
Rate limiting zone Track and limit visitor requests Protect server from overload or abuse
Limit of 10r/s Max 10 requests per second per IP Prevents denial of service or slowdowns

Test and Enforce Rules #

Test configuration and reload NGINX if no errors:

bash
sudo nginx -t && sudo systemctl reload nginx

Step 2: Whitelist Cloudflare IPs #

Whitelisting Cloudflare IPs ensures that only trusted Cloudflare servers can connect directly to your NGINX server. This protects your website from direct attacks, hides your server’s real IP, and helps prevent abuse or malicious traffic bypassing Cloudflare’s security features.

Cloudflare publishes its IP ranges here:

Download and format Cloudflare IP lists:

bash
{
  curl -s https://www.cloudflare.com/ips-v4
  echo ""
  curl -s https://www.cloudflare.com/ips-v6
} | sed 's/^/allow /; s/$/;/' | sudo tee /etc/nginx/cloudflare-ips.conf > /dev/null

Explanation:

  • curl -s https://www.cloudflare.com/ips-v4 fetches IPv4 addresses
  • echo "" ensures a newline between IPv4 and IPv6 entries
  • curl -s https://www.cloudflare.com/ips-v6 fetches IPv6 addresses
  • sed formats each line as allow <IP>; for NGINX syntax
  • sudo tee writes the output with elevated permissions

Verify generated file:

bash
sudo vim /etc/nginx/cloudflare-ips.conf

Open site configuration:

bash
sudo vim /etc/nginx/sites-enabled/example.com

Add:

/etc/nginx/sites-enabled/example.com
server { 
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name example.com;

  # Allow only Cloudflare IP ranges
  include /etc/nginx/cloudflare-ips.conf;

  # Block all other IPs
  deny all;

  ...
}

Test configuration and reload NGINX if no errors:

bash
sudo nginx -t && sudo systemctl reload nginx

Step 3: Verify Website Functionality #

Open in browser:

text
https://example.com