Stack guide
Django security headers — settings.py and django-csp essentials
Django gives you most of the security-header baseline through `settings.py` flags — no third-party packages required for HSTS, redirect-to-HTTPS, X-Content-Type-Options, X-Frame-Options, or Referrer-Policy. CSP is the one that needs an add-on (`django-csp`), and it's worth installing because Django's auth + admin templates use enough inline JS that hand-rolling CSP middleware is a foot-gun.
1. The settings.py block that handles most of the baseline
Add the `SECURE_*` flags to `settings.py` once and you've covered HSTS, the HTTPS redirect, X-Content-Type-Options, X-Frame-Options, and Referrer-Policy. The most-skipped flag is `SECURE_HSTS_PRELOAD = True` — leave it off until the rest of HSTS has been live for a quarter and every subdomain is HTTPS-ready, then submit at hstspreload.org.
HSTS missing — nginx (server / location) (nginx)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # If TLS terminates at a CDN, set the header there instead.
HSTS missing — Express (helmet) (typescript)
import helmet from "helmet";
app.use(helmet.hsts({ maxAge: 31_536_000, includeSubDomains: true }));HSTS missing — Apache (vhost) (apache)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options missing — nginx (nginx)
add_header X-Frame-Options "DENY" always; # Use SAMEORIGIN instead of DENY only if same-origin framing is required.
X-Frame-Options missing — Express (helmet) (typescript)
import helmet from "helmet";
app.use(helmet.frameguard({ action: "deny" }));X-Frame-Options missing — Apache (apache)
Header set X-Frame-Options "DENY"
X-Content-Type-Options missing — nginx (nginx)
add_header X-Content-Type-Options "nosniff" always;
X-Content-Type-Options missing — Express (helmet) (typescript)
import helmet from "helmet"; app.use(helmet.noSniff());
X-Content-Type-Options missing — Apache (apache)
Header set X-Content-Type-Options "nosniff"
HTTP not redirected — nginx (HTTP → HTTPS) (nginx)
server {
listen 80;
return 301 https://$host$request_uri;
}HTTP not redirected — Express (typescript)
app.use((req, res, next) => {
if (req.secure) return next();
res.redirect(301, "https://" + req.headers.host + req.url);
});HTTP not redirected — Apache (apache)
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]Related Scorifya checks: missing_hsts, weak_hsts, missing_xfo, missing_xcto, missing_referrer_policy, http_no_redirect
2. Install django-csp for Content-Security-Policy
`pip install django-csp`, add `csp.middleware.CSPMiddleware` to `MIDDLEWARE`, and configure `CSP_*` settings in `settings.py`. Start with `CSP_REPORT_ONLY = True` and a `CSP_REPORT_URI` pointing at a logging endpoint. Run report-only through one full release cycle — Django's admin alone will surface a handful of inline-style violations you'll want to fix or whitelist before enforcing.
CSP missing — nginx (nginx)
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'self'" always;
CSP missing — Express (helmet) (typescript)
import helmet from "helmet";
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
baseUri: ["'self'"],
formAction: ["'self'"],
},
}),
);CSP missing — Apache (apache)
Header set Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'self'"
Related Scorifya checks: missing_csp
3. Per-view CSP overrides
Most Django sites have one or two views that need a looser CSP — an embed page, an admin pivot, an old reporting dashboard. Use the `@csp_update` and `@csp_replace` decorators from django-csp to scope the relaxation. This keeps the baseline tight everywhere else.
Weak CSP — nginx (tighten script-src) (nginx)
# Drop 'unsafe-inline' / wildcards in production; use nonces or hashes for any inline script. add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'" always;
Weak CSP — Express (helmet) (typescript)
import helmet from "helmet";
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
objectSrc: ["'none'"],
},
}),
);Weak CSP — Apache (apache)
# Replace permissive policy with explicit hosts and no unsafe-inline where possible. Header set Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'"
Related Scorifya checks: weak_csp
Background
What is Content Security Policy (CSP)? A practical explainer →
An accessible explanation of Content Security Policy: what it does, why it exists, the directives that matter, and how to roll one out without breaking your app.
Read more
Verify with a fresh scan
After deploy, run the scanner on the affected hostname. Headers and TLS settings update on the very next request, so you should see the score move within seconds.