# Supabase email links go to localhost: why confirmation and reset links break, and how to fix them

Your app is live, someone signs up, and the confirmation email sends them to `localhost:3000`, which their browser can't reach ("This site can't be reached"). The password reset email does the same thing. The cause is one project setting, and the fix takes a few minutes.

## Where the link comes from

Supabase builds the links in auth emails from your project's **Site URL**. New projects start with it set to `http://localhost:3000`. Three things send users there:

- **The Site URL was never changed.** With no other instruction, every link falls back to it.
- **The app redirects to an address Supabase doesn't allow.** A redirect that isn't on the **Redirect URLs** list is dropped, and the Site URL is used instead.
- **The email template links with `{{ .SiteURL }}`.** A customised template that uses it ignores the redirect your code passed. For redirect flows the template should use `{{ .RedirectTo }}`.

## Work out which one you have

Hover the button in the email and read the address. Then open **Authentication > URL Configuration** in the Supabase dashboard and compare it with the Site URL and the Redirect URLs list. Last, look at the calls in your code to `signUp`, `resetPasswordForEmail` and `signInWithOtp`, and note the redirect address each one passes.

## Fix the project settings

Set the Site URL to the address your users actually use, such as `https://app.example.com`. Under Redirect URLs, add the exact pages your auth flows land on, for example `/auth/callback` and `/reset-password` on that domain. You can keep `http://localhost:3000/**` for local development, but in production the Supabase docs recommend exact paths over wildcards.

## Pass the redirect from your code

When the redirect is built from the current origin, the same code works on previews and in production:

```js
await supabase.auth.signUp({
  email,
  password,
  options: { emailRedirectTo: window.location.origin + '/auth/callback' },
});

await supabase.auth.resetPasswordForEmail(email, {
  redirectTo: window.location.origin + '/reset-password',
});
```

Each origin you run on still has to match an entry on the Redirect URLs list.

## Update custom templates

If you edited the email templates, open **Authentication > Email Templates** and swap `{{ .SiteURL }}` for `{{ .RedirectTo }}` wherever the flow passes a redirect address.

## Prove it's fixed

1. Sign up with an email address you haven't used before. The link should open your live site and sign you in.
2. Ask for a password reset. The link should open the reset page on your live domain.
3. If you have a preview or staging site, try once there too, so you know its pattern is allowed.

## Keep it fixed

Both settings belong to each Supabase project, so add them to your launch checklist for every new one. And set up a custom sending domain for auth emails before launch, so the emails land in inboxes in the first place.

Two questions I get: you don't need to redeploy after changing the Site URL, because new emails pick it up straight away (old emails keep their old links). For a mobile app, use a deep link such as `com.example.app://login-callback` as the redirect and add it to the Redirect URLs list.

Source: [Supabase: Redirect URLs](https://supabase.com/docs/guides/auth/redirect-urls).

---

This fix first appeared on our site: [Supabase confirmation or reset email links to localhost](https://stacksmesh.com/fix/supabase-confirmation-email-redirects-to-localhost?utm_source=hashnode&utm_medium=article&utm_campaign=BR-0003). If you want a quick outside look at your live app (exposed keys, security headers, what Google can read), the free [Launch Check](https://stacksmesh.com/tools/launch-check?utm_source=hashnode&utm_medium=article&utm_campaign=BR-0003) takes about a minute.

*I wrote this with AI assistance and reviewed it myself.*

