đź§­ Situation

I’m setting a Metabase dashboard to view the status of my data stored in Supabase.

Options for Secure Connection to Metabase

Option 1. IP Allowlist via EC2 Security Group

  • Allow inbound access only from your home or office IP.
  • Effect: Only you can reach the login page.
  • Tradeoff: You’ll need to update the rule if you travel or use a new network.

Option 2. Put Metabase Behind Reverse Proxy + Basic Auth

  • Run an Nginx reverse proxy on the same EC2 instance.

  • Nginx handles:

    • HTTPS (TLS certificate)
    • An extra password gate before users even hit Metabase
  • Expose only port 443 (HTTPS) to the world, not 3000.

  • This is the common “small team, serious setup” pattern.

Option 3. Don’t Expose It Publicly at All (What I chose)

Keep port 3000 closed to the world.

Instead, use SSH local port forwarding:

ssh -i your-key.pem \
    -L 3000:localhost:3000 \
    your-ec2@ip-address

Metabase will load through the secure SSH tunnel — no public exposure.

  • You don’t need HTTPS, Nginx, or any firewall rules.
  • It’s the simplest and safest method for a single user setup.
  • Perfect if you’re still in early development or using Metabase as a personal “founder dashboard.”

Later, when you onboard teammates or investors, you can layer on Nginx + HTTPS and make it multi-user.

đź§© On Supabase side

Make a Read-Only DB User Just for Metabase

Inside Supabase (Postgres), never use your service_role for BI tools. Instead, create a minimal read-only role that can view but not modify data.

Even if Metabase is ever compromised, an attacker only gets read access — no inserts, updates, or deletions. Your production data stays safe.

create role metabase_reader login password 'STRONG_PASSWORD';
grant usage on schema public to metabase_reader;
grant select on all tables in schema public to metabase_reader;
alter default privileges in schema public
grant select on tables to metabase_reader;

💡 Principle of least privilege: Metabase can only run SELECT queries and introspect table metadata — nothing else.

đź§© Handling Row-Level Security (RLS)

Supabase enforces Row Level Security (RLS) by default on many tables. Even with SELECT grants, a role sees nothing unless a policy explicitly allows it.

To let Metabase read all rows safely:

-- Create read policy for Metabase
create policy metabase_can_read_all
on public.table
for select
to metabase_reader
using (true);

Repeat for each table you want Metabase to see — or automate it with a DO-block:

do $$
declare r record;
begin
  for r in
    select table_schema, table_name
    from information_schema.tables
    where table_schema in ('public', 'schema2', 'schema3')
      and table_type = 'BASE TABLE'
  loop
    execute format('alter table %I.%I enable row level security;', r.table_schema, r.table_name);
    begin
      execute format('create policy metabase_can_read_all on %I.%I for select to metabase_reader using (true);',
                     r.table_schema, r.table_name);
    exception when duplicate_object then null;
    end;
  end loop;
end$$;

This script:

  • Enables RLS for every table in the target schemas.
  • Adds a universal read-only policy for metabase_reader.
  • Skips tables that already have the same policy.

âś… Wrap-Up

A secure BI connection is not an afterthought — it’s the foundation.

By combining:

  • read-only Postgres role, and
  • restricted network path (SSH tunnel)

You get a dashboard that’s private, auditable, and production-safe.

No public ports. No privileged credentials. No accidental data leaks.