Supabase Production: The 8 Settings Most Lovable Apps Ship Wrong
The eight Supabase settings that break Lovable-built apps in production: rate limits, SMTP, service role exposure, pooler mode, PITR, log retention, unused extensions, and network rules. Defaults and...
An app built in Lovable, Bolt, or v0 with Supabase behind it will work beautifully for the first ten users. It will break for the next thousand. The break is almost never the schema or the generated code. It is the Supabase project itself, running on defaults that are tuned for a hackathon demo rather than a product that gets posted on a subreddit and picks up real traffic overnight.
This is the list of the eight Supabase settings that most vibe-coded apps ship wrong, what the 2026 defaults actually are, and the values you want before anyone who isn't your friend hits the site. Every one of these has been the root cause of a weekend outage or a five-figure overage bill for someone we have spoken to this year.
One: Auth Rate Limits That Are Not Your Limits
Supabase ships every new project with a default set of Auth rate limits that feel strict until you read them carefully. In 2026 the defaults are roughly thirty emails per hour, thirty SMS per hour, thirty OTP verification attempts per hour, eighteen hundred token refreshes per five minutes, thirty signups per hour, and a hundred and fifty anonymous sign-ins per hour, all per client IP address. Those numbers look like ceilings. They are floors.
The problem is not brute force against six-digit OTP codes. The problem is SMS pumping. A coordinated attacker rotating through a botnet of residential IPs can trigger thirty SMS sends per hour per IP, and at five cents per message on Twilio that is thirty-six dollars a day per compromised IP address pointed at your endpoint. A few hundred IPs and you have a four-figure phone bill before you have had your morning coffee. Email pumping works the same way against Resend or SendGrid if you use a paid tier.
The values you want for a consumer app at launch are roughly five SMS per hour, ten emails per hour, ten OTP verifications per hour, five signups per hour, and three hundred token refreshes per five minutes, all per IP. Put Cloudflare Turnstile or hCaptcha in front of the signup and OTP endpoints using the built-in CAPTCHA integration in Auth settings. You can always loosen these if you see legitimate users getting throttled. You cannot unbill a thousand SMS.
Two: The Built-in SMTP That Was Never Meant To Ship
Every new Supabase project uses a built-in SMTP service for Auth emails by default. This service is hard-capped at roughly two emails per hour per project and is marked explicitly in the docs as development-only. Vibe coders see that emails are working, assume they are done, and move on. The app goes live, ten users try to sign up in the first hour, and seven of them silently never receive a magic link. Support tickets start with "I never got the email" and the dashboard shows no errors because the rate limit rejection happens inside the mailer and is not surfaced in Auth logs by default.
Worse, emails come from a Supabase-owned noreply address on a shared domain. Gmail and Outlook treat this as suspicious for any password reset or confirmation flow. Deliverability falls off a cliff the moment you are not the only project sending from that shared pool.
The right value is a real transactional email provider with a verified domain: Resend, Postmark, or SendGrid, plugged into Auth Settings as the custom SMTP provider. For a vibe-coded app Resend is the path of least resistance: three thousand emails per month on the free tier, one-click DNS verification for SPF, DKIM, and DMARC, and it takes ten minutes end to end. Do this before you announce anywhere, not after.
Three: The Service Role Key That Leaks Itself
The service role key appears in the Supabase dashboard next to the anon key, labelled clearly, one click from being copied. It bypasses Row Level Security entirely. Anyone holding the key can read and write every row in every table, regardless of policies. It is the nuclear key to your database.
Vibe coders leak this key in five predictable ways. They paste it into a Next.js file that gets bundled to the client. They prefix it with NEXT_PUBLIC_ because the generated code put the anon key there and they did not notice the difference. They commit it to a public GitHub repo during a weekend refactor. They paste screenshots of their env file into a Discord for help. They put it in a Lovable or Bolt project that publishes the env to a shared preview URL.
GitHub secret scanning catches some of these. It does not catch the screenshot or the Vercel preview. The right model is that the service role key lives only in server-side environment variables, never in any variable named NEXT_PUBLIC_ or VITE_, never in client-side code, and never in a file that gets bundled and shipped. Supabase rolled out the new publishable and secret key format in 2025, with prefixes sb_publishable_ and sb_secret_, and the secret key can be scoped and rotated independently without invalidating every user's JWT. Migrate to the new format before launch and rotate the key any time anyone new touches the project.
Four: The Pooler Mode That Breaks Prisma Silently
Supabase exposes the database at three connection strings: direct (port 5432), session-mode pooler (port 6543), and transaction-mode pooler (port 6543 with a different setting). The dashboard helpfully copies the transaction-mode pooler URL when you click the connection button, because that is the mode you want for serverless. This is correct advice wrapped around a landmine.
Transaction-mode pooling through PgBouncer does not support prepared statements, multi-statement transactions that rely on session state, LISTEN/NOTIFY, or advisory locks. Prisma emits prepared statements by default. Drizzle does too unless you explicitly pass prepare: false. The postgres-js driver prepares statements unless told otherwise. The app works fine locally against a direct connection, works fine in light testing, and then throws "prepared statement s1 already exists" errors intermittently under real load when two requests on the same pooler connection collide.
The correct configuration is platform-dependent. For serverless deployments (Vercel functions, Cloudflare Workers, AWS Lambda) you want transaction-mode pooler AND prepared statements disabled on the client: append pgbouncer=true&connection_limit=1 to a Prisma URL, pass prepare: false to postgres-js, or use the pooler-aware driver wrappers Supabase ships. For long-running servers (Fly, Railway, Render, a VPS) use session-mode pooler or direct connection with a sensible pool. Never point a serverless function at the direct connection on port 5432: you will exhaust the sixty-connection limit within minutes of any traffic spike and every subsequent request will hang.
Five: PITR Or A Real Backup Somewhere Else
Supabase Free and Pro plans ship with daily logical backups retained for seven days. Point-in-Time Recovery is a paid add-on priced at around a hundred dollars per month on Pro and included on Team and above. Most production checklists on the internet say "turn on PITR" and stop there.
The nuance is that daily backups give you a twenty-four-hour worst-case Recovery Point Objective. If a user, a bug, or an attacker runs a destructive query at two in the afternoon and you catch it at four, you can restore to roughly three in the morning and you have lost eleven hours of writes. For a payments app, a marketplace, or anything where users are generating content they care about, that loss is unrecoverable and the support workload of reconstructing it will end the company.
The rule is simple. If the app handles money, user-generated content people will miss, or any data you cannot regenerate from an upstream source, turn on PITR before launch and accept the hundred dollars a month as the cost of shipping. If it is a genuine prototype with fewer than a hundred users on the free tier, daily backups plus a weekly pg_dump to Cloudflare R2 or S3 is defensible. What is not defensible is a paying production app with a twenty-four-hour RPO and no offsite copy, which is where the majority of Lovable apps sit on launch day.
Six: Log Retention That Ends Before You Notice The Problem
Supabase retains logs for one day on Free, seven days on Pro, and twenty-eight days on Team. The logs cover Postgres, Auth, Edge Functions, Realtime, and the API gateway. One day is not long enough to debug a Monday-morning ticket about an error a user saw on Friday night. Seven days is not long enough for a security incident investigation that starts with "did anyone query this table two weeks ago."
On the Free tier, this is a known limitation and you live with it. On Pro and above, set up a log drain the day you upgrade. Supabase supports drains to Axiom, BetterStack, Datadog, and generic HTTP endpoints. Axiom has a generous free tier that will hold six months of a small app's logs at no cost. Forward Postgres logs, Auth logs, API gateway logs, and Edge Function logs into one place and build two saved queries before you need them: "auth failures in the last hour grouped by IP" and "queries against the users table by service role." Turn on the pgAudit extension on any table containing payment data, personal identifiers, or anything covered by a compliance regime.
Seven: Extensions You Forgot You Enabled
A new Supabase project comes with a handful of extensions enabled by default: uuid-ossp, pgcrypto, pg_stat_statements, pg_graphql, and supabase_vault. Another thirty or so sit in the dashboard one click away, including postgis, pg_cron, http, pg_net, and vector. Vibe coders click things while exploring. Extensions get enabled during a five-minute experiment with cron jobs or geography queries and never get disabled.
Each enabled extension is attack surface. The http and pg_net extensions are the dangerous ones: they can make outbound HTTP requests from inside Postgres. If an attacker finds a SQL injection bug or a weak Row Level Security policy, those extensions hand them a Server-Side Request Forgery primitive that reaches the cloud metadata endpoint, internal webhooks, and any network neighbour of the database host. PostGIS adds about fifty megabytes and a significant amount of C code to every backup and every maintenance window, for an app that does not use geography.
Run SELECT extname FROM pg_extension; before launch. Drop anything you are not actively using with DROP EXTENSION. Keep pgcrypto for hashing, keep pg_stat_statements for query performance diagnostics, and keep whichever UUID generator you settled on. If you need http or pg_net for a specific feature, move them into a dedicated schema, revoke execute from PUBLIC and from authenticated, and grant it only to a single service role that runs the feature.
Eight: Network Restrictions And The Custom Domain
By default, the Supabase database accepts direct connections from any IP address on the internet, the API gateway accepts requests from any origin, and Auth emails come from a subdomain of supabase.co that nobody trusts. Each of those is a production hygiene issue on its own and together they form the last mile of a proper launch.
Supabase offers network restrictions that allowlist specific CIDR ranges for direct database connections. If the app only connects from Vercel, Fly, or a known set of egress hosts, add those ranges. For Vercel, the static egress IPs add-on gives you a fixed set of outbound addresses you can allowlist; alternatively, a proxy service like QuotaGuard gives you the same effect. This does not replace good Row Level Security, but it means a leaked service role key cannot be used from a laptop in another country without also compromising your egress infrastructure.
On the frontend of the stack, configure a custom domain for the API gateway and for Auth emails. Magic links and password reset links that point to mail.yourapp.com rather than xyzabc.supabase.co materially improve click-through rates and deliverability, and they protect you on the day you need to migrate off Supabase for any reason. Turn on CAPTCHA in Auth settings for the signup, password reset, and OTP endpoints. None of this is glamorous work, which is exactly why it never gets done before traffic arrives.
The Launch Checklist In One Paragraph
Before announcing anywhere, set Auth rate limits tight, move email to Resend or Postmark on a verified domain, verify that no NEXT_PUBLIC_ variable contains a secret key, point serverless functions at the transaction pooler with prepared statements off, enable PITR or ship a weekly pg_dump to your own bucket, configure a log drain, drop every extension you are not using, and allowlist your egress IPs. Those eight moves take about a day of careful work and they prevent the nine most common failure modes we see in vibe-coded apps hitting real traffic for the first time.
If you would rather not spend that day yourself, this is exactly what WitsCode does as a paid engagement: one engineer, one week, every setting on this list fixed, log drain live, a load test run against the final configuration, and a written handover. -> Book a Supabase production hardening
Get weekly field notes.
Practical writing on shipping products, straight to your inbox. No spam.
Need help with this?
MVP Development
We design and build web apps, MVPs, and SaaS products. Talk to us about what you are working on.
Talk to usWant to discuss vibe coders for your business?
Start a project and we'll talk through where you are, what's working, and the highest-leverage moves for the next 90 days.