
How to Harden Nginx Security: Configuring Essential HTTP Security Headers
A server firewall and standard SSL certificate are not enough to protect a production website. Browsers rely on instructions sent by your web server to determine how to run scripts, prevent malicious layout framing, and encrypt user requests.
By configuring standard HTTP Security Headers on your Nginx server, you can secure your web applications from Cross-Site Scripting (XSS), Clickjacking, Session hijacking, and MIME-type sniffing.
In this guide, we will examine essential security headers and implement a secure configuration setup inside your nginx.conf using Nginx's add_header directive.
1. Strict-Transport-Security (HSTS)
Even if you redirect HTTP to HTTPS, attackers can execute man-in-the-middle exploits (such as SSL stripping) during the initial unencrypted HTTP redirect.
HTTP Strict Transport Security (HSTS) tells the browser that it must only connect to your domain using HTTPS. The browser intercepts all unencrypted links internally and upgrades them to HTTPS before sending network requests.
# Enforce HTTPS for 2 years (63072000 seconds), including subdomains
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;2. Content-Security-Policy (CSP)
A Content Security Policy (CSP) restricts the sources from which the browser is allowed to fetch scripts, images, stylesheets, and fonts. It blocks inline script executions and unauthorized domain connections, preventing XSS attacks.
Here is a basic, strict policy template:
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; frame-ancestors 'none'; object-src 'none';" always;default-src 'self': Restricts resource fetching to your own domain by default.img-src 'self' data:: Allows images from your domain and base64 data strings.frame-ancestors 'none': Prevents other websites from embedding your pages inside iframes, blocking Clickjacking.object-src 'none': Disables flash and old browser plug-ins.
3. X-Frame-Options
This header prevents your website from being embedded inside iframes on other sites, protecting users against Clickjacking attacks (where an attacker overlays an invisible iframe of your site over a button, tricking users into executing clicks).
# Block all iframe embedding attempts globally
add_header X-Frame-Options "DENY" always;4. X-Content-Type-Options
MIME-sniffing is a browser feature where the browser guesses the file type by reading its binary header instead of trusting the server's Content-Type header.
If your site allows user image uploads, an attacker can upload a malicious script file disguised as a .png. If a browser sniffs the file and executes it as JavaScript, it triggers XSS. Setting nosniff disables this behavior.
# Force the browser to respect the declared Content-Type header
add_header X-Content-Type-Options "nosniff" always;5. Referrer-Policy
This header controls what reference information (the URL the user came from) is sent along in headers when a user clicks a link leading to another website.
# Send the full URL on same-origin paths, but only the domain name on external sites
add_header Referrer-Policy "strict-origin-when-cross-origin" always;The Crucial Rule: Always Use the "always" Parameter
When configuring headers in Nginx using add_header, there is an operational trap:
By default, Nginx only includes the declared headers for successful response status codes (200, 201, 204, 301, 302). If your application returns an error (such as a 404 Not Found, 401 Unauthorized, or 500 Internal Server Error), Nginx strips your security headers.
To force Nginx to include the headers across all responses (regardless of status codes), you must append the always parameter at the end of each add_header line.
Combined Nginx Configuration Example
Add this configuration block inside your server block (usually inside /etc/nginx/sites-available/default):
server {
listen 443 ssl http2;
server_name example.com;
# SSL configurations go here...
# Security Headers Block
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; frame-ancestors 'none'; object-src 'none';" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
location / {
proxy_pass http://localhost:3000;
# Proxy headers...
}
}Conclusion
Configuring HTTP security headers on your Nginx server is a simple and highly effective way to harden your system against client-side attacks. By enforcing HSTS for HTTPS connections, establishing strict resource loading rules via CSP, disabling Clickjacking frames with X-Frame-Options, disabling MIME sniffing via X-Content-Type-Options, and utilizing the always parameter to protect error pages, you secure your users and backend services.