Replit gives you a running app in minutes. The deployment story is just as fast, which is exactly where things go wrong. In our scans of Replit-deployed apps, the two most common issues are secrets hardcoded by Replit Agent (the keys end up in source files that anyone can read if the Repl is public) and API calls made directly from client-side code with no server in between.
Here's how to fix both before you share that URL.
TL;DR
Replit's biggest deployment risks: public Repls expose your source code, Replit Agent sometimes hardcodes API keys in your files, and client-side code can leak secrets to the browser. Fix: move every secret to the Secrets tab, set Repl visibility to Private, and push API calls into server-side routes.
Step 1: Audit for hardcoded secrets
Before anything else, search your source for embedded key values. Replit Agent occasionally hardcodes an API key when generating code to make things work immediately. The output is functional but insecure.
Open the Replit Shell (Tools > Shell) and run:
grep -rn 'sk-\|sk_live_\|Bearer \|api_key\s*=\|API_KEY\s*=' . --include="*.js" --include="*.ts" --include="*.py" --include="*.env"
Also check your .replit and replit.nix config files. Agent sometimes puts values there.
If grep returns any hits with actual key values (not process.env.SOMETHING), those are hardcoded. Rotate the key immediately in the affected service's dashboard (OpenAI, Stripe, Supabase). Treat the existing key as compromised, then move the new key to Secrets.
If your Repl is public right now and has hardcoded secrets, set it to Private before you do anything else. Anyone who has visited your Repl URL in the past may have already copied the key.
Step 2: Move all secrets to the Replit Secrets tab
The Secrets tab is Replit's built-in secrets manager. It stores values encrypted, outside your file tree, and excludes them from forks.
How to add a secret:
- Open your Repl.
- Click Tools in the left sidebar.
- Click Secrets.
- Add a key (e.g.,
OPENAI_API_KEY) and paste the value.
How to use it in code:
// Replit injects secrets as environment variables at runtime
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
import os
openai.api_key = os.environ.get("OPENAI_API_KEY")
stripe.api_key = os.environ.get("STRIPE_SECRET_KEY")
One limit worth knowing: secrets are runtime environment variables. If your code runs client-side in the browser (React, Vue), process.env.OPENAI_API_KEY won't work because the server never runs that code. API keys accessed from the browser need a server-side route (see Step 4).
Step 3: Set Repl visibility to Private
A public Repl is not just a deployed URL. It's a source-code viewer. Any visitor can:
- Navigate to your Repl URL
- Click the Files icon in the sidebar
- Read every file in your project
That includes your Express routes with any secrets you missed, your database connection strings, and any config files.
How to change visibility:
- Click your Repl name at the top of the workspace.
- Open Settings.
- Change Visibility to Private.
Free accounts support private Repls. There's no reason to leave a production app public.
If you're building something that genuinely needs to be open-source, keep it public, but make absolutely sure every secret goes through the Secrets tab and nothing sensitive is in a comment or config file.
Step 4: Move API calls server-side
This is the issue that survives even if you use the Secrets tab correctly.
When you call an API from client-side JavaScript (React components, vanilla JS in the browser), that code runs on the user's device. Any value it uses (including values from process.env in a bundled Vite or React app) gets baked into the JS bundle that the browser downloads.
The fix is a server-side route that holds the API key and acts as a proxy:
// server.js -- runs on Replit's server, not in the browser
app.post('/api/generate', async (req, res) => {
const { prompt } = req.body;
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }]
})
});
const data = await response.json();
res.json(data);
});
Your frontend then calls /api/generate. It never sees the key.
This applies to: OpenAI API keys, Stripe secret keys, Supabase service_role keys, and any high-permission credential. The Supabase anon key is designed to be public with proper RLS rules, so that one is fine in client-side code.
Step 5: Configure a custom domain and verify HTTPS
Replit's .repl.co subdomain works fine and comes with a TLS certificate. For production, a custom domain looks more professional and gives you more control.
Add a custom domain in your Repl's Settings > Custom Domain. Replit handles the TLS cert automatically via Let's Encrypt.
Two things to verify after deployment:
- HTTP redirects to HTTPS. Test by navigating to
http://yourdomain.com(it should redirect tohttps://). If you're running Express, add this:
if (process.env.NODE_ENV === 'production') {
app.use((req, res, next) => {
if (req.headers['x-forwarded-proto'] !== 'https') {
return res.redirect(301, `https://${req.headers.host}${req.url}`);
}
next();
});
}
- Security headers are present. Add the
helmetpackage to set CSP, HSTS, X-Frame-Options, and others automatically:
const helmet = require('helmet');
app.use(helmet());
Step 6: Scan the live URL before sharing
Source code review catches hardcoded keys. A live URL scan catches a different set of issues: secrets embedded in your JS bundle, missing security headers, open CORS policies, and exposed stack traces in error responses. These only appear after deployment.
Paste your deployed URL to check for exposed secrets, missing headers, and open auth. Takes under two minutes.
A note on Replit Agent
If you used Replit Agent to build your app, check its generated code for these patterns before deploying:
- API keys as string literals:
const apiKey = 'sk-proj-...'; .envfiles with actual values committed (Agent sometimes creates these with placeholder values replaced by real ones from the conversation context)console.logstatements that print secret values during startup
Agent is good at functional code. It optimizes for "working now" over "secure by default". The security pass is yours to do.
Why is Replit deployment security different from Vercel or Netlify?
Two things are unique to Replit. First, a public Repl exposes your source code directly at the Repl URL. Any visitor can click Files and read your code. Vercel and Netlify serve only the built output. Second, Replit Agent generates code that sometimes hardcodes API keys in source files rather than referencing environment variables. Both issues require an explicit check before you deploy.
How do I check if my Repl is public or private?
Open your Repl and click the name at the top. Look for Public or Private in the settings panel. If it shows Public, anyone can view your source code. Change it to Private before deploying a production app. Free Replit accounts can create private Repls.
What does the Replit Secrets tab actually protect?
Replit Secrets stores values in encrypted form and exposes them as environment variables at runtime. They don't appear in your source files, they don't transfer when someone forks your Repl, and they aren't included in any export. The key limit: they're only accessible server-side. If your code imports a secret in a file that gets bundled to the browser, the value still reaches the client.
Can I use a .env file in Replit instead of the Secrets tab?
You can create a .env file, but the Secrets tab is safer. A .env file is a regular file in your Repl's filesystem. If your Repl is public, that file is visible. The Secrets tab keeps values outside your file tree entirely. If you do use a .env file, add it to .gitignore if you have a connected GitHub repo, and never commit it.
Does Replit deployment support security headers like CSP and HSTS?
Yes, but you have to set them in your server code. In an Express app, use the helmet package: app.use(require('helmet')()). This adds Content-Security-Policy, X-Frame-Options, HSTS, and other headers automatically. Replit doesn't add security headers for you the way Netlify's _headers file does.