How To Install Plotly on Ubuntu 26.04 LTS

Install Plotly on Ubuntu 26.04

A fresh Ubuntu 26.04 box looks like the easy case: Python is already there, pip is a command away, and the install should take thirty seconds. Then pip install plotly answers with an “externally-managed-environment” error. Or the install works, but fig.write_image() fails because Kaleido can’t find Chrome. Or the notebook renders a blank cell behind a reverse proxy. None of this is a Plotly bug. It comes from how modern Ubuntu treats system Python, and from how Plotly splits its work between a pure-Python core and a browser-based rendering layer.

This guide covers the install methods that hold up on a real server. It starts with the one that works for most people (a virtual environment), then covers apt, Jupyter, Dash behind Nginx, static image export, Docker and troubleshooting. Where it matters, the guide explains why each step exists, because the next release will shift something and understanding beats memorizing.

Ubuntu 26.04 LTS, codenamed Resolute Raccoon, shipped in April 2026. It ships Python 3.14 in the default repositories. That is a fairly new interpreter for most scientific packages, and it shapes several decisions below.

What You Are Actually Installing

Plotly is not one thing. The plotly package on PyPI is the Python graphing library. It supports over 40 chart types across statistical, financial, geographic, scientific and 3D use cases. Under it sit a few optional pieces that people often confuse.

  • plotly (core): builds figure objects and renders them as interactive HTML using plotly.js, which ships inside the package.
  • Plotly Express: the high-level API (px.line, px.scatter). It needs a dataframe library, usually pandas, plus NumPy.
  • Kaleido: the static image exporter for PNG, SVG and PDF. Version 1.0.0 or newer is required, and it needs Chrome or Chromium to render images.
  • Dash: a separate framework for building web apps on top of Plotly. It is not part of the core install.
  • JupyterLab or notebook: where most people see Plotly charts first. It is optional, and it needs a little configuration.

Knowing this split tells you where a failure comes from. A chart that doesn’t draw is a front-end problem. A chart that won’t export is a Kaleido or Chrome problem. An install that won’t complete is a Python or pip problem.

Prerequisites and Pre-Flight Checks

Before touching anything, confirm what the machine has. Five seconds of checking saves twenty minutes of guessing.

lsb_release -a
python3 --version
which python3
free -h
df -h /

You should see Ubuntu 26.04 and a Python 3.14.x interpreter. One reference install on 26.04 reported Python 3.14.3. Point releases move, so don’t worry if yours differs slightly.

Update the package index and install the base tooling first:

sudo apt update
sudo apt install -y python3 python3-venv python3-pip python3-dev build-essential

Each package earns its place:

  • python3-venv provides the venv module. On Debian-family systems it is split out from the main Python package, and skipping it leads to the well-known “ensurepip is not available” error.
  • python3-pip gives you pip for the system interpreter. You will mostly use it from inside a venv.
  • python3-dev and build-essential are insurance. When a wheel isn’t available for your Python version, pip falls back to compiling from source, and that needs headers and a compiler.

That last point matters more on 3.14 than it did on older releases. Compiled dependencies sometimes lag a brand-new interpreter. One Kaleido issue report shows pip install plotly[kaleido] failing because orjson had to compile from source on the user’s machine. Having the toolchain installed turns that failure into a slow install instead of a dead end.

Method 1: Install Plotly in a Virtual Environment (Recommended)

This is the method to use on a production server, a workstation, or anything you intend to keep. It isolates Plotly and its dependencies from the operating system’s own Python packages. Several system tools, including parts of apt’s tooling, depend on system Python. Breaking them with a careless sudo pip install is a classic way to ruin an afternoon.

Step 1: Create a project directory and environment

mkdir -p ~/projects/plotly-lab
cd ~/projects/plotly-lab
python3 -m venv .venv
source .venv/bin/activate

The prompt now shows (.venv). From here, python and pip refer to the isolated copy.

Step 2: Upgrade pip and install Plotly

python -m pip install --upgrade pip
python -m pip install plotly

Using python -m pip instead of bare pip is a habit worth building. It guarantees pip belongs to the interpreter you are looking at. Mismatched pip and python binaries cause a large share of “I installed it but can’t import it” reports.

Step 3: Add the extras you will probably need

python -m pip install "plotly[express]" pandas numpy

The official docs describe the plotly[express] extra as the way to pull in Plotly Express dependencies, and note that you also need a supported dataframe library. Installing pandas explicitly keeps things predictable.

Step 4: Verify the install

python - <<'EOF'
import plotly
print("Plotly version:", plotly.__version__)

import plotly.express as px
fig = px.line(x=[1, 2, 3, 4], y=[3, 1, 4, 2], title="Smoke test")
fig.write_html("smoke_test.html")
print("HTML written OK")
EOF

If you see a version string and “HTML written OK”, the core works. Open smoke_test.html in a browser (copy it to your desktop if the server is headless) and confirm the chart is interactive. Hovering should show tooltips, and the toolbar should appear at the top right.

Pin your dependencies

Reproducibility is what separates a lab experiment from a deployable project:

python -m pip freeze > requirements.txt

On a new server, recreate the environment with python -m pip install -r requirements.txt. For stricter control, tools like pip-tools or uv can generate fully pinned lock files. Many admins now use uv for its install speed. It is a reasonable choice, but plain venv plus pip is the baseline and works everywhere.

Method 2: Install Plotly with apt

Ubuntu packages Plotly as python3-plotly, so you can install it system-wide:

sudo apt update
sudo apt install -y python3-plotly

It is clean, signed by the distribution, and updated through normal security channels. The tradeoff is version age. Distribution packages freeze at whatever was current when the release was assembled. For reference, a package tracker showed python3-plotly at 5.20.0 for Ubuntu 24.10. Check what 26.04 actually offers before you commit:

apt-cache policy python3-plotly

When is apt the right choice? Mostly when another system package or a corporate image requires it, or when you need a locked, audited, no-internet install. If you are following current Plotly documentation, the pip route in a venv gives you features that the distro version may not have. Mixing both in one environment is where things get confusing, so pick one per project.

Method 3: The Externally-Managed-Environment Error

Anyone who has tried pip install plotly outside a venv on recent Ubuntu has met this:

error: externally-managed-environment
× This environment is externally managed

This is deliberate. Debian and Ubuntu mark the system Python as managed by apt (PEP 668). Pip refuses to write into it, because mixing pip and apt packages in the same directory can leave the OS in an inconsistent state.

Your options, from best to worst:

  1. Use a virtual environment (Method 1). This is the right answer almost every time.
  2. Use pipx for command-line tools: sudo apt install pipx, then pipx install <tool>. This is not for libraries you import in your own code.
  3. Use apt install python3-plotly for a system-level library.
  4. Use --break-system-packages only on a disposable container or VM. On a server you care about, don’t.

Skip option 4. It exists for a reason, and the reason is that you may regret using it.

Installing JupyterLab with Plotly

Interactive exploration is where Plotly shines, and JupyterLab is the usual front end. Inside the same venv:

python -m pip install jupyterlab
jupyter lab --no-browser --ip=127.0.0.1 --port=8888

Modern Plotly (v6 and later) renders in JupyterLab through anywidget, so the old labextension build steps from past tutorials are mostly obsolete. If you follow an older guide that tells you to run jupyter labextension install jupyterlab-plotly, ignore it. That advice predates the current rendering approach.

Test with a cell like this:

import plotly.express as px
df = px.data.gapminder().query("year == 2007")
fig = px.scatter(df, x="gdpPercap", y="lifeExp", size="pop",
                 color="continent", hover_name="country", log_x=True)
fig.show()

The sample dataset needs pandas, which you already installed.

Accessing Jupyter on a remote server safely

Binding Jupyter to 0.0.0.0 on a public IP is a bad idea. Bots find open notebook servers quickly, and a notebook is remote code execution by design. Use an SSH tunnel instead:

ssh -N -L 8888:127.0.0.1:8888 user@your-server-ip

Then browse to http://127.0.0.1:8888 on your own machine. The token printed in the server log authenticates you. It takes ten seconds to set up and removes a whole class of exposure.

Static Image Export with Kaleido and Chrome

Interactive HTML is great. Reports, PDFs, emails and automated dashboards need static images. That requires Kaleido.

python -m pip install --upgrade kaleido

The Plotly docs state that static export needs Kaleido 1.0.0 or newer, and that Kaleido needs Chrome or Chromium. It uses the installation already on the system, or you can fetch one with plotly_get_chrome.

On a server with no browser:

plotly_get_chrome

Then test it:

import plotly.express as px
fig = px.bar(x=["A", "B", "C"], y=[5, 3, 7])
fig.write_image("bar.png", width=900, height=500, scale=2)
fig.write_image("bar.svg")
fig.write_image("bar.pdf")

Why Chrome fails on a headless server

Kaleido drives a real browser engine. On minimal server images, Chrome may start but exit because shared libraries are missing. Install the usual runtime libraries:

sudo apt install -y libnss3 libatk1.0-0t64 libatk-bridge2.0-0t64 \
  libcups2t64 libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 \
  libgbm1 libpango-1.0-0 libasound2t64

Ubuntu renamed several libraries with a t64 suffix during the 64-bit time_t transition. If apt says a package has no installation candidate, try the name with or without t64, and search with apt-cache search libasound. Old copy-paste lists from 22.04 tutorials will often fail for exactly this reason.

If you run Kaleido as a service user inside containers or under strict sandboxing, Chrome’s sandbox can also fail to start. Do not disable the sandbox on a server that processes untrusted data. Fix user namespace permissions or run the container with the proper security profile instead.

Older Kaleido 0.2.x bundled its own renderer and did not need Chrome. If you are maintaining legacy code pinned to Kaleido 0.2.1, it still installs from PyPI with prebuilt wheels for common platforms. For anything new, use the current version and plan for the Chrome dependency.

Running Dash Apps in Production

Plotly charts often end up inside Dash apps. A quick setup:

python -m pip install dash gunicorn

A minimal app.py:

from dash import Dash, dcc, html
import plotly.express as px

df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species")

app = Dash(__name__)
server = app.server
app.layout = html.Div([html.H3("Iris"), dcc.Graph(figure=fig)])

if __name__ == "__main__":
    app.run(debug=False)

Never run debug=True on a public server. The debugger exposes an interactive console.

For production, use Gunicorn rather than the built-in development server:

gunicorn app:server --workers 3 --bind 127.0.0.1:8050 --timeout 60

A systemd unit keeps it alive across reboots:

# /etc/systemd/system/dashapp.service
[Unit]
Description=Dash app via Gunicorn
After=network.target

[Service]
User=dashsvc
Group=dashsvc
WorkingDirectory=/opt/dashapp
ExecStart=/opt/dashapp/.venv/bin/gunicorn app:server --workers 3 --bind 127.0.0.1:8050
Restart=on-failure
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now dashapp
sudo systemctl status dashapp

Then put Nginx in front for TLS and compression:

server {
    listen 80;
    server_name charts.example.com;

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

    gzip on;
    gzip_types application/json application/javascript text/css;
}

Test with sudo nginx -t, reload with sudo systemctl reload nginx, and issue a certificate with Certbot. Compression matters here. Plotly figures serialize to JSON, and large figures compress very well, often by 80 percent or more. On a slow mobile connection that is the gap between a snappy dashboard and a spinner.

Docker Install for Reproducible Builds

If you want the same environment on a laptop, a CI runner and a server, containerize it:

FROM python:3.14-slim

ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .
USER nobody
CMD ["gunicorn", "app:server", "--bind", "0.0.0.0:8050", "--workers", "2"]

Build and run:

docker build -t plotly-app .
docker run -d --name plotly-app -p 127.0.0.1:8050:8050 plotly-app

Binding the published port to 127.0.0.1 keeps it behind your reverse proxy. A common mistake is publishing -p 8050:8050, which on many setups bypasses host firewall rules, because Docker writes its own iptables entries. If you need Kaleido image export in the container, add Chrome and its libraries to the image, or generate images in a separate worker container so the web tier stays lean.

Troubleshooting Common Errors

ModuleNotFoundError: No module named ‘plotly’

Almost always an environment mismatch. Check which interpreter runs:

which python
python -c "import sys; print(sys.executable)"
python -m pip show plotly

If pip show finds it but your script does not, you are launching the script with a different Python, often a systemd unit or cron job that does not activate the venv. Call the venv interpreter by absolute path: /opt/dashapp/.venv/bin/python script.py.

error: externally-managed-environment

Use a venv, as described above.

The virtual environment was not created successfully because ensurepip is not available

Install the missing package: sudo apt install python3-venv. Then delete the half-created folder with rm -rf .venv and retry.

Build failures during pip install (orjson, numpy, others)

Check whether the failure mentions compiling from source. This happens when no wheel exists for your Python version and platform yet. One reported case with Python 3.14 showed orjson failing to build while installing Kaleido extras. Fixes, in order:

  1. Upgrade pip: python -m pip install --upgrade pip.
  2. Install the toolchain: sudo apt install build-essential python3-dev. Some packages also need Rust, available with sudo apt install rustc cargo.
  3. Retry later or pin to a release that publishes wheels for your interpreter.
  4. As a last resort, install an older Python in a separate environment (for example with uv python install) when a critical dependency has not caught up.

ValueError: Image export using the “kaleido” engine requires the Kaleido package

Install or upgrade it: python -m pip install --upgrade kaleido. Then confirm Chrome is available, or run plotly_get_chrome. Version mismatches between an old Plotly and new Kaleido can also trigger this, so upgrade both together.

Kaleido hangs or times out

Usually Chrome cannot launch. Run the browser by hand to see the error (google-chrome --headless --no-sandbox --dump-dom about:blank, for diagnosis only) and install missing libraries with ldd hints. Low-memory servers can also kill Chrome. Check dmesg | grep -i oom.

Blank figure in Jupyter

Restart the kernel and confirm the notebook server and kernel use the same environment. Try fig.show(renderer="browser") to rule out front-end problems. Behind a proxy, make sure WebSocket upgrade headers are passed.

Dash app returns 502 behind Nginx

Check that Gunicorn is running (systemctl status dashapp), that it is bound to the same address Nginx proxies to, and read the journal with journalctl -u dashapp -n 100 --no-pager. SELinux does not apply on Ubuntu by default, but on AlmaLinux you may need setsebool -P httpd_can_network_connect 1.

r00t is an experienced Linux enthusiast and technical writer with a passion for open-source software. With years of hands-on experience in various Linux distributions, r00t has developed a deep understanding of the Linux ecosystem and its powerful tools. He holds certifications in SCE and has contributed to several open-source projects. r00t is dedicated to sharing her knowledge and expertise through well-researched and informative articles, helping others navigate the world of Linux with confidence.

Related Posts