How to Harden Nginx: Security Headers, TLS 1.3 and OCSP Stapling on RHEL 7

A default Nginx installation exposes unnecessary information about the server software, accepts outdated and broken TLS protocol versions, and omits the HTTP security headers that modern browsers rely on for protection against cross-site scripting, clickjacking, and protocol downgrade attacks. Hardening Nginx is a layered process: you start by reducing the information Nginx leaks about itself, then enforce strong TLS settings, add HTTP response headers that instruct browsers to enforce security policies, and enable OCSP stapling to improve both security and performance during certificate validation. This tutorial covers all of these areas in detail for a RHEL 7 system running Nginx.

Prerequisites

  • RHEL 7 with Nginx installed and running (from EPEL or the official Nginx repository).
  • A valid SSL/TLS certificate installed — either a self-signed certificate or one from Let’s Encrypt / a commercial CA.
  • Root or sudo access.
  • Nginx version 1.13.0 or later for TLS 1.3 support (the Nginx mainline repository provides this).
  • OpenSSL 1.1.1 or later required for TLS 1.3 — note that RHEL 7’s default OpenSSL is 1.0.2 and does not support TLS 1.3. The mainline Nginx packages from the official repo on RHEL 7 bundle their own OpenSSL.

Step 1: Install Nginx from the Official Repository

The EPEL version of Nginx on RHEL 7 is typically 1.12.x, which does not support TLS 1.3. Install from the official Nginx repository to get a modern version:

sudo tee /etc/yum.repos.d/nginx.repo <<'EOF'
[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/rhel/7/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
EOF

sudo yum -y install nginx
nginx -v

You should see Nginx version 1.25.x or later. Enable and start the service:

sudo systemctl enable nginx
sudo systemctl start nginx

Step 2: Disable Server Tokens (Hide Nginx Version)

By default, Nginx includes its version number in HTTP response headers and error pages. This information helps attackers identify the exact version and target known CVEs. Disable it in the main http block in /etc/nginx/nginx.conf:

http {
    server_tokens off;
    ...
}

After reloading Nginx, verify the Server header no longer contains the version:

sudo systemctl reload nginx
curl -I https://yourdomain.com 2>/dev/null | grep -i server

You should see just Server: nginx rather than Server: nginx/1.25.3.

Step 3: Restrict HTTP Methods

Web servers should only accept the HTTP methods required by your application. Methods like TRACE, DELETE, and OPTIONS are often unnecessary and can be exploited. Restrict them in your server block:

server {
    ...
    if ($request_method !~ ^(GET|POST|HEAD)$ ) {
        return 405;
    }
}

If your application uses REST APIs that require PUT, DELETE, or PATCH, adjust the allowed methods accordingly:

if ($request_method !~ ^(GET|POST|HEAD|PUT|DELETE|PATCH)$ ) {
    return 405;
}

Step 4: Configure Strong TLS Settings

Create a dedicated TLS parameters include file to keep your SSL configuration organized and reusable across multiple server blocks. Create /etc/nginx/ssl_params.conf:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_dhparam /etc/nginx/dhparam.pem;

Generate a strong Diffie-Hellman parameters file (this takes a few minutes):

sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048

The ssl_prefer_server_ciphers off setting defers cipher selection to the client when using TLS 1.3, which is the recommended behavior because TLS 1.3 cipher suites are all strong and the client’s preference should be respected. Include this file in your server blocks:

server {
    listen 443 ssl;
    server_name yourdomain.com;
    include /etc/nginx/ssl_params.conf;
    ssl_certificate /etc/ssl/certs/server.crt;
    ssl_certificate_key /etc/ssl/private/server.key;
    ...
}

Step 5: Enable OCSP Stapling

OCSP (Online Certificate Status Protocol) stapling allows Nginx to periodically query the certificate authority’s OCSP responder, cache the response, and include (staple) it in the TLS handshake. This means the client does not need to make a separate OCSP request to verify the certificate has not been revoked — improving both privacy (the CA does not learn which sites the client visits) and connection speed.

Add these directives to your server block or the ssl_params.conf file:

ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

If you are using a certificate chain (intermediate CA certificates), provide the full chain to Nginx. For Let’s Encrypt certificates this would be fullchain.pem rather than just cert.pem. For manually managed certificates, concatenate your certificate with the CA bundle:

cat server.crt ca-bundle.crt > server-fullchain.crt

Then reference the combined file in your Nginx config:

ssl_certificate /etc/ssl/certs/server-fullchain.crt;

Note: OCSP stapling does not work with self-signed certificates because there is no OCSP responder. It is effective only with CA-signed certificates.

Step 6: Add HTTP Security Headers

Create a dedicated headers include file at /etc/nginx/security_headers.conf:

# Prevent clickjacking: page cannot be embedded in iframes from other origins
add_header X-Frame-Options "SAMEORIGIN" always;

# Prevent MIME-type sniffing: browser must use the declared Content-Type
add_header X-Content-Type-Options "nosniff" always;

# Legacy XSS protection (for older browsers not supporting CSP)
add_header X-XSS-Protection "1; mode=block" always;

# Enforce HTTPS for 1 year; include subdomains; allow preload list submission
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# Control what information is sent in the Referer header
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Control browser feature access (microphone, camera, geolocation)
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(self)" always;

# Content Security Policy — adjust sources to match your application's needs
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';" always;

Include this file in your server block:

server {
    listen 443 ssl;
    server_name yourdomain.com;
    include /etc/nginx/ssl_params.conf;
    include /etc/nginx/security_headers.conf;
    ...
}

The always parameter ensures headers are sent even for error responses (4xx, 5xx), not only for 200 OK responses.

Step 7: Add an HTTP to HTTPS Redirect

Ensure all HTTP traffic is permanently redirected to HTTPS. Add a separate server block:

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    return 301 https://$host$request_uri;
}

Step 8: Complete Hardened Server Block

Bring everything together in a single production-ready server block:

server {
    listen 443 ssl;
    http2 on;
    server_name yourdomain.com;

    root /var/www/html;
    index index.html index.php;

    # TLS configuration
    ssl_certificate         /etc/ssl/certs/server-fullchain.crt;
    ssl_certificate_key     /etc/ssl/private/server.key;
    include                 /etc/nginx/ssl_params.conf;

    # OCSP stapling
    ssl_stapling            on;
    ssl_stapling_verify     on;
    resolver                8.8.8.8 8.8.4.4 valid=300s;

    # Security headers
    include /etc/nginx/security_headers.conf;

    # Hide server version
    server_tokens off;

    # Block disallowed HTTP methods
    if ($request_method !~ ^(GET|POST|HEAD)$ ) {
        return 405;
    }

    location / {
        try_files $uri $uri/ =404;
    }
}

Step 9: Validate Configuration and Reload

sudo nginx -t
sudo systemctl reload nginx

Step 10: Verify Headers with curl

Test that all security headers are present in the response:

curl -I https://yourdomain.com

Check the output for all headers you added. You can also use online tools such as securityheaders.com to scan your domain and receive a grade. A fully hardened configuration should score A or A+.

Verify TLS protocol versions are restricted correctly:

# This should fail (TLS 1.1 rejected)
openssl s_client -connect yourdomain.com:443 -tls1_1

# This should succeed (TLS 1.2 accepted)
openssl s_client -connect yourdomain.com:443 -tls1_2

Conclusion

A hardened Nginx configuration on RHEL 7 involves multiple complementary layers: hiding the server version with server_tokens off, restricting HTTP methods to only those your application needs, enforcing TLS 1.2 and 1.3 while disabling older broken protocols, using strong cipher suites, generating custom DH parameters, adding browser-enforced security headers including Strict-Transport-Security, Content-Security-Policy, X-Frame-Options, and X-Content-Type-Options, and enabling OCSP stapling to improve certificate revocation checking. Regularly audit your configuration with tools like Qualys SSL Labs and securityheaders.com, and revisit your Content-Security-Policy as your application changes to ensure it remains both protective and functional.