Content Security Policy
Content Security Policy, or CSP, is an HTTP header that tells the browser where a page may load scripts, styles, images, fonts, and runtime network requests from.
For a static site, CSP is especially useful: if the site should not call third-party origins at runtime, the browser can enforce that.
Baseline policy for a static site
Content-Security-Policy: default-src 'self'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'; object-src 'none'; connect-src 'none'; img-src 'self' data:; script-src 'self'; style-src 'self'; font-src 'self'
This is a strict and understandable profile for a site with:
- no external fonts;
- no CDN;
- no analytics;
- no forms;
- no runtime
fetchcalls; - local CSS/JS only.
What each directive means
| Directive | Meaning |
|---|---|
default-src 'self' | Current origin is allowed by default |
base-uri 'self' | Restricts the <base> element |
form-action 'none' | Blocks form submission |
frame-ancestors 'none' | Blocks embedding the page in a frame |
object-src 'none' | Blocks legacy object/embed content |
connect-src 'none' | Blocks runtime network requests |
img-src 'self' data: | Allows local images and data URI images |
script-src 'self' | Allows local scripts only |
style-src 'self' | Allows local styles only |
font-src 'self' | Allows local fonts only |
Why connect-src 'none'
If the site should not make runtime network requests, explicitly block them.
This blocks:
fetch;XMLHttpRequest;- WebSocket;
- EventSource;
- some beacon requests.
For local browser tools, this is fine when all logic runs in the browser without sending data anywhere.
Why no inline scripts
A strict CSP without 'unsafe-inline' blocks inline JavaScript and inline CSS.
Keep scripts and styles in local files:
/assets/*.js
/assets/*.css
This makes behavior easier to check, cache, and reason about.
Nginx example
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'; object-src 'none'; connect-src 'none'; img-src 'self' data:; script-src 'self'; style-src 'self'; font-src 'self'" always;
Use always, so the header is present on 404 and other non-standard responses too.
Check with curl
curl -kI https://getsrv.app/ | grep -i content-security-policy
Expected:
content-security-policy: default-src 'self'; ...
Check all main security headers:
curl -kI https://getsrv.app/ \
| grep -Ei 'content-security-policy|x-frame-options|x-content-type-options|referrer-policy|permissions-policy'
Check in the browser
Open DevTools → Console.
Healthy result:
- no CSP errors;
- no CSS/JS loading errors;
- no requests to third-party origins.
If CSP blocks something, the browser usually prints the directive that caused it.
Common mistakes
Mistake 1. Adding an external font
Example:
<link href="https://fonts.example.com/font.css" rel="stylesheet">
The current CSP blocks it. For an autonomous static site, avoid external fonts.
Mistake 2. Adding inline script
Example:
<script>
console.log('hello')
</script>
With script-src 'self', this script is blocked. Move it to a local file.
Mistake 3. A tool starts using fetch
If a tool starts calling an API, connect-src 'none' blocks it. For this site, the better rule is: tools run locally and do not send data anywhere.
Mistake 4. CSP exists on 200 but not on 404
Make sure Nginx uses always:
add_header Content-Security-Policy "..." always;
When to expand the policy
Expand CSP only for a specific requirement.
Examples:
- local images from
/assets/— already allowed byimg-src 'self'; - external API — add a specific origin to
connect-src; - external font — add a specific origin to
font-srcandstyle-src.
Avoid:
script-src *
style-src *
connect-src *
That makes CSP much less useful.
Minimal check after changing CSP
curl -kI https://getsrv.app/ | grep -i content-security-policy
curl -kI https://getsrv.app/no-such-page | grep -i content-security-policy
Then open the site in a browser and check Console.
If Console is clean, CSS/JS load correctly, and Network does not show third-party runtime requests, the policy behaves as intended.