NGINX Proxy Manager | HTTP Security Response Headers
For hardening/securing a website which uses NPM (Nginx Proxy Manager) as reverse proxy, its recommended to use security headers. Security headers are like an additional layer of security beside https. For this purpose we are going to implement those headers in our nginx proxy manager configuration which is of course running as Docker Container in our environment.
So basically by adding/enabling security headers, our NPM will inform the browser on client side how to handle specific actions -> those will reduce (among other things) potential attack vectors.
Why to use Security Headers?
Long story short: We are going to use Security headers because of:
- Security improvement:
We are closing vulnerabilities and attack vectors. - Enhancing privacy:
We are controlling how much information are going to be shared and protection user data. - Compliance:
We will fulfill the most common privacy laws/regulations.
I'am using "jc21/nginx-proxy-manager" Docker Image as reverse Proxy, its simple and works stable, furthermore i only need the reverse proxy functionalities of NGINX:
Docker Hub Image
View on Github
Our security related http Headers
We are going to use those security header:
Name | Value | Explanation |
---|---|---|
Strict-Transport-Security | "max-age=63072000;{% if hsts_subdomains == 1 or hsts_subdomains == true -%} includeSubDomains;{% endif %} preload" always | Force/Access only with HTTPS, include all subdomains in the https request, add the site to hsts preload list |
Referrer-Policy | "strict-origin-when-cross-origin" | Limits the amount of information shared with external sites for example it prevents leaking of specific URLs |
X-Permitted-Cross-Domain-Policies | "none" | Blocks other domains from accessing your sites resources. |
X-Content-Type-Options | "nosniff" | Content types specified by the server, ensures that resources are loaded and interpreted as intended. |
X-XSS-Protection | "1; mode=block" | Enables XSS filter on "older" browser. Blocks the page if XSS attack is detected. |
X-Frame-Options | "SAMEORIGIN" | Preventing clickjacking by controlling which sites can embed your page. In this case only your Domain iframes are allowed. |
Content-Security-Policy | upgrade-insecure-requests | Upgrades all resource requests to https. |
Permissions-Policy | "geolocation=(), camera=(), microphone=(), interest-cohort=(), payment=(), clipboard-read=(), clipboard-write=()" | Disallows sensitive features for securing user privacy. |
Expect-CT | 'enforce; max-age=604800' | Helps to enhance the security posture of your site by ensuring that only properly logged certificates are accepted. |
_hsts.conf
To activate mentioned headers for our NPM Docker Container we need to create a file "_hsts.conf" and reference to it within compose file for npm.
The content of the "_hsts.conf" file, copy and paste it:
{% if certificate and certificate_id > 0 -%}
{% if ssl_forced == 1 or ssl_forced == true %}
{% if hsts_enabled == 1 or hsts_enabled == true %}
# HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
add_header Strict-Transport-Security "max-age=63072000;{% if hsts_subdomains == 1 or hsts_subdomains == true -%} includeSubDomains;{% endif %} preload" always;
add_header Referrer-Policy strict-origin-when-cross-origin;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy upgrade-insecure-requests always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), interest-cohort=(), payment=(), clipboard-read=(), clipboard-write=()" always;
add_header Expect-CT 'enforce; max-age=604800' always;
more_set_headers 'Server: Proxy';
more_clear_headers 'X-Powered-By';
{% endif %}
{% endif %}
{% endif %}
I created and placed the file on the same level where my docker-compose.yml file for npm are stored:
NPM Docker Compose file
This is a example docker compose file, as you can see i just added another volume which is referencing to your new created _hsts.conf file with ReadOnly access. This replaces the original file within the Container, we are doing this because of 2 reasons:
- If we would edit to the original _hsts.conf file, our changes would get lost after restarting the container because the file is not declared as persistent.
- NPM cant handle security headers completely at the moment due a bug: Github NPM Bug
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
container_name: nginx-proxy
hostname: nginx-proxy
restart: unless-stopped
env_file:
- .env
ports:
- '8000:80' # Public HTTP Port
- '4433:443' # Public HTTPS Port
- '8181:81' # Admin Web Port
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
- ./_hsts.conf:/app/templates/_hsts.conf:ro
networks:
nginx:
ipv4_address: 172.20.0.2
networks:
nginx:
name: nginx
external: false
driver: bridge
Re-/Enable Security settings withing NPM
As last step login into your npm admin gui, navigate to proxy hosts, select your host, edit, SSL, re-/enable all ssl settings and save.
Congratulations, you added an additional security layer to your website!
Verify
I tested my header settings by visiting this website: https://www.atatus.com/tools/security-header. Another method is using curl:
Before i enabled mentioned security headers:
Afterwards:
References
Props going to Github R0gger for this workaround.
Very good overview: OWASP Cheat Sheet
Cheers!