A user signs up for your live app, clicks the confirmation link, and the browser says:
localhost:3000 refused to connect
Password reset emails do the same. Nothing is wrong with your deploy. Supabase builds email links from the project's Site URL, and a new project starts with that set to http://localhost:3000. Set the Site URL to your live domain, add every address you redirect to under Redirect URLs, and pass a redirect address from the app that is on that list.
I see this a lot in apps built with Lovable, Bolt and Replit on Supabase, usually right after the first real users arrive.
Why it happens
-
The Site URL is still the default. Every confirmation and reset link falls back to it, so they all point at
localhost:3000. - The redirect address isn't allowed. If the app asks Supabase to redirect somewhere that isn't on the Redirect URLs list, Supabase uses the Site URL instead.
-
A customised email template uses
{{ .SiteURL }}. That ignores the redirect you passed. Templates for redirect flows should use{{ .RedirectTo }}.
Find the cause
- Open the email and hover the button. Note the exact address it opens.
- In the Supabase dashboard, open Authentication > URL Configuration and read the Site URL and the Redirect URLs list.
- Find where your app calls
signUp,resetPasswordForEmailorsignInWithOtpand check which redirect address it passes, if any.
Fix 1: set the Site URL and the allow list
- Set Site URL to your production address, for example
https://app.example.com. - Add the exact pages you redirect to, such as
https://app.example.com/auth/callbackandhttps://app.example.com/reset-password. - Keep
http://localhost:3000/**only if you still develop locally. Supabase recommends exact paths in production rather than wildcards.
Fix 2: pass the redirect address from the app
Build the address from the current site, so previews and production both work:
await supabase.auth.resetPasswordForEmail(email, {
redirectTo: window.location.origin + '/reset-password',
});
await supabase.auth.signUp({
email,
password,
options: { emailRedirectTo: window.location.origin + '/auth/callback' },
});
Whatever window.location.origin resolves to on each environment has to be on the Redirect URLs list, or Supabase falls back to the Site URL again.
Fix 3: check customised email templates
In Authentication > Email Templates, replace {{ .SiteURL }} with {{ .RedirectTo }} in the templates whose flow passes a redirect address.
Check the fix worked
- Sign up with a fresh email address: the confirmation link opens your live site and signs the user in.
- Request a password reset: the link opens your reset page on the live domain.
- If you use a preview or staging address, repeat once there to confirm its pattern is allowed.
Stop it coming back
- Put the Site URL and Redirect URLs on your launch checklist. Both are set per project, so a new project starts at the default again.
- Set up a custom sending domain for auth emails before launch so they reach inboxes.
Quick answers
Do I need to redeploy after changing the Site URL? No. It's a project setting and new emails use it right away. Emails already sent keep their old links, so ask affected users to request a new one.
What about a mobile app? Use a deep-link address such as com.example.app://login-callback as the redirect and add it to the Redirect URLs list.
Source: Supabase: Redirect URLs.
I first published this fix on our site: Supabase confirmation or reset email links to localhost: the fix. To check your live app for exposed keys, missing security headers and broken basics in about a minute, run the free Launch Check.
I wrote this with AI assistance and reviewed it myself.
Top comments (0)