[{"data":1,"prerenderedAt":675},["ShallowReactive",2],{"blog-how-to/deploy-replit-securely":3},{"id":4,"title":5,"body":6,"category":647,"date":648,"dateModified":648,"description":649,"draft":650,"extension":651,"faq":652,"featured":650,"headerVariant":659,"image":660,"keywords":661,"meta":662,"navigation":663,"ogDescription":664,"ogTitle":660,"path":665,"readTime":666,"schemaOrg":667,"schemaType":668,"seo":669,"sitemap":670,"stem":671,"tags":672,"twitterCard":673,"__hash__":674},"blog/blog/how-to/deploy-replit-securely.md","How to Deploy Replit Securely (2026)",{"type":7,"value":8,"toc":638},"minimark",[9,13,16,22,27,30,33,88,99,106,112,116,119,125,152,157,184,209,216,220,223,239,242,247,268,271,277,281,284,291,294,401,408,419,423,430,437,440,456,503,515,533,537,540,548,552,555,575,578,612,631,634],[10,11,12],"p",{},"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.",[10,14,15],{},"Here's how to fix both before you share that URL.",[17,18,19],"tldr",{},[10,20,21],{},"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.",[23,24,26],"h2",{"id":25},"step-1-audit-for-hardcoded-secrets","Step 1: Audit for hardcoded secrets",[10,28,29],{},"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.",[10,31,32],{},"Open the Replit Shell (Tools > Shell) and run:",[34,35,37],"code-block",{"label":36},"Shell: search for hardcoded credentials",[38,39,44],"pre",{"className":40,"code":41,"language":42,"meta":43,"style":43},"language-bash shiki shiki-themes github-light github-dark","grep -rn 'sk-\\|sk_live_\\|Bearer \\|api_key\\s*=\\|API_KEY\\s*=' . --include=\"*.js\" --include=\"*.ts\" --include=\"*.py\" --include=\"*.env\"\n","bash","",[45,46,47],"code",{"__ignoreMap":43},[48,49,52,56,60,64,67,70,73,75,78,80,83,85],"span",{"class":50,"line":51},"line",1,[48,53,55],{"class":54},"sScJk","grep",[48,57,59],{"class":58},"sj4cs"," -rn",[48,61,63],{"class":62},"sZZnC"," 'sk-\\|sk_live_\\|Bearer \\|api_key\\s*=\\|API_KEY\\s*='",[48,65,66],{"class":62}," .",[48,68,69],{"class":58}," --include=",[48,71,72],{"class":62},"\"*.js\"",[48,74,69],{"class":58},[48,76,77],{"class":62},"\"*.ts\"",[48,79,69],{"class":58},[48,81,82],{"class":62},"\"*.py\"",[48,84,69],{"class":58},[48,86,87],{"class":62},"\"*.env\"\n",[10,89,90,91,94,95,98],{},"Also check your ",[45,92,93],{},".replit"," and ",[45,96,97],{},"replit.nix"," config files. Agent sometimes puts values there.",[10,100,101,102,105],{},"If grep returns any hits with actual key values (not ",[45,103,104],{},"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.",[107,108,109],"warning-box",{},[10,110,111],{},"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.",[23,113,115],{"id":114},"step-2-move-all-secrets-to-the-replit-secrets-tab","Step 2: Move all secrets to the Replit Secrets tab",[10,117,118],{},"The Secrets tab is Replit's built-in secrets manager. It stores values encrypted, outside your file tree, and excludes them from forks.",[10,120,121],{},[122,123,124],"strong",{},"How to add a secret:",[126,127,128,132,139,145],"ol",{},[129,130,131],"li",{},"Open your Repl.",[129,133,134,135,138],{},"Click ",[122,136,137],{},"Tools"," in the left sidebar.",[129,140,134,141,144],{},[122,142,143],{},"Secrets",".",[129,146,147,148,151],{},"Add a key (e.g., ",[45,149,150],{},"OPENAI_API_KEY",") and paste the value.",[10,153,154],{},[122,155,156],{},"How to use it in code:",[34,158,160],{"label":159},"Node.js: access secrets",[38,161,165],{"className":162,"code":163,"language":164,"meta":43,"style":43},"language-javascript shiki shiki-themes github-light github-dark","// Replit injects secrets as environment variables at runtime\nconst openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY);\n","javascript",[45,166,167,172,178],{"__ignoreMap":43},[48,168,169],{"class":50,"line":51},[48,170,171],{},"// Replit injects secrets as environment variables at runtime\n",[48,173,175],{"class":50,"line":174},2,[48,176,177],{},"const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });\n",[48,179,181],{"class":50,"line":180},3,[48,182,183],{},"const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);\n",[34,185,187],{"label":186},"Python: access secrets",[38,188,192],{"className":189,"code":190,"language":191,"meta":43,"style":43},"language-python shiki shiki-themes github-light github-dark","import os\nopenai.api_key = os.environ.get(\"OPENAI_API_KEY\")\nstripe.api_key = os.environ.get(\"STRIPE_SECRET_KEY\")\n","python",[45,193,194,199,204],{"__ignoreMap":43},[48,195,196],{"class":50,"line":51},[48,197,198],{},"import os\n",[48,200,201],{"class":50,"line":174},[48,202,203],{},"openai.api_key = os.environ.get(\"OPENAI_API_KEY\")\n",[48,205,206],{"class":50,"line":180},[48,207,208],{},"stripe.api_key = os.environ.get(\"STRIPE_SECRET_KEY\")\n",[10,210,211,212,215],{},"One limit worth knowing: secrets are runtime environment variables. If your code runs client-side in the browser (React, Vue), ",[45,213,214],{},"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).",[23,217,219],{"id":218},"step-3-set-repl-visibility-to-private","Step 3: Set Repl visibility to Private",[10,221,222],{},"A public Repl is not just a deployed URL. It's a source-code viewer. Any visitor can:",[224,225,226,229,236],"ul",{},[129,227,228],{},"Navigate to your Repl URL",[129,230,231,232,235],{},"Click the ",[122,233,234],{},"Files"," icon in the sidebar",[129,237,238],{},"Read every file in your project",[10,240,241],{},"That includes your Express routes with any secrets you missed, your database connection strings, and any config files.",[10,243,244],{},[122,245,246],{},"How to change visibility:",[126,248,249,252,258],{},[129,250,251],{},"Click your Repl name at the top of the workspace.",[129,253,254,255,144],{},"Open ",[122,256,257],{},"Settings",[129,259,260,261,264,265,144],{},"Change ",[122,262,263],{},"Visibility"," to ",[122,266,267],{},"Private",[10,269,270],{},"Free accounts support private Repls. There's no reason to leave a production app public.",[272,273,274],"tip-box",{},[10,275,276],{},"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.",[23,278,280],{"id":279},"step-4-move-api-calls-server-side","Step 4: Move API calls server-side",[10,282,283],{},"This is the issue that survives even if you use the Secrets tab correctly.",[10,285,286,287,290],{},"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 ",[45,288,289],{},"process.env"," in a bundled Vite or React app) gets baked into the JS bundle that the browser downloads.",[10,292,293],{},"The fix is a server-side route that holds the API key and acts as a proxy:",[34,295,297],{"label":296},"Express: server-side API proxy",[38,298,300],{"className":162,"code":299,"language":164,"meta":43,"style":43},"// server.js -- runs on Replit's server, not in the browser\napp.post('/api/generate', async (req, res) => {\n  const { prompt } = req.body;\n  const response = await fetch('https://api.openai.com/v1/chat/completions', {\n    method: 'POST',\n    headers: {\n      'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,\n      'Content-Type': 'application/json'\n    },\n    body: JSON.stringify({\n      model: 'gpt-4o',\n      messages: [{ role: 'user', content: prompt }]\n    })\n  });\n  const data = await response.json();\n  res.json(data);\n});\n",[45,301,302,307,312,317,323,329,335,341,347,353,359,365,371,377,383,389,395],{"__ignoreMap":43},[48,303,304],{"class":50,"line":51},[48,305,306],{},"// server.js -- runs on Replit's server, not in the browser\n",[48,308,309],{"class":50,"line":174},[48,310,311],{},"app.post('/api/generate', async (req, res) => {\n",[48,313,314],{"class":50,"line":180},[48,315,316],{},"  const { prompt } = req.body;\n",[48,318,320],{"class":50,"line":319},4,[48,321,322],{},"  const response = await fetch('https://api.openai.com/v1/chat/completions', {\n",[48,324,326],{"class":50,"line":325},5,[48,327,328],{},"    method: 'POST',\n",[48,330,332],{"class":50,"line":331},6,[48,333,334],{},"    headers: {\n",[48,336,338],{"class":50,"line":337},7,[48,339,340],{},"      'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,\n",[48,342,344],{"class":50,"line":343},8,[48,345,346],{},"      'Content-Type': 'application/json'\n",[48,348,350],{"class":50,"line":349},9,[48,351,352],{},"    },\n",[48,354,356],{"class":50,"line":355},10,[48,357,358],{},"    body: JSON.stringify({\n",[48,360,362],{"class":50,"line":361},11,[48,363,364],{},"      model: 'gpt-4o',\n",[48,366,368],{"class":50,"line":367},12,[48,369,370],{},"      messages: [{ role: 'user', content: prompt }]\n",[48,372,374],{"class":50,"line":373},13,[48,375,376],{},"    })\n",[48,378,380],{"class":50,"line":379},14,[48,381,382],{},"  });\n",[48,384,386],{"class":50,"line":385},15,[48,387,388],{},"  const data = await response.json();\n",[48,390,392],{"class":50,"line":391},16,[48,393,394],{},"  res.json(data);\n",[48,396,398],{"class":50,"line":397},17,[48,399,400],{},"});\n",[10,402,403,404,407],{},"Your frontend then calls ",[45,405,406],{},"/api/generate",". It never sees the key.",[10,409,410,411,414,415,418],{},"This applies to: OpenAI API keys, Stripe secret keys, Supabase ",[45,412,413],{},"service_role"," keys, and any high-permission credential. The Supabase ",[45,416,417],{},"anon"," key is designed to be public with proper RLS rules, so that one is fine in client-side code.",[23,420,422],{"id":421},"step-5-configure-a-custom-domain-and-verify-https","Step 5: Configure a custom domain and verify HTTPS",[10,424,425,426,429],{},"Replit's ",[45,427,428],{},".repl.co"," subdomain works fine and comes with a TLS certificate. For production, a custom domain looks more professional and gives you more control.",[10,431,432,433,436],{},"Add a custom domain in your Repl's ",[122,434,435],{},"Settings > Custom Domain",". Replit handles the TLS cert automatically via Let's Encrypt.",[10,438,439],{},"Two things to verify after deployment:",[126,441,442],{},[129,443,444,447,448,451,452,455],{},[122,445,446],{},"HTTP redirects to HTTPS."," Test by navigating to ",[45,449,450],{},"http://yourdomain.com"," (it should redirect to ",[45,453,454],{},"https://","). If you're running Express, add this:",[34,457,459],{"label":458},"Express: force HTTPS in production",[38,460,462],{"className":162,"code":461,"language":164,"meta":43,"style":43},"if (process.env.NODE_ENV === 'production') {\n  app.use((req, res, next) => {\n    if (req.headers['x-forwarded-proto'] !== 'https') {\n      return res.redirect(301, `https://${req.headers.host}${req.url}`);\n    }\n    next();\n  });\n}\n",[45,463,464,469,474,479,484,489,494,498],{"__ignoreMap":43},[48,465,466],{"class":50,"line":51},[48,467,468],{},"if (process.env.NODE_ENV === 'production') {\n",[48,470,471],{"class":50,"line":174},[48,472,473],{},"  app.use((req, res, next) => {\n",[48,475,476],{"class":50,"line":180},[48,477,478],{},"    if (req.headers['x-forwarded-proto'] !== 'https') {\n",[48,480,481],{"class":50,"line":319},[48,482,483],{},"      return res.redirect(301, `https://${req.headers.host}${req.url}`);\n",[48,485,486],{"class":50,"line":325},[48,487,488],{},"    }\n",[48,490,491],{"class":50,"line":331},[48,492,493],{},"    next();\n",[48,495,496],{"class":50,"line":337},[48,497,382],{},[48,499,500],{"class":50,"line":343},[48,501,502],{},"}\n",[126,504,505],{"start":174},[129,506,507,510,511,514],{},[122,508,509],{},"Security headers are present."," Add the ",[45,512,513],{},"helmet"," package to set CSP, HSTS, X-Frame-Options, and others automatically:",[34,516,518],{"label":517},"Express: security headers with helmet",[38,519,521],{"className":162,"code":520,"language":164,"meta":43,"style":43},"const helmet = require('helmet');\napp.use(helmet());\n",[45,522,523,528],{"__ignoreMap":43},[48,524,525],{"class":50,"line":51},[48,526,527],{},"const helmet = require('helmet');\n",[48,529,530],{"class":50,"line":174},[48,531,532],{},"app.use(helmet());\n",[23,534,536],{"id":535},"step-6-scan-the-live-url-before-sharing","Step 6: Scan the live URL before sharing",[10,538,539],{},"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.",[541,542,545],"cta-box",{"href":543,"label":544},"/","Scan Your Replit App",[10,546,547],{},"Paste your deployed URL to check for exposed secrets, missing headers, and open auth. Takes under two minutes.",[23,549,551],{"id":550},"a-note-on-replit-agent","A note on Replit Agent",[10,553,554],{},"If you used Replit Agent to build your app, check its generated code for these patterns before deploying:",[224,556,557,563,569],{},[129,558,559,560],{},"API keys as string literals: ",[45,561,562],{},"const apiKey = 'sk-proj-...';",[129,564,565,568],{},[45,566,567],{},".env"," files with actual values committed (Agent sometimes creates these with placeholder values replaced by real ones from the conversation context)",[129,570,571,574],{},[45,572,573],{},"console.log"," statements that print secret values during startup",[10,576,577],{},"Agent is good at functional code. It optimizes for \"working now\" over \"secure by default\". The security pass is yours to do.",[579,580,581,588,594,600,606],"faq-section",{},[582,583,585],"faq-item",{"question":584},"Why is Replit deployment security different from Vercel or Netlify?",[10,586,587],{},"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.",[582,589,591],{"question":590},"How do I check if my Repl is public or private?",[10,592,593],{},"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.",[582,595,597],{"question":596},"What does the Replit Secrets tab actually protect?",[10,598,599],{},"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.",[582,601,603],{"question":602},"Can I use a .env file in Replit instead of the Secrets tab?",[10,604,605],{},"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.",[582,607,609],{"question":608},"Does Replit deployment support security headers like CSP and HSTS?",[10,610,611],{},"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.",[613,614,615,621,626],"related-articles",{},[616,617],"related-card",{"description":618,"href":619,"title":620},"Already have an exposed key? Step-by-step rotation and cleanup guide.","/blog/how-to/fix-replit-api-key-exposure","How to Fix Replit API Key Exposure",[616,622],{"description":623,"href":624,"title":625},"15-item checklist for Replit apps before going live.","/blog/checklists/replit-security-checklist","Replit Security Checklist",[616,627],{"description":628,"href":629,"title":630},"Full security analysis: public Repls, shared infrastructure, and production readiness.","/blog/is-safe/replit","Is Replit Safe?",[541,632],{"href":543,"label":633},"Start Free Scan",[635,636,637],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":43,"searchDepth":174,"depth":174,"links":639},[640,641,642,643,644,645,646],{"id":25,"depth":174,"text":26},{"id":114,"depth":174,"text":115},{"id":218,"depth":174,"text":219},{"id":279,"depth":174,"text":280},{"id":421,"depth":174,"text":422},{"id":535,"depth":174,"text":536},{"id":550,"depth":174,"text":551},"how-to","2026-06-12","Replit deployment exposes secrets through public Repls, hardcoded keys from Replit Agent, and client-side env vars baked into your bundle. Six steps to ship securely.",false,"md",[653,654,656,657,658],{"question":584,"answer":587},{"question":590,"answer":655},"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.",{"question":596,"answer":599},{"question":602,"answer":605},{"question":608,"answer":611},"yellow",null,"replit deploy securely, replit deployment security, replit secrets tab, replit public repl security, replit agent api key, replit production security",{},true,"Six steps to deploy Replit securely: audit secrets, use the Secrets tab, lock Repl visibility, move API calls server-side, configure HTTPS, and scan before launch.","/blog/how-to/deploy-replit-securely","9 min read","[object Object]","HowTo",{"title":5,"description":649},{"loc":665},"blog/how-to/deploy-replit-securely",[],"summary_large_image","Lg8LM5KlUMsEzrN6FRDMUi1n9RnIaKgcqPW0uyAM414",1784736388784]