DEV Community

Mahidhar Reddy
Mahidhar Reddy

Posted on • Originally published at mahidhark.Medium

Supabase Blocked in India: The Complete Guide to Fixing Your App

Every solution, from 5-minute quick fixes to permanent $0/month architecture changes


What Happened

Last Friday evening, a user in Chennai sent me a screenshot. My app's login page was throwing a 525 SSL handshake error. No Google login, no email login, nothing.

I checked git log. No recent changes. Checked server logs. Clean. Checked Supabase status — "All Systems Operational."

Then I scrolled down and found it buried under the main dashboard: "Users Experiencing Network Connectivity Problems (India Region)."

My stomach dropped.

I'm building WhatsScale, a WhatsApp automation platform. My target market is India — 535 million WhatsApp users. And the authentication service my entire app depends on had just been blocked by the Indian government.

On February 24, 2026, a blocking order was issued under Section 69A of the Information Technology Act. Major ISPs — Jio (500M+ subscribers), Airtel, ACT Fibernet — were instructed to block DNS resolution for *.supabase.co domains.

This isn't a DNS glitch. This is a deliberate government action. There is no official timeline for when (or if) it will be resolved.

Supabase is the 4th-largest BaaS market in India — 365,000 developers used it in January 2026 alone, with 179% year-over-year growth. All of them woke up to broken apps.

If you're one of them, this guide is for you.


What's Actually Broken

The block is at the DNS level. When a browser or server in India tries to reach any *.supabase.co domain, the ISP's DNS resolver returns nothing. Some ISPs also use deep packet inspection (DPI), which blocks requests even if you change your DNS settings.

This breaks different things depending on your architecture:

What How it works Broken?
Google OAuth / email login Browser redirects through *.supabase.co ❌ Yes — browser can't reach Supabase
supabase.from().select() from browser Browser calls Supabase REST API directly ❌ Yes
Supabase Realtime Browser opens WebSocket to *.supabase.co ❌ Yes
Supabase Storage Browser uploads/downloads from *.supabase.co ❌ Yes
Supabase Edge Functions (from browser) Browser calls *.supabase.co/functions/v1/ ❌ Yes
Server-to-Supabase (server outside India) Server calls Supabase from US/EU/etc ✅ Works fine
Server-to-Supabase (server in India) Server calls Supabase from Indian VPS ❌ Yes — server DNS is also blocked
Local development Your laptop calls Supabase ❌ Yes — your laptop uses Indian ISP DNS

The pattern is simple: if the request originates from an Indian network and goes to *.supabase.co, it fails.


All Available Solutions

Here's every option, from quickest to most permanent. Pick based on your situation.


Fix 1: Change Your DNS (5 minutes, $0)

Fixes: Your own development machine only
Doesn't fix: Your end users' browsers

Change your system DNS to Cloudflare (1.1.1.1) or Google (8.8.8.8). This bypasses your ISP's DNS resolver.

macOS:

# Set DNS
networksetup -setdnsservers Wi-Fi 1.1.1.1 1.0.0.1

# Flush cache
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder
Enter fullscreen mode Exit fullscreen mode

Linux:

# Edit resolv.conf
sudo nano /etc/resolv.conf
# Add:
nameserver 1.1.1.1
nameserver 1.0.0.1
Enter fullscreen mode Exit fullscreen mode

Android / iOS:
Settings → Wi-Fi → your network → DNS → set to 1.1.1.1

Limitations:

  • You cannot ask your end users to do this. It's fine for your dev machine, not for production.
  • Some ISPs use DPI (deep packet inspection) which blocks traffic regardless of DNS settings. If changing DNS doesn't work, your ISP is using DPI.

When to use this: Local development, testing, debugging. That's it.


Fix 2: Supabase Custom Domain (30 minutes, $35/month)

Fixes: Everything — auth, database, storage, realtime
Doesn't fix: Cost sensitivity

This is the fastest production fix. Instead of your app calling your-project.supabase.co, it calls db.yourapp.com. Since your domain isn't blocked, the ISP lets the traffic through. Supabase handles the rest behind the scenes.

Prerequisites

  • A domain on Cloudflare (free plan works)
  • Supabase Pro plan ($25/month)
  • Custom Domain add-on ($10/month)

Step 1: Upgrade to Supabase Pro

Supabase dashboard → Organization → Billing → Upgrade to Pro.

Step 2: Enable Custom Domain

Project Settings → General → Custom Domains → Add.

Step 3: Add DNS Records in Cloudflare

CNAME record:

Type:    CNAME
Name:    db
Target:  <your-project-ref>.supabase.co
Proxy:   DNS only (grey cloud — critical!)
TTL:     Auto
Enter fullscreen mode Exit fullscreen mode

The proxy MUST be off (DNS only / grey cloud). Supabase needs to handle SSL termination directly.

TXT record (for verification):

Type:    TXT
Name:    _acme-challenge.db
Content: (copy from Supabase dashboard)
Enter fullscreen mode Exit fullscreen mode

Step 4: Verify and Activate

Back in Supabase, click Verify, wait 1-2 minutes, then Activate.

Step 5: Update Your App

# Before
NEXT_PUBLIC_SUPABASE_URL=https://<project-ref>.supabase.co

# After
NEXT_PUBLIC_SUPABASE_URL=https://db.yourapp.com
Enter fullscreen mode Exit fullscreen mode

Rebuild and deploy.

Step 6: Update Google OAuth (if using)

Google Cloud Console → APIs & Credentials → your OAuth client → add redirect URI:

https://db.yourapp.com/auth/v1/callback
Enter fullscreen mode Exit fullscreen mode

Keep the old one too (for non-Indian users on the default domain).

Step 7: Fix nginx Cookie Error (if applicable)

If you get 400 Bad Request — Request Header Or Cookie Too Large after OAuth redirect:

# Add to your nginx server block
large_client_header_buffers 4 32k;
Enter fullscreen mode Exit fullscreen mode

Reload nginx and retry.

When to use this: You need everything working today and $35/month is acceptable. This is the lowest-effort production fix.


Fix 3: Cloudflare Worker Proxy (2-3 hours, $0)

Fixes: REST API, Auth, Storage, Edge Functions from browser
Doesn't fix: Realtime/WebSockets (needs extra work)

A Cloudflare Worker sits between your users and Supabase. Your app calls db.yourapp.com, the Worker forwards everything to *.supabase.co. Cloudflare's network isn't blocked in India.

Free tier: 100,000 requests/day. More than enough for most apps.

Step 1: Create the Worker

Install Wrangler:

npm install -g wrangler
wrangler login
Enter fullscreen mode Exit fullscreen mode

Create the project:

mkdir supabase-proxy && cd supabase-proxy
wrangler init
Enter fullscreen mode Exit fullscreen mode

Step 2: Write the Proxy

// src/index.ts

const SUPABASE_URL = 'https://<your-project-ref>.supabase.co';

export default {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    // Build the target URL
    const targetURL = SUPABASE_URL + url.pathname + url.search;

    // Clone headers, forward everything
    const headers = new Headers(request.headers);
    headers.set('Host', new URL(SUPABASE_URL).host);

    // Forward the request
    const response = await fetch(targetURL, {
      method: request.method,
      headers,
      body: request.method !== 'GET' && request.method !== 'HEAD'
        ? request.body
        : undefined,
    });

    // Clone response and add CORS headers
    const responseHeaders = new Headers(response.headers);
    responseHeaders.set('Access-Control-Allow-Origin', '*');
    responseHeaders.set('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
    responseHeaders.set('Access-Control-Allow-Headers', '*');

    // Handle preflight
    if (request.method === 'OPTIONS') {
      return new Response(null, { status: 204, headers: responseHeaders });
    }

    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: responseHeaders,
    });
  },
};
Enter fullscreen mode Exit fullscreen mode

Step 3: Configure the Route

# wrangler.toml
name = "supabase-proxy"
main = "src/index.ts"
compatibility_date = "2024-01-01"

routes = [
  { pattern = "db.yourapp.com/*", zone_name = "yourapp.com" }
]
Enter fullscreen mode Exit fullscreen mode

Step 4: Deploy

wrangler deploy
Enter fullscreen mode Exit fullscreen mode

Step 5: Update Your App

// Change your Supabase client URL
const supabase = createClient(
  'https://db.yourapp.com',  // Your Cloudflare Worker
  SUPABASE_ANON_KEY          // Anon key stays the same
);
Enter fullscreen mode Exit fullscreen mode

That's it. All REST API calls, auth redirects, storage requests, and edge function calls now route through your Worker.

What About Realtime (WebSockets)?

Cloudflare Workers support WebSockets, but proxying Supabase Realtime requires additional handling — you need to upgrade the connection and relay frames. This is doable but complex enough to deserve its own article. If Realtime is critical for you, use Fix 2 (custom domain) for now.

When to use this: You want a free solution that covers most use cases, and you don't depend heavily on Supabase Realtime.


Fix 4: Firebase Auth + Supabase Database (2 days, $0/month)

Fixes: Authentication permanently — ISP-proof
Doesn't fix: Direct browser-to-Supabase DB calls (combine with Fix 3 for that)

This is what I'm doing for WhatsScale. Firebase Auth runs on Google's infrastructure. India is not going to block Google. Firebase Auth's free tier includes unlimited Google and email logins.

The architecture: Firebase handles authentication, Supabase handles the database, and a token bridge connects them.

Architecture: Before vs After

Before (everything through Supabase):

Browser → Supabase Auth (BLOCKED in India) → Google
Browser → Supabase DB with Supabase JWT → RLS checks auth.uid()
Enter fullscreen mode Exit fullscreen mode

After (Firebase Auth + Supabase DB):

Browser → Firebase Auth → Google (never touches supabase.co)
Browser → Your server /api/auth/token → mints Supabase JWT
Browser → Supabase DB with custom JWT → RLS still checks auth.uid()
Enter fullscreen mode Exit fullscreen mode

The browser never reaches *.supabase.co for auth. Firebase handles Google OAuth entirely on Google's domain.

The Token Bridge

This is the critical component. It takes a Firebase ID token, verifies it, looks up the user in your Supabase database, and returns a Supabase-compatible JWT.

// app/api/auth/token/route.ts

import { NextRequest, NextResponse } from 'next/server';
import * as admin from 'firebase-admin';
import jwt from 'jsonwebtoken';
import { createClient } from '@supabase/supabase-js';

// Initialize Firebase Admin (once)
if (!admin.apps.length) {
  admin.initializeApp({
    credential: admin.credential.cert({
      projectId: process.env.FIREBASE_ADMIN_PROJECT_ID,
      clientEmail: process.env.FIREBASE_ADMIN_CLIENT_EMAIL,
      privateKey: process.env.FIREBASE_ADMIN_PRIVATE_KEY?.replace(/\\n/g, '\n'),
    }),
  });
}

const supabaseAdmin = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
);

export async function POST(request: NextRequest) {
  try {
    const { firebaseToken } = await request.json();

    // 1. Verify the Firebase token
    const decoded = await admin.auth().verifyIdToken(firebaseToken);
    const email = decoded.email;

    if (!email) {
      return NextResponse.json({ error: 'No email in token' }, { status: 400 });
    }

    // 2. Find existing user by email
    let { data: profile } = await supabaseAdmin
      .from('profiles')
      .select('id')
      .eq('email', email)
      .single();

    // 3. New user? Create them in Supabase
    if (!profile) {
      const { data: authUser, error } = await supabaseAdmin.auth.admin.createUser({
        email,
        email_confirm: true,
      });
      if (error) throw error;

      await supabaseAdmin.from('profiles').insert({
        id: authUser.user.id,
        email,
        full_name: decoded.name || null,
      });

      profile = { id: authUser.user.id };
    }

    // 4. Mint a Supabase-compatible JWT
    const supabaseJWT = jwt.sign(
      {
        sub: profile.id,          // auth.uid() reads this
        role: 'authenticated',
        email,
        aud: 'authenticated',
        exp: Math.floor(Date.now() / 1000) + 3600,
        iat: Math.floor(Date.now() / 1000),
      },
      process.env.SUPABASE_JWT_SECRET!
    );

    return NextResponse.json({
      access_token: supabaseJWT,
      user: { id: profile.id, email },
    });

  } catch (error) {
    console.error('Token bridge error:', error);
    return NextResponse.json({ error: 'Authentication failed' }, { status: 401 });
  }
}
Enter fullscreen mode Exit fullscreen mode

Why Existing Data Doesn't Break

This was my biggest worry. My database has users, contacts, scheduled messages — all linked by Supabase UUIDs. Would I need to migrate everything?

No.

Supabase RLS policies use auth.uid(), which reads the sub claim from the JWT. The token bridge looks up the user by email and puts their original Supabase UUID in the sub claim. All existing RLS policies work without changes. Zero data migration.

-- This policy still works:
CREATE POLICY "Users can view own contacts"
  ON contacts FOR SELECT
  USING (user_id = auth.uid());
-- auth.uid() reads 'sub' from your custom JWT
-- 'sub' = the original Supabase UUID
Enter fullscreen mode Exit fullscreen mode

The Updated Auth Client (Simplified)

// lib/firebase-auth.ts

import { initializeApp } from 'firebase/app';
import {
  getAuth,
  signInWithRedirect,
  getRedirectResult,
  GoogleAuthProvider,
  signInWithEmailAndPassword,
  createUserWithEmailAndPassword,
  sendPasswordResetEmail,
  onAuthStateChanged,
} from 'firebase/auth';
import { createClient } from '@supabase/supabase-js';

const firebaseApp = initializeApp({
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
});

const auth = getAuth(firebaseApp);
const googleProvider = new GoogleAuthProvider();

// Exchange Firebase token for Supabase JWT
async function getSupabaseSession(firebaseUser) {
  const idToken = await firebaseUser.getIdToken();
  const res = await fetch('/api/auth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ firebaseToken: idToken }),
  });
  return res.json();
}

// Create authenticated Supabase client
function createAuthenticatedClient(accessToken) {
  return createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
    { global: { headers: { Authorization: `Bearer ${accessToken}` } } }
  );
}

// Google login — use redirect, not popup
export const signInWithGoogle = () => signInWithRedirect(auth, googleProvider);

// Email login
export const signInWithEmail = (email, password) =>
  signInWithEmailAndPassword(auth, email, password);

// Email signup
export const signUpWithEmail = (email, password) =>
  createUserWithEmailAndPassword(auth, email, password);

// Password reset
export const resetPassword = (email) => sendPasswordResetEmail(auth, email);

// Sign out
export const signOut = () => auth.signOut();
Enter fullscreen mode Exit fullscreen mode

Important: Use signInWithRedirect, not signInWithPopup. Many Indian users open links from WhatsApp or Telegram. In-app browsers block popups. Mobile Safari blocks them. The redirect flow works everywhere.

Bonus: Phone OTP Login

Firebase gives you phone number auth for free — up to 10,000 verifications per month. In India, phone OTP is the preferred login method. This crisis actually unlocked the auth method my users want most.

import { signInWithPhoneNumber, RecaptchaVerifier } from 'firebase/auth';

// Send OTP
const confirmation = await signInWithPhoneNumber(
  auth,
  '+91' + phoneNumber,
  recaptchaVerifier
);

// Verify OTP — then use same token bridge
const result = await confirmation.confirm(otpCode);
const { access_token } = await getSupabaseSession(result.user);
Enter fullscreen mode Exit fullscreen mode

Same token bridge, same Supabase JWT, same RLS. Just a different Firebase auth method.

When to use this: You want permanently ISP-proof authentication, phone OTP for Indian users, and $0/month auth costs. Combine with Fix 3 (Cloudflare Worker) if your frontend also makes direct Supabase DB calls.


Fix 5: Move Your Server Outside India ($5-12/month)

Fixes: Server-to-Supabase calls
Doesn't fix: Browser-to-Supabase calls (still need Fix 2, 3, or 4)

If your backend runs on an Indian VPS or your local machine, even server-side Supabase calls are blocked. The simplest fix is to move your server to a non-Indian region.

DigitalOcean, Hetzner, and Vultr all offer VPS instances in Singapore, Amsterdam, or Frankfurt for $5-12/month. Your Indian users will see slightly higher latency (50-100ms to Singapore), but your server can reach Supabase without any issues.

When to use this: Your server is currently hosted in India.


Which Fix For Your Situation

Your situation Recommended fix Time Cost
Need it working TODAY Fix 2 (Custom Domain) 30 min $35/mo
Indie dev, $35/mo matters Fix 3 (Cloudflare Worker) 2-3 hrs $0
Want auth that can never be blocked Fix 4 (Firebase Auth) 2 days $0
Server is in India Fix 5 (Move server) + Fix 3 or 4 Hours $5-12/mo
Just need local dev working Fix 1 (Change DNS) 5 min $0
Full Supabase app (DB + Auth + Storage from browser) Fix 3 (Cloudflare Worker) covers everything 2-3 hrs $0
Starting a new project for India Consider Firebase, AWS Amplify, or self-hosted Postgres

You can combine fixes. I'm running Fix 2 (custom domain) as my emergency solution while I build Fix 4 (Firebase Auth) as the permanent architecture.


Will This Block Be Lifted?

Nobody knows.

Supabase has publicly stated they are in contact with Indian authorities. There has been no response from the government. Section 69A blocking orders are not published — there is no public document explaining why Supabase was blocked or what would need to change for it to be lifted.

For context: TikTok was banned in India in June 2020. It's still banned in 2026. Some blocks are temporary. Some aren't. Building your architecture around the assumption that this will be resolved soon is a risk.


The Bigger Lesson

This incident taught me something I should have known from the start: never let a third-party domain sit in your browser's critical path if your users are in a country where ISP blocks happen.

India has blocked TikTok, PUBG, and hundreds of other services under Section 69A. The precedent exists. If your app targets Indian users and depends on a third-party domain that the browser must reach directly, you have a single point of failure.

Quick Architecture Audit

Does your user's browser directly call any third-party domain during:

  • Login / signup / OAuth redirects?
  • Database queries?
  • File uploads or downloads?
  • Real-time subscriptions?
  • Payment processing?

If yes — what happens if that domain gets blocked tomorrow? Can you proxy it through your own domain? Can you move the call server-side?

The cost of this audit is an hour. The cost of not doing it is finding out on a Friday evening when your users send you screenshots.


Summary

Fix Time Cost What it fixes Best for
Change DNS 5 min $0 Your dev machine Local development
Supabase Custom Domain 30 min $35/mo Everything Need it working today
Cloudflare Worker Proxy 2-3 hrs $0 REST, Auth, Storage Free fix for most apps
Firebase Auth + Supabase DB 2 days $0 Auth permanently Long-term ISP-proof auth
Move server outside India Hours $5-12/mo Server-side calls Server currently in India

Pick the fix that matches your urgency and architecture. Or combine them.

If this helped, share it with other Indian devs who are dealing with broken Supabase apps right now. The more people who have working solutions, the better.


I'm Mahi, building WhatsScale — a WhatsApp automation platform for creators and businesses. If you're using WhatsApp in India, check it out.

Questions about any of these fixes? Drop a comment or find me on Twitter/X.

Top comments (0)