How do I add security headers on Replit?
By StackSiren · Published
On Replit, security headers are set by your application server — for example with helmet in Express or response headers in Flask — not by a dedicated “headers” panel you can assume exists. Publish the app, then confirm with curl -I on the live URL.
Replit's own security checklist tells you to add headers such as X-Frame-Options, X-Content-Type-Options and HSTS, and notes that HTTPS is on by default for Replit apps. It does not document a stable Dashboard UI that injects those headers for every stack. Treat anything that looks like a hosting-wide toggle as unverified; put the headers in code you control. Background: security headers guide. Related: Are Replit Secrets safe?, Add security headers on Vercel.
What to send
Minimum useful set for a small web app:
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: frame-ancestors 'none'
Prefer CSP frame-ancestors as the modern clickjacking control and keep X-Frame-Options as a fallback — see X-Frame-Options vs frame-ancestors. A fuller CSP (default-src, script-src, …) is valuable but needs a careful rollout so you do not break your own scripts.
Do not rely on <meta http-equiv="X-Frame-Options">. MDN is explicit: that header only works as a real HTTP response header. HTML meta tags for framing are a false sense of safety. If Agent or a tutorial pastes meta tags into your layout, replace them with server headers.
Express (Node) with helmet
Most Replit Agent Node apps use Express. Helmet sets a sensible default bundle:
npm install helmet
import express from "express";
import helmet from "helmet";
const app = express();
app.use(
helmet({
// Tighten framing; helmet defaults evolve — set explicitly
frameguard: { action: "deny" },
contentSecurityPolicy: {
useDefaults: true,
directives: {
"frame-ancestors": ["'none'"],
// Adjust script-src etc. to match your front end
},
},
hsts: { maxAge: 31536000, includeSubDomains: true },
}),
);
app.get("/", (_req, res) => {
res.type("html").send("<h1>ok</h1>");
});
app.listen(Number(process.env.PORT) || 3000);
Notes:
- Bind to the port Replit injects (
process.env.PORT) so the published app receives traffic. - After you change CSP directives, hard-refresh and watch the browser console for blocked scripts or styles.
- Secrets stay in Replit Secrets and are read only in server code — never
res.sendthem into HTML.
Flask (Python)
from flask import Flask
app = Flask(__name__)
@app.after_request
def set_security_headers(response):
response.headers["Strict-Transport-Security"] = (
"max-age=31536000; includeSubDomains"
)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Content-Security-Policy"] = "frame-ancestors 'none'"
return response
@app.get("/")
def index():
return "<h1>ok</h1>"
Other stacks (FastAPI, Go, Rails) follow the same idea: set headers on every response in middleware, then verify on the published hostname.
Static-only or Autoscale / Reserved VM hedges
Replit hosting options change over time (Always On, Autoscale, deployments, static site flows). Hedge:
- If your “app” is only static files with no server process, you may not have a place to run helmet. Options then are: add a tiny server that serves the static files with headers, put a CDN / reverse proxy you control in front of the custom domain, or export and host somewhere that has a
_headers/vercel.jsonstyle file (Netlify, Vercel). - Do not invent a Replit “Headers” settings page in docs or screenshots — if the product adds one later, still verify with curl because UI labels drift.
- Custom domains should be checked separately from
*.replit.app/*.repl.cohostnames; platform defaults on the subdomain do not prove what your apex sends.
Check with curl on the live URL
Publish (or redeploy) first. Then:
curl -sI https://YOUR-REPL-OR-CUSTOM-DOMAIN/ | grep -iE \
'strict-transport|x-content-type|x-frame|referrer-policy|content-security|permissions-policy'
You want to see your headers on the final HTTPS response (follow redirects with curl -sI -L if needed). If they are missing:
- Confirm the request hit the process that runs helmet /
after_request, not a different preview. - Confirm you are not looking at a cached error page.
- Search the response for meta tags that claimed to set XFO — replace them with real headers.
StackSiren's free first scan of a site you own flags missing security headers; the security headers checker is the focused tool for this cluster. Re-scan after each publish.
A minimal Express checklist before you publish
helmet(or manual headers) registered before routes that send HTML.PORTread from the environment Replit provides.- No secrets in client-side templates — see Are Replit Secrets safe?.
- Publish / redeploy so the running process matches the code you just edited.
curl -sIon the custom domain if you use one, not only the default Replit hostname.- Optional: open DevTools → Network → the document request → Response Headers, and compare with curl.
If curl shows headers but the browser does not (or the reverse), you are probably looking at different hosts, an intermediary cache, or a service worker. Disable the service worker for the test or use a private window.
Permissions-Policy and Referrer-Policy
Two cheap extras many Replit apps omit:
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Tighten Permissions-Policy only after you know which browser features your UI needs (for example a video call app must allow camera/microphone on its own origin). Starting closed and opening specific origins is safer than leaving everything at browser defaults.
Helmet can set both; in Flask add them next to the other response.headers[...] assignments.
Working with Replit Agent safely
Agent can paste helmet into index.js in one pass. Treat that as a draft:
- Read the generated CSP — Agent sometimes copies a strict
default-src 'self'that breaks inline scripts or Tailwind CDNs your app still uses. - Prefer nonces or hashes later; for a first ship, a slightly looser CSP plus solid
frame-ancestors/ HSTS / nosniff is better than a “perfect” CSP that 500s the UI. - After Agent finishes, you run curl. Do not assume the checklist in chat means production matches.
Replit's published security checklist is a good prompt source (“add the security headers to my application”) but it historically showed HTML meta examples alongside real headers — keep the HTTP header path from this post as the source of truth.
Pair headers with the rest of the Replit checklist
Headers are one line on Replit's security checklist. Also:
- Keep API keys in Secrets, not in client JS (Are Replit Secrets safe?).
- Authenticate sensitive routes; do not rely on obscurity of a Replit URL.
- Set CORS deliberately if you expose an API (avoid reflecting any
Originwith credentials) — CORS wildcard guide. - Run
npm audit/ equivalent and keep dependencies updated.
Related reading
- Missing security headers guide
- Are Replit Secrets safe?
- X-Frame-Options vs CSP frame-ancestors
- Add security headers on Vercel
- Add security headers on Netlify
FAQ
Does Replit add HSTS for me?
Replit documents HTTPS by default for apps. That is not the same as an explicit long Strict-Transport-Security on your custom domain. Set HSTS in your server and confirm with curl.
Why did meta tags not block framing?
X-Frame-Options is ignored in meta. Use HTTP headers from Express/Flask (or your reverse proxy).
Can Agent add the headers for me?
You can ask Agent to add helmet or Flask after_request middleware. Still verify the published URL yourself — Agent output is a draft until curl says otherwise.
What about Autoscale vs Always On? As long as the same server code handles HTTPS responses, helmet/middleware applies. If a deployment path serves static files without that code, headers will disappear — that is why curl on the live hostname is the source of truth.
Is this enough to ship? Headers are necessary but not sufficient. Secrets, auth, and CORS still matter. StackSiren's first scan is free and passive on sites you own.
Fix guides for this topic
Platform checklists: Replit