Security & Auth

Securing Your Metabase Deployment - A Security Checklist

Securing a Metabase deployment means locking down five layers: network access, authentication, data access permissions, application configuration, and...

šŸ“…
šŸ“–13 min read

Securing Your Metabase Deployment: A Security Checklist

Securing a Metabase deployment means locking down five layers: network access, authentication, data access permissions, application configuration, and operational hygiene. This guide consolidates the security decisions spread across deployment, permissions, and SSO configuration into a single checklist — organized by priority — that applies to any self-hosted Metabase deployment serving sensitive data.

Use this as a pre-launch security review for new deployments and as a periodic audit checklist for existing ones.

---

Priority 1: Critical — Must Be Done Before Going Live

These are the configurations that, if left at default, create serious security vulnerabilities.

☐ Run Metabase Behind a Reverse Proxy or Load Balancer

Never expose Metabase's application port (3000) directly to the internet. All traffic should arrive through a reverse proxy (nginx, Caddy) or a managed load balancer (AWS ALB, GCP Load Balancer) that handles TLS termination.

nginx

<h1 class="text-4xl font-bold mb-6 text-slate-900">nginx: proxy to Metabase with correct headers</h1> server { listen 443 ssl; server_name analytics.yourapp.com;

location / { proxy_pass http://metabase:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 300s; # allow long-running queries } }

☐ Enforce HTTPS — Disable HTTP

Metabase should only be accessible over HTTPS. Configure your load balancer or proxy to redirect HTTP to HTTPS:

hcl

<h1 class="text-4xl font-bold mb-6 text-slate-900">AWS ALB: redirect HTTP to HTTPS</h1> resource "aws_lb_listener" "http" { port = 80 protocol = "HTTP"

default_action { type = "redirect" redirect { port = "443" protocol = "HTTPS" status_code = "HTTP_301" } } }

Set MB_SITE_URL to the HTTPS URL — Metabase uses this in email links, embedding redirect URLs, and SAML callbacks:

bash

MB_SITE_URL=https://analytics.yourapp.com

☐ Use PostgreSQL as the Application Database — Not H2

The embedded H2 database is not suitable for production. It lacks concurrent write support, has limited backup options, and is not supported by Metabase for production use.

bash

MB_DB_TYPE=postgres MB_DB_HOST=your-postgres-host MB_DB_PORT=5432 MB_DB_DBNAME=metabase MB_DB_USER=metabase_app MB_DB_PASS=<from-secrets-manager>

☐ Store All Secrets in a Secrets Manager

Never hardcode credentials in Docker run commands, docker-compose files, or Kubernetes manifests committed to git. Use:

  • AWS: Secrets Manager with ECS secrets injection or External Secrets Operator for Kubernetes
  • GCP: Secret Manager with Workload Identity
  • Kubernetes: Sealed Secrets or External Secrets Operator backed by Vault
  • Minimum: Environment variables sourced at runtime from a .env file that is never committed
  • Secrets that must be managed:

  • MB_DB_PASS — application database password
  • MB_EMBEDDING_SECRET_KEY — signing secret for embedded analytics
  • MB_JWT_SHARED_SECRET — if using JWT SSO
  • Database connection passwords for your analytics data sources
  • ☐ Set a Strong Embedding Secret Key

    If you use signed embedding, MB_EMBEDDING_SECRET_KEY is the secret that signs JWT tokens controlling data access. A weak or guessable secret means users could forge tokens and access arbitrary data.

    Generate a cryptographically strong secret:

    bash
    

    openssl rand -hex 64 <h1 class="text-4xl font-bold mb-6 text-slate-900">Example output: a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1...</h1>

    Treat this secret with the same care as a private key — if it leaks, rotate it immediately (which invalidates all existing embed URLs).

    ☐ Restrict All Users Group to Minimal Permissions

    By default, Metabase's All Users group has unrestricted access to all databases. This means any user — including newly created ones — can query all your data before you've assigned them to a specific group.

    Immediately after setup:

  • Go to Admin → Permissions → Data
  • Set All Users to No access for every database
  • Go to Admin → Permissions → Collections
  • Set All Users to No access or View on a limited set of collections
  • Grant access explicitly through specific groups.

    ☐ Use Read-Only Database Credentials

    Metabase should connect to your analytics databases with read-only credentials. If Metabase's connection is ever compromised, read-only credentials prevent data modification.

    sql
    

    -- PostgreSQL: create read-only user for Metabase CREATE USER metabase_reader WITH PASSWORD 'strong-password'; GRANT CONNECT ON DATABASE analytics TO metabase_reader; 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;

    Never use your application's primary database user or an admin user for Metabase database connections.

    ☐ Place Metabase in a Private Network Segment

    Metabase should not be able to reach arbitrary internet endpoints or internal services it doesn't need. Place it in a private subnet with:

  • Outbound access only to: your connected databases, your SMTP server (for emails), your IdP (for SSO), and Metabase's update check endpoint
  • No inbound access except from the load balancer
  • hcl
    

    <h1 class="text-4xl font-bold mb-6 text-slate-900">Security group: ECS task only accepts traffic from the ALB</h1> resource "aws_security_group" "ecs" { ingress { from_port = 3000 to_port = 3000 protocol = "tcp" security_groups = [aws_security_group.alb.id] # only from ALB } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] # outbound for DB access, IdP, etc. } }

    ---

    Priority 2: Important — Complete Within First Week

    ☐ Enable SSO and Disable Password Login for Non-Admins

    Password-based login is a weaker authentication method than SSO with MFA. Once SSO (SAML, LDAP, or JWT) is working:

  • Verify SSO works for a non-admin user
  • Enable "Disable password login for SSO users" in SSO settings
  • Keep one break-glass admin account with password login
  • With SSO, your IdP's MFA policies automatically apply to Metabase — no additional Metabase configuration needed.

    ☐ Limit Administrator Access

    Administrators bypass all data and collection permission restrictions. Only the Metabase admin team should hold this role — not all engineers.

  • Create a non-admin group for engineers who need to author dashboards
  • Reserve the Administrators group for people who need to manage users, permissions, and settings
  • Audit the Administrators group quarterly: Admin → People → Groups → Administrators
  • ☐ Restrict Native Query (SQL Editor) Access

    The SQL editor allows users to run arbitrary queries against connected databases. Restrict access for any group that includes external users, customers, or non-technical stakeholders.

    Admin → Permissions → Data → [Database] → [Group] → Native queries: No

    Groups that typically should not have SQL editor access:

  • Embedded/customer-facing users
  • Executive dashboards-only users
  • Any externally-exposed group
  • ☐ Configure Security Headers

    Add security headers to your reverse proxy or load balancer to defend against common web attacks:

    nginx
    

    <h1 class="text-4xl font-bold mb-6 text-slate-900">nginx security headers</h1> add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

    <h1 class="text-4xl font-bold mb-6 text-slate-900">Content Security Policy — adjust frame-ancestors for embedding</h1> add_header Content-Security-Policy " default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; frame-ancestors 'self' https://yourapp.com; " always;

    Note on CSP and embedding: If you're embedding Metabase in an iframe on another domain, frame-ancestors must include that domain. Otherwise the iframe will be blocked by the browser.

    ☐ Enable Audit Logging (Enterprise)

    If you're on Metabase Enterprise, enable audit logging immediately — not after an incident occurs:

    Admin → Settings → Audit — confirm it is enabled.

    Configure log retention and, for compliance requirements, set up shipping to an external SIEM. See the Audit Logging guide in this series for full configuration.

    ☐ Pin to a Specific Metabase Version

    Using the latest Docker tag means Metabase can update automatically, without review or testing. This is a stability and security risk.

    yaml
    

    <h1 class="text-4xl font-bold mb-6 text-slate-900">docker-compose.yml — always pin the version</h1> services: metabase: image: metabase/metabase:v0.50.0 # not :latest

    Subscribe to Metabase's release notes and security advisories. Apply updates deliberately after testing in a non-production environment.

    ---

    Priority 3: Operational Security — Ongoing Practices

    ☐ Rotate Secrets on a Schedule

    SecretRotation frequencyRotation impact
    Application database passwordAnnually or on personnel changeUpdate MB_DB_PASS, restart Metabase
    Embedding secret keyOn suspected leak; otherwise annuallyInvalidates all embed URLs — regenerate
    JWT SSO secretAnnuallyUpdate in both Metabase and your application
    Analytics database passwordsPer your DB security policyUpdate connection in Admin → Databases
    API keysOn personnel change; annuallyRevoke old key, create new

    ☐ Rotate API Keys When Team Members Leave

    API keys in Metabase are associated with a user account. When a team member who used an API key leaves:

  • Create a new API key under a service account
  • Update all integrations using the old key
  • Deactivate the departing team member's account (which invalidates their API keys)
  • Use service account users (non-human accounts) for API keys rather than individual team members' accounts.

    ☐ Review User Access Quarterly

    Conduct a quarterly access review:

    javascript
    

    async function quarterlyAccessReview() { const users = await fetch(${METABASE_URL}/api/user, { headers: { "x-api-key": API_KEY }, }).then(r => r.json());

    const now = new Date(); const ninetyDaysAgo = new Date(now - 90 <em class="italic"> 24 </em> 60 <em class="italic"> 60 </em> 1000);

    const report = { total_users: users.data.length, admins: users.data.filter(u => u.is_superuser), inactive_90_days: users.data.filter(u => u.last_login && new Date(u.last_login) < ninetyDaysAgo ), never_logged_in: users.data.filter(u => !u.last_login && u.is_active), };

    return report; }

    Deactivate accounts that have been inactive for 90+ days or that belong to users who have left the organization.

    ☐ Monitor for Unusual Query Patterns

    Configure alerts on patterns that may indicate data exfiltration or unauthorized access:

  • Large result sets (> N rows) exported to CSV
  • Users querying tables they don't normally access
  • Login attempts from unusual IP addresses or geographies
  • Queries running outside normal business hours
  • Sudden increase in query volume from a single user
  • ☐ Separate API Keys Per Integration

    Create one API key per external integration or automation script — not a single shared key for everything. This allows you to:

  • Revoke access for one integration without affecting others
  • Trace API actions to a specific integration in audit logs
  • Apply least-privilege scoping (future Metabase versions may support scoped API keys)
  • ☐ Back Up and Test Restores Monthly

    Security includes availability. Ransomware, accidental deletion, and infrastructure failures are real threats. Verify that:

  • Application database is backed up daily
  • Backups are stored in a separate location from the application (not the same server or bucket)
  • A restore has been tested within the last 30 days
  • The restore takes less than your defined RTO (Recovery Time Objective)
  • ---

    Priority 4: Hardening — For High-Security Environments

    ☐ Disable Anonymous Telemetry

    Metabase sends anonymous usage data to Metabase, Inc. by default. Disable this in environments with strict data residency requirements:

    bash
    

    MB_ANON_TRACKING_ENABLED=false

    Or in Admin → Settings → General → Anonymous Tracking.

    ☐ Configure Database Connection SSL

    All connections from Metabase to your analytics databases should use TLS/SSL. This prevents query results from being intercepted in transit within your network.

    In Admin → Databases → [Database] → Advanced options:

  • Enable SSL
  • Upload the CA certificate for your database
  • For mutual TLS, upload the client certificate and key
  • ☐ Restrict Metabase's Outbound Network Access

    Define explicit allow-list outbound rules for Metabase:

    DestinationPortPurpose
    Your analytics databases5432 / 3306 / etc.Query execution
    Your PostgreSQL app DB5432Application state
    Your SMTP server587 / 465Email delivery
    Your IdP (Okta, Azure AD)443SSO authentication
    api.ipstack.com443IP geolocation (optional, disable if not needed)
    Block all other outbound access. This limits the blast radius if Metabase is compromised — it can't be used to exfiltrate data to arbitrary external destinations.

    ☐ Enable HTTP Strict Transport Security (HSTS)

    Once HTTPS is fully working, enable HSTS to prevent protocol downgrade attacks:

    nginx
    

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    Do not enable this until you're certain HTTPS is working correctly — HSTS is difficult to reverse and can lock users out if misconfigured.

    ☐ Implement IP Allowlisting for Admin Access

    Restrict Metabase admin panel access (/admin/*) to known IP ranges (your office, VPN):

    nginx
    

    location /admin { allow 10.0.0.0/8; # internal network allow 203.0.113.0/24; # office IP range deny all;

    proxy_pass http://metabase:3000; }

    This doesn't replace authentication but adds a network-level barrier against credential stuffing attacks on admin accounts.

    ☐ Configure Session Timeout

    Set a session expiry to limit the window of exposure from stolen session tokens:

    In Admin → Settings → General:

  • Session cookie max age: 1440 minutes (24 hours) for internal tools; shorter for sensitive environments
  • ---

    Security Checklist Summary

    Critical (pre-launch)

  • [ ] Reverse proxy with TLS — no direct port 3000 exposure
  • [ ] HTTPS enforced, HTTP redirects to HTTPS
  • [ ] PostgreSQL application database (not H2)
  • [ ] All secrets in a secrets manager
  • [ ] Strong, randomly generated embedding secret key
  • [ ] All Users group restricted to no data access
  • [ ] Read-only database credentials for all analytics connections
  • [ ] Metabase in private subnet
  • Important (first week)

  • [ ] SSO configured and password login disabled for non-admins
  • [ ] Administrator group limited to admin team only
  • [ ] Native query (SQL editor) access restricted for external groups
  • [ ] Security headers configured on reverse proxy
  • [ ] Audit logging enabled and shipping to SIEM (Enterprise)
  • [ ] Metabase version pinned (not latest)
  • Operational (ongoing)

  • [ ] Secret rotation schedule defined and calendared
  • [ ] API keys per integration, not shared keys
  • [ ] Quarterly user access review process in place
  • [ ] Anomalous query pattern alerting configured
  • [ ] Monthly backup restore test
  • Hardening (high-security environments)

  • [ ] Anonymous telemetry disabled
  • [ ] SSL on all database connections
  • [ ] Outbound network allowlist configured
  • [ ] HSTS enabled
  • [ ] Admin panel IP allowlisting
  • [ ] Session timeout configured
  • ---

    Summary

    Metabase security follows the principle of layered defense: no single control is sufficient, but the combination of network isolation, strong authentication, minimal permissions, secret management, and operational hygiene makes unauthorized data access extremely difficult. The most critical configurations — HTTPS, secrets management, read-only database credentials, and All Users group restrictions — take under an hour to implement on a fresh deployment. Treat this checklist as a living document: revisit it after every Metabase upgrade, every significant infrastructure change, and at least annually as part of your security review cycle.