HTTP security headers instruct browsers how to handle your site’s content and protect visitors from clickjacking, cross-site scripting, MIME sniffing, and other common attacks. Adding these headers to Nginx or Apache on RHEL 8 is a high-value, low-effort hardening step that improves both your security posture and your score on tools like securityheaders.com. This tutorial covers the most important headers — HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, and Permissions-Policy — with configurations for both Nginx and Apache. All changes are applied without recompiling or installing additional modules.

Prerequisites

  • RHEL 8 server with Nginx or Apache (httpd) installed and serving HTTPS traffic
  • A valid TLS certificate (HSTS requires HTTPS)
  • Root or sudo access
  • curl installed for header testing
  • firewalld allowing ports 80 and 443

Step 1 — Install Nginx or Apache on RHEL 8

Install your preferred web server if not already present, and enable the service.

# Nginx
dnf install -y nginx
systemctl enable --now nginx
firewall-cmd --permanent --add-service=http --add-service=https
firewall-cmd --reload

# Apache (httpd)
dnf install -y httpd mod_ssl
systemctl enable --now httpd

Step 2 — Add Security Headers in Nginx

Edit your Nginx server block (typically in /etc/nginx/conf.d/yourdomain.conf) and add the header directives inside the server { } block. All headers should be served over HTTPS.

server {
    listen 443 ssl;
    server_name example.com www.example.com;

    # Strict-Transport-Security: force HTTPS for 1 year, include subdomains
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # Content-Security-Policy: restrict resource origins (adjust for your app)
    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'; frame-ancestors 'none';" always;

    # Prevent clickjacking
    add_header X-Frame-Options "SAMEORIGIN" always;

    # Prevent MIME type sniffing
    add_header X-Content-Type-Options "nosniff" always;

    # Control referrer information sent to other origins
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Disable unused browser features
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()" always;

    # Remove the server version banner
    server_tokens off;
}
nginx -t
systemctl reload nginx

Step 3 — Add Security Headers in Apache

Apache requires the mod_headers module, which is loaded by default in RHEL 8’s httpd package. Add directives to your VirtualHost configuration or to /etc/httpd/conf.d/security.conf.


    ServerName example.com

    # Confirm mod_headers is loaded
    LoadModule headers_module modules/mod_headers.so

    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none';"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set X-Content-Type-Options "nosniff"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"

    # Suppress server version
    ServerTokens Prod
    ServerSignature Off
apachectl configtest
systemctl reload httpd

Step 4 — Test Headers with curl

Verify the response headers are present and correctly formatted using curl -I (fetch headers only).

# Fetch only response headers
curl -sI https://example.com

# Filter for security-relevant headers
curl -sI https://example.com | grep -iE 
  "strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy"

# Test HTTP → HTTPS redirect
curl -sI http://example.com | grep -i location

Each header should appear exactly once. Multiple add_header directives in nested Nginx blocks can cause headers to be omitted — use the always keyword to ensure headers are sent with all response codes including 4xx and 5xx.

Step 5 — Refine Your Content-Security-Policy

An overly broad CSP undermines its protections. Use report-only mode first to identify violations without breaking your site.

# Nginx — CSP Report-Only mode for auditing
add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report-endpoint;" always;

# After reviewing reports, tighten the policy and switch to enforcing mode
# Replace report-only header with the enforcing version:
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; frame-ancestors 'none'; upgrade-insecure-requests;" always;

Step 6 — Automate HSTS Preload Submission

Once HSTS is deployed and stable, submit your domain to the browser preload list so users are protected even on their first visit before any HSTS header is received.

# Verify the preload directive is present in your HSTS header
curl -sI https://example.com | grep -i strict-transport
# Expected output:
# Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

# Submit to https://hstspreload.org
# Requirements: max-age >= 31536000, includeSubDomains, preload, all subdomains HTTPS

Conclusion

Adding HTTP security headers to Nginx or Apache on RHEL 8 is one of the fastest ways to harden a web server against common browser-based attacks. HSTS prevents protocol downgrade attacks, CSP limits injection attack surfaces, and X-Frame-Options blocks clickjacking. After deploying, validate your configuration with curl -I and an online scanner such as securityheaders.com to confirm all headers are present and correctly scoped.

Next steps: How to Configure DNSSEC on RHEL 8, How to Encrypt Disk Partitions with LUKS on RHEL 8, and How to Scan for Vulnerabilities with OpenVAS on RHEL 8.