Apache Superset is one of the most popular open-source BI tools out there, but out of the box it runs on Gunicorn or Flask’s built-in server, bound to a raw port like 8088. That’s fine for local testing, but it’s not something to hand out to an entire organization. Putting Nginx in front of Superset fixes that: proper HTTPS, a clean domain name, request buffering for heavier dashboard queries, and a single, familiar place to manage access control and logging.
This guide covers the full process of setting up Nginx as a reverse proxy for Superset — from the initial concept through hardening it for production traffic.
Understanding the Setup
Superset is a Flask application typically served through Gunicorn (multiple worker processes handling requests). Nginx sits in front of Gunicorn, accepting external connections on port 80/443 and forwarding them internally to Gunicorn’s bind address, usually 127.0.0.1:8088.
Two things make Superset slightly more involved than a plain Flask app:
- It relies on WebSocket-like async communication for some chart rendering and Celery-backed async queries, so proxy timeouts need to be generous.
- Flask’s session and CSRF handling need to know it’s running behind a proxy, or redirect URLs and cookies can end up pointing at the wrong scheme (
httpinstead ofhttps), breaking login.
Requirements
- A working Superset installation (via pip, Docker, or Docker Compose), reachable internally on its Gunicorn port (default
8088). - Nginx installed on the front-end server.
- A registered domain name pointing to the server’s public IP.
- A TLS certificate — Let’s Encrypt via Certbot works well here.
- Access to Superset’s
superset_config.pyfile to adjust proxy-related settings.
Install Nginx:
sudo apt update && sudo apt install nginx -y
Confirm Superset is running and listening:
sudo ss -tlnp | grep 8088
Step 1: Configure Superset for Proxy Awareness
Open superset_config.py (commonly found at /app/pythonpath/superset_config.py in Docker setups, or wherever SUPERSET_CONFIG_PATH points).
Add the following so Flask correctly interprets the X-Forwarded-* headers Nginx will send:
from werkzeug.middleware.proxy_fix import ProxyFix
ENABLE_PROXY_FIX = True
PROXY_FIX_CONFIG = {
"x_for": 1,
"x_proto": 1,
"x_host": 1,
"x_port": 1,
"x_prefix": 1,
}
Also make sure SECRET_KEY is set to a strong, static value (not the default), since running behind a proxy with multiple workers requires a consistent session secret:
SECRET_KEY = "a-very-long-random-string-here"
Restart Superset after saving:
sudo systemctl restart superset
# or, for Docker Compose setups:
docker compose restart superset
Step 2: Write the Nginx Server Block
Create the config file:
sudo nano /etc/nginx/sites-available/superset.conf
Full example configuration:
server {
listen 80;
server_name superset.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name superset.example.com;
ssl_certificate /etc/letsencrypt/live/superset.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/superset.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
client_max_body_size 50M;
location / {
proxy_pass http://127.0.0.1:8088;
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_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# Dashboards with heavy queries can take a while to render
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_connect_timeout 60s;
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 8 16k;
}
location /static/ {
proxy_pass http://127.0.0.1:8088;
proxy_set_header Host $host;
expires 7d;
add_header Cache-Control "public";
}
}
A few notes on the choices here:
client_max_body_size 50Maccommodates CSV uploads used for Superset’s “upload a file as a data source” feature; adjust based on actual upload needs.- The
/static/block enables longer browser caching for Superset’s JS/CSS bundle, which noticeably speeds up repeat page loads since Superset’s frontend is fairly large. proxy_buffering oncombined with sane buffer sizes prevents slow client connections from tying up Gunicorn workers unnecessarily.
Enable and test:
sudo ln -s /etc/nginx/sites-available/superset.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Step 3: Testing
Confirm basic reachability:
curl -Ik https://superset.example.com/login/
Then in a browser:
- Load the login page and confirm the URL bar shows
https://, not a mixed-content warning. - Log in and check that redirects after login stay on
https://superset.example.comrather than reverting tohttp://or an internal hostname — this is the classic symptom of a missing or incorrectProxyFixconfiguration. - Open a dashboard with several charts and confirm they render without timing out.
- If using Superset’s async query feature (Celery + Redis), confirm results populate correctly, since this relies on additional API polling that must also pass through the proxy cleanly.
Troubleshooting
Login redirect loops or “CSRF token missing/invalid” This is the most common Superset-behind-proxy issue. It’s almost always caused by ENABLE_PROXY_FIX not being set, or x_proto not being passed, so Flask thinks the request came in over plain HTTP even though the browser used HTTPS. Double check both the Nginx X-Forwarded-Proto header and the PROXY_FIX_CONFIG dictionary.
502 Bad Gateway Usually means Gunicorn isn’t listening where Nginx expects. Verify with:
sudo ss -tlnp | grep 8088
sudo journalctl -u superset -n 50
Dashboards time out on complex queries Increase proxy_read_timeout further, and check Superset’s own SUPERSET_WEBSERVER_TIMEOUT setting in superset_config.py, which needs to be equal to or greater than the Nginx timeout, or Gunicorn will kill the worker before Nginx gives up waiting.
Static assets 404 or fail to load Confirm Superset was built with superset init and the frontend assets exist, and that the /static/ location block path matches what Superset actually serves them under (this can vary slightly between versions).
Mixed content warnings in the browser console Indicates Superset is generating absolute URLs with http:// instead of https://. Confirm x_proto is included in PROXY_FIX_CONFIG and that Nginx is correctly sending X-Forwarded-Proto: https.
Security Considerations
- Enforce HTTPS everywhere and redirect all HTTP traffic, as shown in the config above.
- Set strong security headers:
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
- Rotate
SECRET_KEYcarefully. Changing it invalidates all existing sessions, so plan for a maintenance window if rotating in production. - Restrict the Superset admin interface by IP allowlist if it’s only meant for internal analysts, using
allow/denyin the relevant location block. - Rate limit the login endpoint to reduce brute-force risk:
limit_req_zone $binary_remote_addr zone=superset_login:10m rate=5r/m;
location /login/ {
limit_req zone=superset_login burst=10 nodelay;
proxy_pass http://127.0.0.1:8088;
}
- Never expose Gunicorn’s port directly. Bind it to
127.0.0.1ingunicorn_config.pyor the Docker Compose port mapping, so Superset is only reachable through Nginx.
Performance Tips
- Enable gzip compression in Nginx for text-based responses, since Superset’s frontend bundle and JSON API responses compress well:
gzip on;
gzip_types application/json text/css application/javascript;
gzip_min_length 1024;
- Cache static assets aggressively (as shown earlier) — Superset’s frontend doesn’t change between deploys, so long cache lifetimes are safe as long as cache-busting filenames are used, which Superset already does by default.
- If running multiple Superset/Gunicorn workers behind Nginx for horizontal scaling, use an
upstreamblock withleast_connto spread load evenly across workers handling long-running dashboard queries. - Keep Celery workers and the Redis/database backend on fast storage; Nginx tuning can’t compensate for slow query execution on the backend.
Real-World Use Cases
- Company-wide BI portals where Superset needs a clean internal URL like
analytics.company.cominstead ofhttp://10.0.4.12:8088. - Multi-environment setups (staging vs. production Superset instances) proxied through different subdomains on the same Nginx server.
- Embedding dashboards into internal applications via Superset’s embedded SDK, where a consistent HTTPS origin is required for CORS and iframe security policies to work correctly.
- SSO integration, where Nginx can also serve as the point where an identity-aware proxy (like an OAuth2 proxy sidecar) sits before Superset, adding a login layer on top of Superset’s own authentication.
Best Practices Checklist
- Always enable
ENABLE_PROXY_FIXwith the fullPROXY_FIX_CONFIGdictionary when Superset sits behind any reverse proxy. - Match Nginx and Superset/Gunicorn timeout values so neither side kills a request the other is still waiting on.
- Bind Gunicorn to
127.0.0.1or an internal-only interface once the proxy is confirmed working. - Use a strong, static
SECRET_KEY, especially in multi-worker or multi-node deployments. - Cache static assets, compress responses, and keep query performance tuning on the database/Celery side rather than trying to solve slow dashboards purely at the proxy layer.
- Apply rate limiting to the login route and consider IP restrictions for admin-only instances.
Superset behind Nginx is a fairly standard Flask-behind-reverse-proxy setup, with the one real gotcha being proxy-awareness on the Flask side. Get ENABLE_PROXY_FIX and the forwarded headers right, keep timeouts consistent between Nginx and Gunicorn, and the rest — TLS, caching, security headers — is routine reverse proxy work.