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...
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
.env file that is never committedSecrets that must be managed:
MB_DB_PASS ā application database passwordMB_EMBEDDING_SECRET_KEY ā signing secret for embedded analyticsMB_JWT_SHARED_SECRET ā if using JWT SSOā 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:
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:
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:
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.
ā 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:
ā 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
| Secret | Rotation frequency | Rotation impact |
|---|---|---|
| Application database password | Annually or on personnel change | Update MB_DB_PASS, restart Metabase |
| Embedding secret key | On suspected leak; otherwise annually | Invalidates all embed URLs ā regenerate |
| JWT SSO secret | Annually | Update in both Metabase and your application |
| Analytics database passwords | Per your DB security policy | Update connection in Admin ā Databases |
| API keys | On personnel change; annually | Revoke 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:
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:
ā 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:
ā Back Up and Test Restores Monthly
Security includes availability. Ransomware, accidental deletion, and infrastructure failures are real threats. Verify that:
---
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:
ā Restrict Metabase's Outbound Network Access
Define explicit allow-list outbound rules for Metabase:
| Destination | Port | Purpose |
|---|---|---|
| Your analytics databases | 5432 / 3306 / etc. | Query execution |
| Your PostgreSQL app DB | 5432 | Application state |
| Your SMTP server | 587 / 465 | Email delivery |
| Your IdP (Okta, Azure AD) | 443 | SSO authentication |
api.ipstack.com | 443 | IP geolocation (optional, disable if not needed) |
ā 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:
---
Security Checklist Summary
Critical (pre-launch)
Important (first week)
latest)Operational (ongoing)
Hardening (high-security environments)
---
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.