HTTP security headers are a lightweight but highly effective layer of defence for web applications, instructing browsers to enforce policies that mitigate common attacks like clickjacking, MIME-type sniffing, cross-site scripting (XSS), and protocol downgrade attacks. HSTS (HTTP Strict Transport Security) forces browsers to use HTTPS exclusively, while a Content Security Policy (CSP) controls which resources a page may load, blocking injected scripts. This tutorial covers configuring these headers for both Nginx and Apache on RHEL 9, including testing with curl and automated scanning tools. Proper header configuration can significantly improve your score on security scanners such as securityheaders.com.

Prerequisites

  • RHEL 9 server with root or sudo access
  • Nginx (dnf install -y nginx) or Apache (dnf install -y httpd mod_ssl) installed
  • A valid TLS certificate configured (required for HSTS to be meaningful)
  • Basic familiarity with Nginx or Apache configuration syntax

Step 1 — Configure Security Headers in Nginx

Open your Nginx server block configuration file, typically located at /etc/nginx/conf.d/yourdomain.conf or within the default /etc/nginx/nginx.conf. Add the following add_header directives inside the server block. Each header targets a specific class of attack.

server {
    listen 443 ssl;
    server_name example.com;

    # Prevent clickjacking — disallow embedding in iframes
    add_header X-Frame-Options "DENY" always;

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

    # Legacy XSS filter for older browsers
    add_header X-XSS-Protection "1; mode=block" always;

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

    # Restrict access to browser features (camera, geolocation, etc.)
    add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;

    # Remove server version disclosure
    server_tokens off;
}

Test the configuration and reload Nginx:

nginx -t && systemctl reload nginx

Step 2 — Configure HSTS (Strict-Transport-Security) in Nginx

HSTS tells browsers to always connect via HTTPS for the specified duration, preventing SSL stripping attacks. The preload directive allows your domain to be submitted to browser HSTS preload lists, giving protection even on the very first visit. Only add preload after you are confident your entire domain and all subdomains support HTTPS permanently.

server {
    listen 443 ssl;
    server_name example.com;

    # HSTS: enforce HTTPS for 1 year, include all subdomains, allow preloading
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}

# Redirect all HTTP traffic to HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

To submit your domain to the HSTS preload list after confirming full HTTPS readiness, visit hstspreload.org.

Step 3 — Configure a Content Security Policy in Nginx

A Content Security Policy (CSP) is the most powerful XSS mitigation available. It whitelists the sources from which scripts, styles, images, and other resources may be loaded. Start with a restrictive policy and loosen it as needed. The default-src 'self' directive blocks all external resources by default.

server {
    listen 443 ssl;
    server_name example.com;

    # Strict CSP — only allow resources from same origin
    # Adjust sources as needed for your CDN, analytics, fonts, etc.
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none';" always;
}

To develop a CSP without breaking your site, use Content-Security-Policy-Report-Only first, which reports violations without enforcing them:

add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report-endpoint;" always;

Step 4 — Configure Security Headers in Apache

For Apache on RHEL 9, security headers are set using the Header always set directive. Ensure the mod_headers module is loaded. Edit your virtual host configuration file at /etc/httpd/conf.d/yourdomain.conf or in the main /etc/httpd/conf/httpd.conf.


    ServerName example.com

    # Load mod_headers if not already in httpd.conf
    # LoadModule headers_module modules/mod_headers.so

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

    # Remove server version from headers
    ServerTokens Prod
    ServerSignature Off



    ServerName example.com
    Redirect permanent / https://example.com/

Test and reload Apache:

apachectl configtest && systemctl reload httpd

Step 5 — Test Security Headers with curl and Online Tools

Verify the headers are being sent correctly using curl -I to request only the response headers. Each security header should appear in the output.

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

# Expected headers in output:
# strict-transport-security: max-age=31536000; includeSubDomains; preload
# x-frame-options: DENY
# x-content-type-options: nosniff
# x-xss-protection: 1; mode=block
# referrer-policy: strict-origin-when-cross-origin
# content-security-policy: default-src 'self'; ...

# Test with verbose TLS information
curl -v --head https://example.com 2>&1 | grep -E "^< (strict|x-frame|x-content|content-security|referrer)"

For a comprehensive automated analysis, submit your URL to securityheaders.com which grades your header configuration from F to A+. Mozilla Observatory at observatory.mozilla.org provides similar scoring with detailed remediation guidance.

Conclusion

You have configured a full suite of HTTP security headers for both Nginx and Apache on RHEL 9, including HSTS, CSP, X-Frame-Options, and Referrer-Policy. These headers operate at the browser level to neutralise a wide range of injection and protocol-downgrade attacks without requiring application code changes. Regularly review your CSP policy as your application evolves to ensure new resource sources are explicitly whitelisted.

Next steps: How to Set Up TLS 1.3 and Disable Weak Cipher Suites on RHEL 9, How to Configure OCSP Stapling for Faster TLS on RHEL 9, and How to Set Up a Web Application Firewall with ModSecurity on RHEL 9.