Apache Superset is an open source business intelligence platform that connects to your existing SQL databases and turns them into interactive dashboards. It handles ad hoc SQL exploration through SQL Lab, chart building through a no-code explorer, and dashboard sharing across teams. Unlike hosted BI tools, Superset keeps your query results and dashboard definitions on infrastructure you control.
This guide walks through a production install on a RamNode VPS using Python virtual environments rather than Docker, with PostgreSQL as the metadata database, Redis for caching and the Celery queue, Gunicorn as the application server, and Nginx terminating TLS in front.
What You Will Need
- A RamNode VPS running Ubuntu 24.04 LTS with at least 4 GB RAM and 2 vCPU. Superset with Celery workers and a headless browser for reports will not fit comfortably in 2 GB. If you plan to run alert reports, choose 8 GB.
- Root or sudo access.
- A domain or subdomain with an A record pointing at your VPS IP address.
- Basic familiarity with systemd and Nginx.
Superset stores dashboard definitions, user accounts, and query history in its metadata database. It does not store your analytics data. Size your VPS around concurrent users and Celery workers, not around the size of the warehouse you are querying.
Step 1: Prepare the System
Update packages and install the build dependencies Superset needs to compile its Python wheels.
apt update && apt upgrade -y
apt install -y build-essential libssl-dev libffi-dev libsasl2-dev libldap2-dev \
default-libmysqlclient-dev libpq-dev pkg-config git curl unzipCreate a dedicated system user so Superset never runs as root.
useradd --system --create-home --home-dir /opt/superset --shell /bin/bash supersetAdd swap if your plan has 4 GB or less. Superset's Python processes spike during chart rendering and package installs.
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstabStep 2: Install Python 3.11
Ubuntu 24.04 ships Python 3.12. Superset 4.x and 5.x target Python 3.10 and 3.11, and several of its pinned dependencies do not build cleanly on 3.12. Install 3.11 from the deadsnakes PPA and leave the system Python alone.
add-apt-repository -y ppa:deadsnakes/ppa
apt update
apt install -y python3.11 python3.11-dev python3.11-venvConfirm the version.
python3.11 --versionStep 3: Install and Configure PostgreSQL
Superset defaults to SQLite for metadata, which cannot handle concurrent writes from the web workers and the Celery daemon. Use PostgreSQL.
apt install -y postgresql postgresql-contrib
systemctl enable --now postgresqlCreate the database and role.
sudo -u postgres psql <<'SQL'
CREATE USER superset WITH PASSWORD 'ReplaceWithAStrongPassword';
CREATE DATABASE superset OWNER superset;
GRANT ALL PRIVILEGES ON DATABASE superset TO superset;
SQLSuperset writes frequently to its metadata tables during query execution. Tune PostgreSQL for a small VPS by editing /etc/postgresql/16/main/postgresql.conf:
shared_buffers = 512MB
work_mem = 16MB
maintenance_work_mem = 128MB
effective_cache_size = 2GB
max_connections = 100Restart PostgreSQL.
systemctl restart postgresqlStep 4: Install Redis
Redis serves three roles in Superset: the Celery broker, the results backend for async queries, and the cache layer for chart data and filter state.
apt install -y redis-serverEdit /etc/redis/redis.conf and set these values:
bind 127.0.0.1 ::1
maxmemory 512mb
maxmemory-policy allkeys-lru
supervised systemdThe allkeys-lru policy matters. Superset uses Redis for cache data that is safe to evict. Without an eviction policy, Redis will refuse writes once it hits the memory ceiling and Superset will throw errors on every cached chart load.
systemctl restart redis-server
systemctl enable redis-serverStep 5: Create the Virtual Environment and Install Superset
Switch to the superset user and build the environment.
sudo -u superset -i
python3.11 -m venv /opt/superset/venv
source /opt/superset/venv/bin/activate
pip install --upgrade pip setuptools wheelInstall Superset along with the drivers and extras you need.
pip install apache-superset
pip install psycopg2-binary redis celery gevent PillowAdd database drivers for whatever you plan to query. Common additions:
pip install pymysql # MySQL and MariaDB
pip install clickhouse-connect # ClickHouse
pip install trino # Trino and Starburst
pip install snowflake-sqlalchemyInstall each driver into this same virtual environment. Superset discovers database engines at import time, so a driver installed system wide will not be visible.
Step 6: Write the Superset Configuration
Superset reads its configuration from a Python file named superset_config.py found on the PYTHONPATH. Create a dedicated config directory.
mkdir -p /opt/superset/config /opt/superset/data /opt/superset/logsGenerate a secret key. Superset refuses to start with the default key, and rotating it later invalidates every saved database connection password.
openssl rand -base64 42Create /opt/superset/config/superset_config.py:
import os
from celery.schedules import crontab
from cachelib.redis import RedisCache
SECRET_KEY = "PASTE_YOUR_GENERATED_KEY_HERE"
SQLALCHEMY_DATABASE_URI = (
"postgresql+psycopg2://superset:ReplaceWithAStrongPassword@localhost:5432/superset"
)
REDIS_HOST = "localhost"
REDIS_PORT = 6379
# Chart and dashboard metadata cache
CACHE_CONFIG = {
"CACHE_TYPE": "RedisCache",
"CACHE_DEFAULT_TIMEOUT": 86400,
"CACHE_KEY_PREFIX": "superset_",
"CACHE_REDIS_HOST": REDIS_HOST,
"CACHE_REDIS_PORT": REDIS_PORT,
"CACHE_REDIS_DB": 1,
}
# Query result cache
DATA_CACHE_CONFIG = {
"CACHE_TYPE": "RedisCache",
"CACHE_DEFAULT_TIMEOUT": 3600,
"CACHE_KEY_PREFIX": "superset_data_",
"CACHE_REDIS_HOST": REDIS_HOST,
"CACHE_REDIS_PORT": REDIS_PORT,
"CACHE_REDIS_DB": 2,
}
FILTER_STATE_CACHE_CONFIG = {
"CACHE_TYPE": "RedisCache",
"CACHE_DEFAULT_TIMEOUT": 86400,
"CACHE_KEY_PREFIX": "superset_filter_",
"CACHE_REDIS_HOST": REDIS_HOST,
"CACHE_REDIS_PORT": REDIS_PORT,
"CACHE_REDIS_DB": 3,
}
EXPLORE_FORM_DATA_CACHE_CONFIG = {
"CACHE_TYPE": "RedisCache",
"CACHE_DEFAULT_TIMEOUT": 86400,
"CACHE_KEY_PREFIX": "superset_explore_",
"CACHE_REDIS_HOST": REDIS_HOST,
"CACHE_REDIS_PORT": REDIS_PORT,
"CACHE_REDIS_DB": 4,
}
# Async query results
RESULTS_BACKEND = RedisCache(
host=REDIS_HOST, port=REDIS_PORT, db=5, key_prefix="superset_results_"
)
class CeleryConfig:
broker_url = f"redis://{REDIS_HOST}:{REDIS_PORT}/0"
result_backend = f"redis://{REDIS_HOST}:{REDIS_PORT}/0"
imports = ("superset.sql_lab", "superset.tasks.scheduler")
worker_prefetch_multiplier = 1
task_acks_late = True
beat_schedule = {
"reports.scheduler": {
"task": "reports.scheduler",
"schedule": crontab(minute="*", hour="*"),
},
"reports.prune_log": {
"task": "reports.prune_log",
"schedule": crontab(minute=10, hour=0),
},
}
CELERY_CONFIG = CeleryConfig
FEATURE_FLAGS = {
"ALERT_REPORTS": True,
"DASHBOARD_RBAC": True,
"EMBEDDED_SUPERSET": False,
}
SQLLAB_CTAS_NO_LIMIT = True
SUPERSET_WEBSERVER_TIMEOUT = 120
ROW_LIMIT = 50000
SQL_MAX_ROW = 100000
# Behind Nginx with TLS
ENABLE_PROXY_FIX = True
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
WEBDRIVER_BASEURL = "http://127.0.0.1:8088/"
WEBDRIVER_BASEURL_USER_FRIENDLY = "https://superset.example.com/"Lock down the file. It holds your database password and secret key.
chmod 600 /opt/superset/config/superset_config.pyStep 7: Initialize the Database and Create an Admin
Export the environment Superset expects, then run the migrations.
export PYTHONPATH=/opt/superset/config:$PYTHONPATH
export FLASK_APP=superset
superset db upgrade
superset fab create-admin
superset initsuperset db upgrade builds the metadata schema. superset init creates the default roles, Admin, Alpha, Gamma, and Public, and assigns permissions. Run superset init again after every version upgrade, since new features add new permissions that existing roles will not have.
Add these exports to /opt/superset/.bashrc so future shell sessions pick them up automatically.
Step 8: Create the systemd Services
Exit back to root. Create /etc/systemd/system/superset.service:
[Unit]
Description=Apache Superset
After=network.target postgresql.service redis-server.service
Requires=postgresql.service redis-server.service
[Service]
Type=simple
User=superset
Group=superset
WorkingDirectory=/opt/superset
Environment="PYTHONPATH=/opt/superset/config"
Environment="FLASK_APP=superset"
Environment="SUPERSET_CONFIG_PATH=/opt/superset/config/superset_config.py"
ExecStart=/opt/superset/venv/bin/gunicorn \
--workers 4 \
--worker-class gthread \
--threads 20 \
--timeout 120 \
--limit-request-line 0 \
--limit-request-field_size 0 \
--bind 127.0.0.1:8088 \
"superset.app:create_app()"
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetSet worker count to roughly two times your vCPU count plus one. On a 2 vCPU plan, four workers with 20 threads each is a reasonable starting point. Each worker is a full Python process holding its own copy of Superset, so watch memory before increasing this.
Create the Celery worker at /etc/systemd/system/superset-worker.service:
[Unit]
Description=Superset Celery Worker
After=network.target redis-server.service postgresql.service
Requires=redis-server.service
[Service]
Type=simple
User=superset
Group=superset
WorkingDirectory=/opt/superset
Environment="PYTHONPATH=/opt/superset/config"
Environment="FLASK_APP=superset"
ExecStart=/opt/superset/venv/bin/celery \
--app=superset.tasks.celery_app:app worker \
--pool=prefork \
--concurrency=2 \
-O fair \
--loglevel=INFO
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetCreate the Celery beat scheduler at /etc/systemd/system/superset-beat.service:
[Unit]
Description=Superset Celery Beat
After=network.target redis-server.service
Requires=redis-server.service
[Service]
Type=simple
User=superset
Group=superset
WorkingDirectory=/opt/superset
Environment="PYTHONPATH=/opt/superset/config"
Environment="FLASK_APP=superset"
ExecStart=/opt/superset/venv/bin/celery \
--app=superset.tasks.celery_app:app beat \
--pidfile=/opt/superset/data/celerybeat.pid \
--schedule=/opt/superset/data/celerybeat-schedule \
--loglevel=INFO
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetRun exactly one beat process. Two beat schedulers pointed at the same Redis broker will fire every scheduled report twice.
Enable and start everything.
chown -R superset:superset /opt/superset
systemctl daemon-reload
systemctl enable --now superset superset-worker superset-beat
systemctl status superset --no-pagerStep 9: Configure Nginx and TLS
apt install -y nginx certbot python3-certbot-nginxCreate /etc/nginx/sites-available/superset:
upstream superset_app {
server 127.0.0.1:8088 fail_timeout=0;
}
server {
listen 80;
server_name superset.example.com;
client_max_body_size 100M;
location / {
proxy_pass http://superset_app;
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_redirect off;
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
}
}The long read timeout matters. SQL Lab queries against slow warehouses will otherwise hit a 504 from Nginx while the query is still running. If you routinely run queries longer than five minutes, enable async query execution through Celery instead of raising this further.
Enable the site and request a certificate.
ln -s /etc/nginx/sites-available/superset /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl reload nginx
certbot --nginx -d superset.example.comStep 10: Lock Down the Firewall
Superset should never be reachable on port 8088 from outside the box.
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw --force enableConfirm Gunicorn is bound to loopback only.
ss -tlnp | grep 8088You should see 127.0.0.1:8088 and not 0.0.0.0:8088.
Step 11: Enable Alert Reports
Superset renders scheduled reports by driving a headless browser against its own UI. Install Chromium and the matching driver.
apt install -y chromium-browser chromium-chromedriverOn Ubuntu 24.04 the Chromium packages are snap wrappers, which do not work well from a systemd service. Install the Debian build instead:
apt install -y wget
wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
apt install -y ./google-chrome-stable_current_amd64.debThen install the matching chromedriver into the virtual environment:
sudo -u superset /opt/superset/venv/bin/pip install chromedriver-binary-autoAdd the webdriver block to superset_config.py:
from selenium.webdriver.chrome.options import Options
WEBDRIVER_TYPE = "chrome"
WEBDRIVER_OPTION_ARGS = [
"--headless=new",
"--disable-gpu",
"--disable-dev-shm-usage",
"--no-sandbox",
"--disable-setuid-sandbox",
"--window-size=1600,1000",
]
THUMBNAIL_SELENIUM_USER = "admin"
ALERT_REPORTS_NOTIFICATION_DRY_RUN = False
SMTP_HOST = "smtp.example.com"
SMTP_PORT = 587
SMTP_STARTTLS = True
SMTP_SSL = False
SMTP_USER = "reports@example.com"
SMTP_PASSWORD = "your-smtp-password"
SMTP_MAIL_FROM = "reports@example.com"Note that RamNode blocks outbound SMTP on standard ports for abuse reasons. Route report email through an external provider API relay on port 587 or 2525, or send reports to Slack using SLACK_API_TOKEN instead.
Restart the worker and beat services after changing the config.
systemctl restart superset superset-worker superset-beatStep 12: Connect Your First Database
Log in at your domain with the admin account you created. Go to Data, then Database Connections, then plus Database.
For a database on the same VPS, use localhost. For a remote warehouse, allowlist your RamNode VPS IP address on the warehouse side first. Superset connects outbound from the VPS, so the source IP your warehouse sees is the VPS public IP.
Under the Advanced tab of the connection dialog, enable these where they apply:
- Allow DML if users need to run INSERT or UPDATE from SQL Lab. Leave off for read only analytics.
- Asynchronous query execution if you configured Celery. This hands long queries to the worker instead of blocking a Gunicorn thread.
- Chart cache timeout to control how long chart results stay in Redis.
Verifying the Install
Check that all three services are running.
systemctl is-active superset superset-worker superset-beatConfirm Celery sees the worker.
sudo -u superset /opt/superset/venv/bin/celery \
--app=superset.tasks.celery_app:app inspect pingWatch the logs while you load a dashboard.
journalctl -u superset -fTroubleshooting
Superset will not start and the log mentions the default SECRET_KEY. You did not set SECRET_KEY in superset_config.py, or PYTHONPATH is not pointing at the config directory. Confirm the service environment includes PYTHONPATH=/opt/superset/config.
Charts load but never finish, spinner stays forever. Async queries are enabled on the database connection but the Celery worker is not running or cannot reach Redis. Check systemctl status superset-worker and confirm RESULTS_BACKEND is set.
"Database connection failed" for a database that works from psql. Superset connects through SQLAlchemy inside the venv. Confirm the driver package is installed in /opt/superset/venv and not system wide.
Reports generate blank images. Chrome cannot reach WEBDRIVER_BASEURL. It must be the internal address Gunicorn listens on, not the public HTTPS URL, because the headless browser runs on the same box and the certificate hostname will not resolve to loopback.
502 from Nginx after upgrade. Run superset db upgrade and superset init after any version bump, then restart. Migrations that have not run will crash the app on boot.
Memory climbs until OOM kill. Reduce Gunicorn workers or Celery concurrency. A single Superset worker holds roughly 400 to 600 MB resident once warm.
Upgrading
Stop the services, upgrade the package, run migrations, then restart.
systemctl stop superset superset-worker superset-beat
sudo -u superset /opt/superset/venv/bin/pip install --upgrade apache-superset
sudo -u superset -i
export PYTHONPATH=/opt/superset/config FLASK_APP=superset
source /opt/superset/venv/bin/activate
superset db upgrade
superset init
exit
systemctl start superset superset-worker superset-beatBack up the metadata database before every upgrade. Superset migrations are not reliably reversible.
sudo -u postgres pg_dump superset > /root/superset-$(date +%F).sqlHardening Notes
- Change the default role assignment. New users land in the Gamma role by default. Review
PUBLIC_ROLE_LIKEand leave it unset unless you intend anonymous dashboard access. - Set
TALISMAN_ENABLED = Truein production to get security headers and a content security policy. - Rotate the Postgres password out of the config file into an environment file loaded by systemd with
EnvironmentFile=andchmod 600. - Enable
DASHBOARD_RBACso dashboard access is granted per dashboard rather than per underlying dataset. - Take regular metadata backups. Losing the Superset database means losing every chart, dashboard, and saved query.
Where to Go Next
Once Superset is running, the natural next steps are wiring it to a proper warehouse and putting a scheduler in front of your data pipelines. Superset visualizes tables, but something has to build those tables on a schedule. Pair this install with a dedicated orchestration VPS running Dagster or Prefect, and point Superset at the outputs.
