How to self-host Metabase on a VPS the easy way

TL;DR

You can run Metabase Community Edition on a $6/month VPS in under an hour using Docker, Nginx, and a free SSL certificate from Let’s Encrypt. the full setup covers provisioning the server, containerising Metabase with a dedicated PostgreSQL metadata store, and sitting Nginx in front as a reverse proxy. you need basic comfort with SSH and a domain name you control.

What You Need Before You Start

  • a VPS with at least 2 GB RAM and 1 vCPU (Hetzner CX22, DigitalOcean Basic, or Vultr Cloud Compute all work at roughly $5-6/month)
  • Ubuntu 22.04 LTS on that server (the commands below assume this)
  • a domain or subdomain pointed at the server’s IP address (an A record, TTL 300 is fine)
  • SSH access to the server as a non-root sudo user
  • Docker 25+ and Docker Compose v2 (installed in Step 3)
  • a local terminal, or use the provider’s browser console if you prefer
  • optional: Metabase Cloud if you want zero maintenance, but this guide is for the self-hosted path

Step 1: Provision Your VPS and Create a Sudo User

Log into your hosting provider and spin up a Ubuntu 22.04 instance. pick a region close to your users. once the server is live, SSH in as root:

ssh root@YOUR_SERVER_IP

create a non-root user and give it sudo access:

adduser metauser
usermod -aG sudo metauser
rsync --archive --chown=metauser:metauser ~/.ssh /home/metauser

then switch to that user for everything that follows:

su - metauser

you should now see your shell prompt change to metauser@hostname:~$. running as a non-root user with sudo rights is safer and is how most production Linux work is done.

Step 2: Update the Server and Set the Hostname

Before installing anything, pull in the latest package lists and security patches:

sudo apt update && sudo apt upgrade -y

set a meaningful hostname so you can identify the server later:

sudo hostnamectl set-hostname metabase-server

also make sure your firewall allows SSH, HTTP, and HTTPS:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

you should now see Status: active after the ufw enable command, and the allowed rules listed. do not skip this step. a server without a firewall is a server waiting to be owned.

Step 3: Install Docker and Docker Compose

Docker is the fastest way to run Metabase without wrestling with Java dependencies. install it via the official convenience script:

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

add your user to the docker group so you can run containers without sudo:

sudo usermod -aG docker metauser
newgrp docker

verify both Docker and Compose v2 are ready:

docker --version
docker compose version

you should now see output like Docker version 26.x.x and Docker Compose version v2.x.x. if compose is missing, install it with sudo apt install docker-compose-plugin -y.

Step 4: Set Up a PostgreSQL Metadata Database

By default Metabase stores its own config and question history in H2, a file-based database. that is fine for a quick test but H2 corrupts easily on power loss and does not survive container restarts gracefully. swap it for PostgreSQL now while setup is clean.

create a directory for your stack:

mkdir ~/metabase && cd ~/metabase

create a postgres-data subfolder for the volume:

mkdir postgres-data

you will wire this into the compose file in the next step. using a proper relational database for Metabase’s internal state means your dashboards and user permissions survive upgrades without drama.

you should now see ~/metabase/postgres-data exists when you run ls -la ~/metabase/. this directory will hold all PostgreSQL files on the host.

Step 5: Write Your docker-compose.yml

create the compose file:

nano ~/metabase/docker-compose.yml

paste in the following (swap the passwords for something real):

version: "3.9"
services:
  postgres:
    image: postgres:16-alpine
    restart: always
    environment:
      POSTGRES_DB: metabase
      POSTGRES_USER: metabase
      POSTGRES_PASSWORD: CHANGE_THIS_PASSWORD
    volumes:
      - ./postgres-data:/var/lib/postgresql/data

  metabase:
    image: metabase/metabase:latest
    restart: always
    ports:
      - "3000:3000"
    environment:
      MB_DB_TYPE: postgres
      MB_DB_DBNAME: metabase
      MB_DB_PORT: 5432
      MB_DB_USER: metabase
      MB_DB_PASS: CHANGE_THIS_PASSWORD
      MB_DB_HOST: postgres
    depends_on:
      - postgres

save with Ctrl+O, exit with Ctrl+X. note that Metabase listens on port 3000 inside Docker. Nginx will sit in front and forward public traffic to it in Step 7.

you should now see the file saved without errors. run cat docker-compose.yml to double-check the contents look right before proceeding.

Step 6: Start Metabase and Confirm It Is Running

cd ~/metabase
docker compose up -d

Docker pulls the images (about 500 MB total) and starts both containers. watch the Metabase logs to see when it finishes initialising:

docker compose logs -f metabase

Metabase takes 60-120 seconds to start the first time. you are waiting for this line:

Metabase Initialization COMPLETE

press Ctrl+C to stop following logs once you see it. you can also test locally on the server:

curl -I http://localhost:3000

you should now see an HTTP 200 or 302 response from localhost:3000. if you get Connection refused, wait another 30 seconds and try again.

Step 7: Install Nginx as a Reverse Proxy

running Metabase directly on port 3000 over HTTP is not something you want exposed to the internet. install Nginx and configure it to proxy traffic to the container:

sudo apt install nginx -y

create a site config. replace analytics.yourdomain.com with your actual subdomain:

sudo nano /etc/nginx/sites-available/metabase
server {
    listen 80;
    server_name analytics.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1: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;
    }
}

enable the site and reload Nginx:

sudo ln -s /etc/nginx/sites-available/metabase /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

you should now see nginx: configuration file ... syntax is ok from the test command. visiting http://analytics.yourdomain.com in a browser should load the Metabase setup screen over plain HTTP.

Step 8: Add a Free SSL Certificate with Certbot

Certbot automates Let’s Encrypt certificates and auto-renews them. install it:

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d analytics.yourdomain.com

Certbot will ask for an email address and whether to redirect HTTP to HTTPS. choose option 2 (redirect). it rewrites your Nginx config automatically.

verify auto-renewal works:

sudo certbot renew --dry-run

you should now see Congratulations, all simulated renewals succeeded. your Metabase instance is now available at https://analytics.yourdomain.com with a valid TLS certificate that renews itself every 90 days without you touching it.

Step 9: Complete the Metabase Setup Wizard and Connect Your Data

open https://analytics.yourdomain.com in your browser. Metabase walks you through:

  1. create your admin account (use a strong password, not admin@admin.com)
  2. set the instance name
  3. connect your first database

for the database connection, you need the host, port, database name, username, and password for your actual data source (not the internal PostgreSQL you set up earlier). Metabase supports MySQL, PostgreSQL, BigQuery, Redshift, Snowflake, SQLite, and more.

once connected, Metabase scans your schema and suggests questions automatically. head to Browse Data to verify your tables appear, then try a quick question from + New > Question to confirm queries run.

you should now see your database tables listed under Browse Data with row counts next to them. if the connection fails, double-check that your data source allows inbound connections from your VPS IP address.


For more on connecting external data sources, see how to connect Google Sheets to Metabase and the BI tools category for a broader look at your options.

Common Mistakes To Avoid

  • leaving Metabase on H2 in production. H2 works for demos but the file gets corrupted if the container restarts mid-write. set up PostgreSQL as the metadata DB from day one, which is exactly what Step 4 covers.
  • skipping the reverse proxy and exposing port 3000 directly. opening port 3000 to the internet means no HTTPS, no host-header filtering, and a much larger attack surface. always put Nginx in front.
  • using 1 GB RAM VPS. Metabase’s JVM needs headroom. on a 1 GB instance you will hit OOM kills during complex queries or after a few users log in at once. 2 GB is the real minimum.
  • not setting MB_DB_PASS to something random. the example in every tutorial uses password or secret. use at least a 20-character random string. generate one with openssl rand -hex 16.
  • forgetting to back up postgres-data. your dashboards, questions, and user settings live in that volume. add a daily cron job that runs pg_dump and ships the output to S3 or Backblaze B2.
  • running docker compose up without -d. without the detached flag, your Metabase process dies when you close your SSH session. always use -d for background services.

When To Level Up

self-hosting Metabase on a single VPS is solid for one person or a small team querying a couple of databases. it breaks down in specific situations.

if your queries start timing out because your data source is large and slow, the bottleneck is rarely Metabase itself. you need a dedicated analytical layer like a data warehouse. that is a different problem from where to run Metabase.

if you reach 20+ users all running heavy queries simultaneously, the single container approach struggles. at that point you are looking at a proper Kubernetes deployment or Metabase Pro/Enterprise with its caching and connection pooling features.

if your company’s compliance requirements mean you cannot store user credentials and query history on a server you manage yourself, Metabase Cloud removes that burden and the price difference versus a $6 VPS plus your time is smaller than it looks.

for a wider view of what alternatives exist once you outgrow a simple self-hosted setup, the BI tools category covers Redash, Apache Superset, Grafana, and cloud-managed options side by side. also worth reading: Metabase vs Redash for small teams.

Frequently Asked Questions

does Metabase Community Edition cost anything?
Metabase Community Edition is free and open source under the AGPL license. you pay for the server ($5-6/month) and your domain, nothing else. Metabase Pro and Enterprise add SSO, serialisation, and priority support at a monthly fee.

how much traffic can a 2 GB VPS handle?
comfortably five to ten concurrent users running normal exploratory queries. if everyone hammers complex joins at the same moment you may hit memory limits. monitor with docker stats and upgrade to 4 GB RAM if you routinely see swap usage.

can I connect multiple databases?
yes. Metabase has no limit on the number of database connections in Community Edition. go to Admin > Databases > Add database and add as many as you need. each connection appears as a separate source in Browse Data.

what happens when Metabase releases a new version?
pull the new image and recreate the container. your data is safe in the postgres-data volume.

cd ~/metabase
docker compose pull
docker compose up -d

check the Metabase release notes before upgrading across major versions, as some releases include database migrations that cannot be rolled back.

do I need to know Java to run this?
no. Docker abstracts away the Java runtime entirely. you interact with Linux, Docker, and Nginx. you do not touch the JVM at all unless you want to tune memory settings via the JAVA_OPTS environment variable in the compose file.

Bottom Line

self-hosting Metabase on a VPS is a legitimate production setup for solopreneurs and small teams who want full control over their analytics without a monthly SaaS bill. the core path is straightforward: spin up a 2 GB Ubuntu server, run Metabase and PostgreSQL in Docker Compose, put Nginx in front with a free Let’s Encrypt certificate, and connect your database. the whole process takes under an hour if your DNS has propagated. the main gotchas are using H2 in production and skipping the reverse proxy, both of which this guide avoids from the start. once your instance is live, you can build questions, dashboards, and automated reports without touching the server again. if you want to explore what else the BI tool landscape offers beyond Metabase, start with the BI tools guide on this site.