HOW-TO GUIDE

How to Take a Vibe-Coded App to Production Safely

Treat the prototype as a draft. Move every secret to the server, check that each endpoint enforces who owns the data, verify payment webhooks, rate-limit sign-in and turn off debug output. Then add HTTPS, error logs and tested backups, and test the paths that fail, not only the happy path.

Why AI-built prototypes break in production

Coding agents are very good at making a demo work. The demo is one user, honest input and a happy path. Production is many users, some hostile, plus network failures, retries and crashes. The gaps that appear are consistent:

  • API keys or service credentials shipped in front-end code or committed to git.
  • Endpoints that check a user is signed in but not that the record belongs to them. Broken access control is A01 in the OWASP Top 10:2025.
  • Hosted databases left open because row-level security was never switched on.
  • Payments granted from the success redirect instead of verified webhooks.
  • No rate limits on sign-in, and stack traces shown to users.
  • No backups, or backups nobody has restored.

None of this means the code must be thrown away. It means it needs a deliberate production pass.

Step 1: find and move every secret

Search the working tree and the history. A key that was ever committed should be treated as leaked and rotated, even if you deleted it later.

# current files
git grep -nE "sk_live_|sk_test_|whsec_|AKIA[0-9A-Z]{16}|BEGIN (RSA|EC|OPENSSH) PRIVATE KEY"

# history: commits that added or removed a live Stripe key
git log -p -S "sk_live_" --all

Then open your app in a browser, open the developer tools Network tab and look at what the front end receives. Anything secret that appears there is public. Secrets belong in server-side environment variables or an .env file outside the web root, readable only by the app user, and excluded from git.

Step 2: check authorisation on every route

For each endpoint that reads or changes data, ask: whose data is this, and where does the code check it? Scope every query by the signed-in user rather than trusting an ID from the request:

// vulnerable: any signed-in user can read any note by changing the id
$stmt = $pdo->prepare('SELECT * FROM notes WHERE id = ?');

// safe: the record must belong to the current user
$stmt = $pdo->prepare('SELECT * FROM notes WHERE id = ? AND user_id = ?');
$stmt->execute([$noteId, $currentUser['id']]);

Test it with two accounts in two browsers: copy a URL or API call from one account and replay it as the other. Admin actions must check the role on the server; hiding a button is not access control. If you use a hosted database that the browser talks to directly, enable row-level security on every table and write a policy for each role.

Step 3: lock down sign-in, payments and input

  • Sign-in: rate-limit login and code verification per IP and per account. Regenerate the session ID at login. Use HttpOnly, Secure cookies.
  • CSRF: if you use cookie sessions, require a token on every state-changing request.
  • Payments: grant access only from signature-verified webhooks, with idempotent processing. See handling Stripe webhooks safely.
  • Input and output: prepared statements for every query; escape output with htmlspecialchars(); validate uploads by content, not file extension.
  • Dependencies: remove packages the agent added and never used, and pin what remains.

Pay particular attention to anything that costs you money per call: AI model requests, image generation, SMS, email. An unauthenticated or unthrottled endpoint that triggers a paid API is an open invitation to run up your bill. Require sign-in, rate-limit per account, and if users pay per use, debit their balance atomically before the expensive work starts.

Step 4: production configuration

Turn debug mode off so errors are logged, not displayed. Serve everything over HTTPS and point your configured app URL at the HTTPS domain. Send basic security headers (X-Content-Type-Options: nosniff, a frame policy, a referrer policy and a Content Security Policy that avoids unsafe-inline scripts where possible). Make sure the web server's document root contains only public files, so .env, logs, databases and backups can never be downloaded. The VPS deployment guide has a full Nginx example.

For a PHP app, the production settings that matter most are:

; php.ini (or the FPM pool) in production
display_errors = Off
log_errors = On
expose_php = Off
session.cookie_httponly = 1
session.cookie_secure = 1
session.use_strict_mode = 1

Use separate keys for development and production for every provider, and confirm the live ones are only on the server. Then try to break it: request /.env, /.git/config and your database file by URL, and check each returns 403 or 404.

Step 5: backups, monitoring and rollback

  1. Schedule automatic database backups and copy them off the server.
  2. Restore one into a scratch environment and sign in. Until you have done this, you do not know your backups work.
  3. Add an external uptime check and make sure errors land in a log you actually read.
  4. Keep the previous release on the server, or tagged in git, so you can roll back in minutes.

Also write down the boring things: where the server is, who has access, where the keys live, how to rotate them and how to restore. If you are the only person who knows, that document is for future you at 2am. Finally, publish the pages a real business needs before taking money: privacy policy, terms and a contact address that someone reads.

Use the agent for the audit, then verify yourself

An agent can do a useful first sweep if you give it a precise brief:

Review this codebase for production readiness. For every route and API
endpoint, list who can call it and where ownership is checked. Find secrets
in code or git history, missing rate limits, unverified webhooks, queries
built from strings and debug output. Report findings with file and line;
do not change code yet.

Then fix in small, reviewed changes and re-run your tests after each one. The model's report is a checklist, not a guarantee. If you would rather start from a base where this work is already done, see why agents build better on a working boilerplate.

Sources: OWASP Top 10:2025 · Supabase: the vibe coding master checklist · OWASP Session Management Cheat Sheet

How Simple Stack handles it

Simple Stack is the production pass already applied to a small PHP app. Its php scripts/doctor.php --prod command checks configuration, and php scripts/smoke_test.php exercises the core flows, so an agent working on it has checks to run after every change.

  • doctor --prod fails on debug mode, a non-HTTPS APP_URL, test Stripe keys or a world-readable .env
  • The smoke test runs the core flows against an isolated temporary copy of the app
  • Browser mutations need a CSRF token; inline scripts use a per-request CSP nonce
  • Example resources are scoped by user in SQL, so one account cannot read another's data
  • Sign-in, code verification and API calls are rate-limited out of the box
  • Stripe webhooks are signature-verified and deduplicated before they change any access

Questions people ask

One download

Skip the plumbing. Build the product.

Sign-in, Stripe, emails, an admin and a CMS — already built and tested in plain PHP. £49, once, for unlimited projects.