Back to tutorials
Tutorial

Server Security Headers Configuration Tutorial (2026): Add HSTS, CSP, and Safe Defaults on Nginx or Apache

Server security headers configuration tutorial for Nginx/Apache: HSTS, CSP, XFO, permissions policy, testing, and safe rollouts.

By Anurag Singh
Updated on Jun 09, 2026
Category: Tutorial
Share article
Server Security Headers Configuration Tutorial (2026): Add HSTS, CSP, and Safe Defaults on Nginx or Apache

A surprising number of hosting incidents start with “nothing was hacked.” A site gets framed. A third-party script runs where it shouldn’t. Cookies leak because the browser never got clear rules. This server security headers configuration tutorial shows you how to set those rules at the server layer on Nginx or Apache, with a rollout plan that won’t torpedo production.

You’ll set headers that tighten the browser boundary: HSTS, CSP, clickjacking protection, MIME sniffing prevention, and saner cross-origin behavior.

Then you’ll verify what’s actually being served with curl and browser tooling. You’ll also deploy per site (not as a blunt global default). That keeps multi-site and shared environments predictable.

What you’ll implement (and what it protects)

Security headers aren’t magic. They’re browser policy.

Set them well. You can block whole classes of front-end abuse without touching application code.

  • HSTS forces HTTPS for your domain, stopping SSL stripping and accidental HTTP.
  • CSP reduces XSS impact by limiting where scripts, styles, frames, and images can load from.
  • X-Frame-Options / frame-ancestors blocks clickjacking and malicious framing.
  • X-Content-Type-Options prevents MIME sniffing.
  • Referrer-Policy controls how much URL/referrer data you leak to third parties.
  • Permissions-Policy disables unneeded browser features (camera, mic, etc.).
  • Cross-Origin policies (CORP/COOP/COEP) reduce cross-origin data exposure; use with care.

If you’re doing this on a VPS you manage, plan for predictable access, quick rollbacks, and snapshots.

A HostMyCode VPS gives you root access for Nginx/Apache tuning. managed VPS hosting is a safer fit if you’d rather offload patching and core service upkeep.

Prerequisites and a safe rollout plan

Before you touch CSP or HSTS in production, handle these two items first. They prevent the most common self-inflicted outages.

  1. Confirm HTTPS is already correct for every host you’ll enable HSTS on (main domain, www, app subdomains). If anything still relies on HTTP, fix that first.
  2. Stage policies in “report-only” mode (for CSP) and test for at least a day, ideally through a normal traffic cycle.

Also: don’t enable HSTS preload casually. Preload is hard to undo.

A bad HTTPS change can lock users out until the policy expires.

If you need a clean HTTPS baseline first, follow your existing SSL workflow and automation. For Nginx on Ubuntu, see this Let’s Encrypt SSL setup tutorial.

Quick diagnostic: see your current headers

Run these from your laptop (not the server). They show what browsers actually receive.

curl -I https://example.com
curl -I https://example.com | egrep -i 'strict-transport|content-security|x-frame|x-content-type|referrer|permissions|cross-origin'

For a deeper view, include redirects and HTTP/2:

curl -s -D - -o /dev/null -L --http2 https://example.com

Save this output. After changes, diff it to confirm exactly what changed.

Server security headers configuration tutorial for Nginx (per-site, production-safe)

This section assumes Ubuntu 24.04/26.04-class deployments running Nginx 1.24+ or 1.26+ from your distro or vendor repo.

It also assumes site configs in /etc/nginx/sites-available/ and /etc/nginx/sites-enabled/.

Goal: apply headers at the server { } level for one site first.

Avoid pushing CSP globally across unrelated vhosts. This matters most on multi-tenant servers.

Step 1: create a headers include

Create a dedicated include file. It’s easier to reuse, audit, and roll back.

sudo mkdir -p /etc/nginx/snippets
sudo nano /etc/nginx/snippets/security-headers.conf

Paste this baseline. It stays conservative on purpose.

It fits most brochure sites plus many CMS installs.

# /etc/nginx/snippets/security-headers.conf

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

# Reduce referrer leakage
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Basic clickjacking defense (CSP frame-ancestors is stronger; keep both for older clients)
add_header X-Frame-Options "SAMEORIGIN" always;

# Limit browser feature access
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), usb=(), payment=()" always;

# Cross-origin isolation policies (start conservative; adjust if you embed cross-site resources)
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-site" always;

# HSTS (enable only if HTTPS is correct for this host)
add_header Strict-Transport-Security "max-age=15552000" always;

# CSP: start with Report-Only first (see next step)
# add_header Content-Security-Policy "default-src 'self';" always;
# add_header Content-Security-Policy-Report-Only "default-src 'self'; report-to csp-endpoint;" always;

Why these choices? HSTS starts at 180 days (15552000). That’s a sensible production ramp.

Once you’re confident, raise it to 1 year. The cross-origin headers use a “safer default,” but they don’t force full isolation. COEP is omitted for compatibility.

Step 2: include the snippet in one server block

Open your site config (example: example.com).

sudo nano /etc/nginx/sites-available/example.com

Inside the server { } block for HTTPS (port 443), add:

include /etc/nginx/snippets/security-headers.conf;

Do not add HSTS on the port 80 server block. HSTS only matters over HTTPS.

Keep the HTTP vhost focused on redirects. That makes failures easier to reason about.

Step 3: validate and reload without downtime

sudo nginx -t
sudo systemctl reload nginx

Now re-run your curl check:

curl -I https://example.com | egrep -i 'strict-transport|x-frame|x-content-type|referrer|permissions|cross-origin'

Step 4: add a CSP safely (Report-Only first)

CSP is the setting that breaks WordPress themes, tag managers, and analytics when you guess.

Don’t guess. Watch real violations.

Start with a Report-Only policy. Replace the commented CSP lines in the snippet with something you can evaluate.

Here’s a practical starter CSP for sites that load assets from self plus a CDN and still rely on inline styles (common with page builders):

# Report-Only CSP (tune sources before enforcing)
add_header Content-Security-Policy-Report-Only "default-src 'self'; img-src 'self' data: https:; font-src 'self' data: https:; style-src 'self' 'unsafe-inline' https:; script-src 'self' https:; frame-ancestors 'self'; base-uri 'self'; object-src 'none';" always;

Interpretation: scripts can load from your domain and any HTTPS origins you explicitly allow later. Framing is limited to self.

Plugin-style embedded content (object-src) is blocked.

Reload Nginx again. Then watch the browser console for report-only violations.

If you have a log pipeline, capture violation reports by adding report-uri (deprecated) or report-to with a collector endpoint.

If you don’t run a collector, the browser console is enough for a first pass.

Step 5: enforce CSP after you tune it

Once you understand the violations, allowlist only the sources you actually intend to use.

Then switch to enforcement. Replace Content-Security-Policy-Report-Only with Content-Security-Policy.

Do this in a low-risk window, and keep your rollback ready.

Rollback plan: comment the CSP line, reload Nginx, and you’re back in minutes.

Configure the same headers on Apache (Ubuntu/Debian)

Apache still shows up everywhere: shared hosting stacks, cPanel servers, and older deployments with PHP as FPM or DSO.

You’ll set headers with mod_headers. When possible, set them inside a single vhost. Avoid pushing policies server-wide.

Step 1: enable required modules

sudo a2enmod headers
sudo systemctl reload apache2

Step 2: add headers in your SSL VirtualHost

Edit your SSL vhost file. Paths vary; these are common:

  • /etc/apache2/sites-available/example.com-le-ssl.conf (Let’s Encrypt)
  • /etc/apache2/sites-available/example.com.conf
sudo nano /etc/apache2/sites-available/example.com-le-ssl.conf

Inside the <VirtualHost *:443> block, add:

<IfModule mod_headers.c>
  Header always set X-Content-Type-Options "nosniff"
  Header always set Referrer-Policy "strict-origin-when-cross-origin"
  Header always set X-Frame-Options "SAMEORIGIN"
  Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), usb=(), payment=()"
  Header always set Cross-Origin-Opener-Policy "same-origin"
  Header always set Cross-Origin-Resource-Policy "same-site"
  Header always set Strict-Transport-Security "max-age=15552000"

  # CSP: start with Report-Only
  Header always set Content-Security-Policy-Report-Only "default-src 'self'; img-src 'self' data: https:; font-src 'self' data: https:; style-src 'self' 'unsafe-inline' https:; script-src 'self' https:; frame-ancestors 'self'; base-uri 'self'; object-src 'none';"
</IfModule>

Step 3: validate and reload

sudo apachectl configtest
sudo systemctl reload apache2

Then validate with:

curl -I https://example.com | egrep -i 'strict-transport|content-security|x-frame|x-content-type|referrer|permissions|cross-origin'

Special notes for WordPress and common hosting stacks

WordPress core usually doesn’t need inline scripts. Themes, page builders, and ad/analytics tooling often do.

If CSP blocks admin screens or breaks the front-end editor, you’ll notice immediately.

  • Start with Report-Only and gather violations before enforcing.
  • Expect to allowlist analytics domains (GA4), font CDNs, payment widgets, and embedded video providers.
  • Don’t “fix” CSP by adding 'unsafe-inline' to scripts unless you fully accept the risk trade-off. Prefer nonces/hashes where possible, but that usually needs app changes.

If your main pain point is speed rather than hardening, headers won’t fix it.

Treat headers as part of a broader baseline, alongside caching and tuning.

Pair this with Nginx performance optimization for WordPress/PHP so security work doesn’t become an excuse to ignore latency.

HSTS: the part that can lock you out (so deploy it carefully)

HSTS is simple: you tell browsers “always use HTTPS.” The risk is just as straightforward.

If HTTPS fails later, users can’t click through the warning until HSTS expires.

Use this checklist before increasing HSTS beyond 180 days:

  • Every hostname you serve (apex, www, common subdomains) has a valid certificate.
  • Port 80 redirects to 443 cleanly with a 301.
  • No mixed-content warnings remain on key pages.
  • You can renew certificates automatically (Certbot timer/cron) and you’ve tested renewal.

If you’re planning a migration soon, schedule HSTS after the move, not before.

For a near-zero downtime approach, see this server migration tutorial. Keep policy changes separate from infrastructure changes.

Deploy headers at scale (multi-site VPS, reseller, or dedicated server)

On a server that hosts multiple sites, aim for consistency without surprise breakage.

Recommended structure

  • One baseline include applied to most sites (nosniff, referrer-policy, permissions-policy, basic clickjacking protection).
  • Per-site CSP include because CSP varies wildly by site.
  • Per-site HSTS decision especially if some sites still need transitional hostnames.

On Nginx, that usually means:

/etc/nginx/snippets/security-headers-base.conf
/etc/nginx/snippets/csp-example.com.conf
/etc/nginx/snippets/csp-shop.example.com.conf

Include the base everywhere. Then attach the CSP snippet only where it applies.

Pitfall: duplicate headers

If you set headers in both a reverse proxy layer and the app server, you can ship duplicates (two HSTS lines, two CSP lines).

Browsers handle duplicates inconsistently. Duplicates also make debugging miserable.

Pick one layer to own the policy. On most VPS hosting stacks, that’s the front web server (Nginx/Apache), not the application.

Testing and troubleshooting: what to do when something breaks

Breakage usually shows up as blocked scripts, fonts, iframes, or API calls.

Find the directive first. Then decide whether to allowlist or redesign.

Browser-side checks (fastest)

  • Open DevTools → Console. CSP violations are logged with the blocked URL and directive.
  • Open DevTools → Network. Filter by “blocked” or watch failing requests.

Server-side checks (prove what you’re sending)

# Nginx: verify which config is active
sudo nginx -T | sed -n '1,120p'

# Confirm response headers
curl -s -D - -o /dev/null https://example.com

If the site feels slower after changes, headers aren’t the culprit (they’re tiny).

Hardening often makes you inspect the request path and notice existing bottlenecks.

Use a structured approach: diagnose slow responses with metrics and logs before you start ripping out policies.

Quick hardening checklist you can keep next to your runbook

  • ✅ Add X-Content-Type-Options: nosniff
  • ✅ Add Referrer-Policy: strict-origin-when-cross-origin
  • ✅ Add Permissions-Policy with features you don’t use disabled
  • ✅ Add clickjacking controls (X-Frame-Options and/or CSP frame-ancestors)
  • ✅ Enable HSTS only after HTTPS is stable; start at 180 days
  • ✅ Deploy CSP as Report-Only first; tune, then enforce
  • ✅ Validate with curl after reload; check duplicates through proxy layers
  • ✅ Keep a rollback plan (comment policy, reload, re-test)

Security headers don’t replace patching, firewalls, or SSH controls. They sit next to them.

If your baseline server posture still needs work, apply your SSH and firewall standards.

Keep them consistent across new servers. A good next step is to follow your existing guides for SSH lockdown and host firewalling.

If you want to apply headers like these across multiple sites, you’ll get the cleanest results on a VPS where you control Nginx/Apache configs and can test safely. Start with a HostMyCode VPS, or choose managed VPS hosting if you’d rather have help keeping the server maintained and hardened while you focus on your sites.

FAQ: practical questions before you deploy

Will security headers break my WordPress site?

The baseline headers (nosniff, referrer-policy, permissions-policy, XFO) rarely break WordPress. CSP can break themes/plugins if you enforce it without tuning. Use CSP Report-Only first.

Should I enable HSTS preload?

Only if you meet the preload requirements and you’re confident you can keep HTTPS valid long-term on all subdomains you serve. For most small businesses, HSTS without preload is the safer move.

Do I need both X-Frame-Options and CSP frame-ancestors?

CSP frame-ancestors is the modern control and more flexible. Keeping X-Frame-Options helps older clients and some embedded browser views.

Where should I set these headers if I use a reverse proxy?

Set them at the edge layer that directly answers the browser (often Nginx). Avoid duplicates by removing app-level header injection once the proxy owns policy.

How do I verify I didn’t accidentally send duplicate headers?

Use curl -s -D - -o /dev/null https://example.com and look for repeated header names. If you see duplicates, remove one source (proxy or app) and re-test.

Summary: deploy safe defaults first, then tighten per-site

Start with a baseline set that won’t break normal pages.

Add HSTS only after HTTPS proves stable. Treat CSP like a change request.

Run report-only, tune what you see, then enforce.

That workflow keeps rollbacks simple and your hosting stack predictable.

If you’re standardizing policies across client sites or moving off shared hosting into an environment you control, a HostMyCode VPS gives you the control you need to apply these headers per vhost and validate them against real traffic.

Server Security Headers Configuration Tutorial (2026): Add HSTS, CSP, and Safe Defaults on Nginx or Apache | HostMyCode