đź”§ Validate Your CSP Policy

Copy a CSP example, deploy it, then verify it works with a real scan.

Verify with headertest.com →

Tailwind CSS is great right up until you try to lock down your site with a real Content Security Policy.

Then the usual “just drop in the CDN script” advice falls apart.

If you care about CSP, Tailwind setup choices matter. A lot. Some Tailwind patterns work cleanly with a strict policy. Others push you toward 'unsafe-inline' or 'unsafe-eval', which is exactly the kind of compromise you were probably trying to avoid.

The short version: if you want a strong CSP, compile Tailwind ahead of time and serve it as a static stylesheet. Avoid the browser-side CDN compiler in production.

Why Tailwind and CSP can clash#

CSP is there to control what the browser is allowed to load and execute. Tailwind can be used in a few different ways:

  1. Precompiled CSS file
    You build tailwind.css during your build step and serve it like any other stylesheet.

  2. Tailwind Play CDN / browser-side generation
    A script runs in the browser, scans your HTML, and generates styles on the fly.

  3. Inline styles or style injections from other tooling
    Some frameworks and libraries inject styles dynamically, which can trigger CSP issues even if Tailwind itself is fine.

Only the first option plays nicely with a strict CSP by default.

If you use Tailwind via a normal compiled CSS file, your CSP is simple: allow your stylesheet source and move on.

If you use the CDN script that generates CSS in the browser, you’re now dealing with script-src, possibly inline behavior, and a larger attack surface. That setup is convenient for demos, not for hardened production sites.

The CSP-friendly Tailwind setup#

This is the setup you want:

  • Install Tailwind in your project
  • Build CSS at compile time
  • Serve the output as a static file
  • Reference it with a normal <link rel="stylesheet">

Example:

<link rel="stylesheet" href="/css/app.css">

Then your CSP can stay tight:

Content-Security-Policy:
  default-src 'self';
  style-src 'self';
  script-src 'self';
  img-src 'self' data:;
  font-src 'self';
  connect-src 'self';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  object-src 'none';

That’s clean. No inline styles. No special Tailwind exceptions. No weird policy carveouts.

A typical Tailwind setup looks like this:

npm install -D tailwindcss @tailwindcss/cli
npx tailwindcss -i ./src/input.css -o ./public/css/app.css --minify

Your src/input.css might be:

@import "tailwindcss";

And your HTML:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Tailwind with CSP</title>
  <link rel="stylesheet" href="/css/app.css">
</head>
<body>
  <main class="mx-auto max-w-3xl p-6">
    <h1 class="text-3xl font-bold text-slate-900">Hello</h1>
    <p class="mt-4 text-slate-700">This works nicely with CSP.</p>
  </main>
</body>
</html>

That’s all Tailwind needs from a CSP perspective.

The setup that causes trouble#

A lot of quick-start examples use the Tailwind browser CDN:

<script src="https://cdn.tailwindcss.com"></script>

To make that work, your CSP now has to allow that external script:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.tailwindcss.com;
  style-src 'self' 'unsafe-inline';

That style-src 'unsafe-inline' is where things start getting ugly. Browser-generated CSS and style injection often force you to loosen policy in ways you wouldn’t otherwise accept.

For a prototype, maybe that’s fine. For production, I wouldn’t do it.

If your goal is “strong CSP with Tailwind,” don’t use runtime Tailwind generation in the browser.

Do you need 'unsafe-inline' for Tailwind?#

For compiled Tailwind CSS: no.

Tailwind classes live in a stylesheet. Class attributes like this are not considered inline styles:

<div class="bg-blue-600 text-white p-4 rounded-lg">
  Safe with CSP
</div>

That’s perfectly fine under:

style-src 'self';

What does count as inline style is this:

<div style="background: red; color: white;">
  Not ideal
</div>

That may require 'unsafe-inline' in style-src, unless you switch to hashes or nonces in very controlled cases. Most teams are better off avoiding inline style attributes entirely.

What about nonces and hashes?#

Nonces and hashes are useful, but not usually for Tailwind’s normal compiled CSS file.

They’re mainly relevant when you have:

  • inline <script> blocks
  • inline <style> blocks
  • framework code that injects markup dynamically
  • one-off critical CSS embedded in the page

For example, an inline style block with a nonce:

<style nonce="{{ .CSPNonce }}">
  .hero-bg { background-image: url('/img/hero.jpg'); }
</style>

And the matching CSP:

Content-Security-Policy:
  default-src 'self';
  style-src 'self' 'nonce-r4nd0m123';

That works, but if you can move CSS into your compiled stylesheet, that’s still better.

Hashes are another option for static inline blocks:

Content-Security-Policy:
  style-src 'self' 'sha256-AbCdEf123...';

Useful sometimes. Annoying to maintain if content changes often.

Fonts, icons, and Tailwind projects#

Tailwind itself usually isn’t the real problem. Fonts and third-party assets are where CSP gets widened.

Say you load a web font from Google Fonts. You might need:

Content-Security-Policy:
  default-src 'self';
  style-src 'self' https://fonts.googleapis.com;
  font-src 'self' https://fonts.gstatic.com;

That’s reasonable if you actually need it. Still, self-hosting fonts is cleaner and keeps policy simpler:

style-src 'self';
font-src 'self';

Same advice for icon libraries, analytics, A/B testing scripts, and tag managers. Every external dependency expands your policy.

Common CSP errors in Tailwind projects#

Here are the usual ones.

1. Refused to load stylesheet#

You linked a CSS file from a source not allowed by style-src.

Example fix:

style-src 'self' https://cdn.example.com;

Or better, serve the CSS yourself.

2. Refused to apply inline style#

Something is using a style="" attribute or injecting inline CSS.

You can:

  • remove the inline style
  • move it into a stylesheet
  • use a nonce or hash if you absolutely need it

Avoid solving this by blindly adding:

style-src 'self' 'unsafe-inline';

That works, but it weakens your policy.

3. Refused to load script from cdn.tailwindcss.com#

You’re using the Tailwind CDN script without allowing it in script-src.

Fix:

script-src 'self' https://cdn.tailwindcss.com;

Better fix: stop using it in production.

4. Styles missing in production build#

This often isn’t CSP at all. Tailwind may have purged classes because your content paths are wrong.

That’s a build config issue, not a policy issue.

A practical CSP for a Tailwind site#

If your site is static or server-rendered and uses compiled Tailwind CSS, this is a solid baseline:

Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self';
  img-src 'self' data:;
  font-src 'self';
  connect-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;

If you need external APIs, image hosts, or fonts, add them one by one. Don’t start with a permissive policy and promise yourself you’ll tighten it later. That almost never happens.

How to verify your policy#

After setting your headers, test the site in the browser console and with a header scanner. A quick way to sanity-check what the browser sees is headertest.com.

That helps catch obvious mistakes like missing directives, malformed syntax, or a policy that accidentally allows more than you intended.

My opinionated rule for Tailwind and CSP#

If you want a secure production setup, treat Tailwind like a build tool, not a runtime dependency.

That means:

  • compile CSS ahead of time
  • serve one static stylesheet
  • avoid inline style attributes
  • be suspicious of libraries that inject styles dynamically
  • keep style-src and script-src as narrow as possible

Tailwind itself doesn’t require a weak CSP. Convenience-driven setup choices do.

So if your current Tailwind integration is forcing 'unsafe-inline', step back and look at the architecture. The fix usually isn’t a more permissive policy. The fix is using Tailwind in a way that doesn’t fight the browser’s security model.