Back to Learn

How to Configure Security Headers | NOC.org

Configuring Security Headers

HTTP security headers provide powerful browser-side protections, but they only work if they are properly configured on your web server. This guide walks through the practical steps for adding each major security header in Apache and Nginx, the two most widely deployed web servers. Whether you manage a single website or hundreds, the configuration patterns are the same.

Before you begin, understand that incorrectly configured security headers can break your website. A Content Security Policy that is too restrictive will block legitimate scripts and styles. An HSTS header applied before your site fully supports HTTPS can lock users out. Always test changes in a staging environment first and roll them out gradually.

Apache Configuration

Apache supports security headers through the mod_headers module. You can add headers in the main server configuration, a virtual host block, or an .htaccess file. To ensure mod_headers is enabled:

sudo a2enmod headers
sudo systemctl restart apache2

Add the following directives to your .htaccess file or virtual host configuration:

Complete Apache .htaccess Example

# HSTS - Enforce HTTPS for 1 year, including subdomains
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

# CSP - Restrict resource loading to same origin and trusted CDNs
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"

# Prevent clickjacking
Header always set X-Frame-Options "DENY"

# Prevent MIME-type sniffing
Header always set X-Content-Type-Options "nosniff"

# Control referrer information
Header always set Referrer-Policy "strict-origin-when-cross-origin"

# Restrict browser features
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(self), payment=()"

The always keyword ensures headers are set on all responses, including error pages. Without it, Apache only sets headers on successful (2xx) responses, leaving error pages unprotected.

Nginx Configuration

Nginx uses the add_header directive, which can be placed in the http, server, or location blocks. Note that add_header directives in a child block completely override those in a parent block, so it is usually best to define all headers at the server level.

Complete Nginx Server Block Example

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

    # HSTS - Enforce HTTPS for 1 year, including subdomains
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # CSP - Restrict resource loading
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;

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

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

    # Control referrer information
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

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

    # ... rest of server configuration
}

The always parameter (available in Nginx 1.7.5+) ensures headers are added to all response codes, including 4xx and 5xx errors. Without it, Nginx only adds headers to responses with codes 200, 201, 204, 206, 301, 302, 303, 304, 307, or 308.

Header-by-Header Configuration Guide

Strict-Transport-Security (HSTS)

HSTS should only be deployed after confirming that your entire site works correctly over HTTPS, including all subdomains if using includeSubDomains. Start with a short max-age and increase it once you are confident:

# Start with 5 minutes for testing
Strict-Transport-Security: max-age=300

# Increase to 1 week
Strict-Transport-Security: max-age=604800

# Production: 1 year with subdomains and preload
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Content-Security-Policy (CSP)

CSP is the most complex header to configure. Start with report-only mode to identify what your site loads without breaking anything:

# Apache - Report-only mode (does not block, only reports)
Header always set Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report-endpoint"

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

Review the violation reports, adjust your policy to allow all legitimate resources, and then switch from Content-Security-Policy-Report-Only to Content-Security-Policy to enforce the policy.

Common CSP directives you will need to configure:

  • default-src: Fallback for all resource types not explicitly specified.
  • script-src: Controls JavaScript sources. Avoid 'unsafe-inline' and 'unsafe-eval' whenever possible.
  • style-src: Controls CSS sources. Many frameworks require 'unsafe-inline' for styles.
  • img-src: Controls image sources. You often need data: for inline images.
  • font-src: Controls web font sources.
  • connect-src: Controls AJAX, WebSocket, and EventSource connections.
  • frame-ancestors: Replaces X-Frame-Options with more granular control over framing.

X-Frame-Options

For most sites, DENY is appropriate. If your site needs to be embedded in iframes on its own pages, use SAMEORIGIN:

# Apache
Header always set X-Frame-Options "SAMEORIGIN"

# Nginx
add_header X-Frame-Options "SAMEORIGIN" always;

X-Content-Type-Options

This header has only one valid value. Always set it:

# Apache
Header always set X-Content-Type-Options "nosniff"

# Nginx
add_header X-Content-Type-Options "nosniff" always;

Ensure all your resources are served with correct MIME types. With nosniff enabled, browsers will refuse to load scripts or styles that have an incorrect content type.

Referrer-Policy

The recommended default is strict-origin-when-cross-origin, which provides a good balance of privacy and functionality:

# Apache
Header always set Referrer-Policy "strict-origin-when-cross-origin"

# Nginx
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Permissions-Policy

Disable features you do not use and restrict others to same-origin:

# Apache
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(self), payment=(), usb=()"

# Nginx
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(self), payment=(), usb=()" always;

Testing Your Configuration

After deploying security headers, verify them using multiple methods:

Command-Line Testing

curl -I https://example.com

This displays the response headers. Verify that all expected security headers are present and have the correct values.

Online Scanners

The most popular tool for testing security headers is securityheaders.com. Enter your URL and it provides a letter grade along with detailed analysis of each header. It identifies missing headers and provides recommendations. Aim for an A or A+ grade.

Other useful tools include:

  • Mozilla Observatory (observatory.mozilla.org): Comprehensive security scan including headers, TLS, and more.
  • Google CSP Evaluator (csp-evaluator.withgoogle.com): Specifically analyzes your CSP for weaknesses.
  • SSL Labs (ssllabs.com): While focused on TLS, it also reports on HSTS configuration.

Browser Developer Tools

Open your browser's developer tools (F12), go to the Network tab, click on any request to your site, and examine the Response Headers section. This shows exactly what headers the browser received.

Common Pitfalls

  • Nginx header inheritance: If you add headers in a location block, all headers from the parent server block are lost. You must repeat all headers in every location block, or use the ngx_http_headers_more_module for more flexible header management.
  • CDN and proxy behavior: Some CDNs and reverse proxies strip or override security headers. After deploying, verify headers from the perspective of the end user, not just the origin server.
  • CSP breaking functionality: Inline scripts, inline styles, eval(), and third-party widgets are common causes of CSP violations. Always start with report-only mode.
  • HSTS with mixed content: Enabling HSTS before fixing all mixed-content issues will cause browsers to block HTTP resources, breaking pages. Ensure everything loads over HTTPS first.
  • Load balancer headers: If you use a WAF or load balancer in front of your server, consider adding security headers there instead of (or in addition to) the origin server.

Summary

Configuring security headers is one of the most impactful steps you can take to improve your website's security. The process is straightforward for most headers: add the appropriate directive to your Apache or Nginx configuration, test with tools like securityheaders.com and browser developer tools, and monitor for any issues. CSP requires more care and should be deployed in report-only mode first. HSTS should be rolled out gradually with increasing max-age values. With all six major security headers in place, your site will be significantly more resilient against client-side attacks.

Need help securing your infrastructure? Explore NOC.org plans to get started.

Improve Your Websites Speed and Security

14 days free trial. No credit card required.