How to Run Jupyter Notebook on a VPS: Remote Python Data Science Environment with SSL
Running JupyterLab on a VPS gives you a cloud-based Python data science environment accessible from any browser — no local Python installation required, no laptop performance limits, and datasets that stay on the server. Particularly valuable for large datasets (too big for laptop RAM), long-running training jobs, and team collaboration on shared notebooks.
Why VPS for Jupyter
- Large datasets: Process datasets exceeding laptop RAM on a VPS with 4–16 GB RAM
- Long-running jobs: Notebooks run overnight without keeping your laptop open
- Team collaboration: Multiple team members access the same JupyterLab instance and shared data
- Access from anywhere: Browser-based — work from any device
Step 1: Install Miniconda
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh -b -p /opt/miniconda3
echo 'export PATH="/opt/miniconda3/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
Step 2: Create Data Science Environment
conda create -n datascience python=3.11 -y
conda activate datascience
conda install -y jupyterlab numpy pandas matplotlib seaborn \
scikit-learn scipy notebook ipywidgets
pip install plotly xgboost lightgbm polars duckdb sqlalchemy psycopg2-binary
Step 3: Configure JupyterLab Security
jupyter lab --generate-config
# Generate hashed password (enter and confirm when prompted)
python3 -c "from jupyter_server.auth import passwd; print(passwd())"
nano ~/.jupyter/jupyter_lab_config.py
c.ServerApp.ip = '127.0.0.1' # Bind to localhost only — Nginx proxies
c.ServerApp.port = 8888
c.ServerApp.password = 'argon2:YOUR_HASHED_PASSWORD_HERE'
c.ServerApp.open_browser = False
c.ServerApp.root_dir = '/home/deploy/notebooks'
c.ServerApp.token = '' # Disable token auth (using password instead)
Step 4: Nginx Reverse Proxy with SSL
sudo nano /etc/nginx/sites-available/jupyter
server {
listen 80;
server_name jupyter.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name jupyter.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8888;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
# Long timeout for WebSocket kernel connections
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}
sudo ln -s /etc/nginx/sites-available/jupyter /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d jupyter.yourdomain.com
Step 5: systemd Service for Auto-Start
sudo nano /etc/systemd/system/jupyterlab.service
[Unit]
Description=JupyterLab Server
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/notebooks
ExecStart=/opt/miniconda3/envs/datascience/bin/jupyter lab
Environment="PATH=/opt/miniconda3/envs/datascience/bin:/usr/local/bin:/usr/bin"
Restart=on-failure
RestartSec=10s
[Install]
WantedBy=multi-user.target
mkdir -p /home/deploy/notebooks
sudo systemctl enable --now jupyterlab
# Visit: https://jupyter.yourdomain.com
Connect to a Database from Jupyter
import pandas as pd
from sqlalchemy import create_engine
engine = create_engine('postgresql://user:password@localhost:5432/mydb')
df = pd.read_sql("SELECT * FROM orders WHERE created_at > '2025-01-01'", engine)
df.head()
Query Large Datasets with DuckDB
import duckdb
# Query a 50 GB CSV directly without loading into memory
result = duckdb.query("""
SELECT
date_trunc('month', order_date) as month,
SUM(revenue) as monthly_revenue,
COUNT(*) as order_count
FROM '/home/deploy/data/orders_2025.csv'
GROUP BY 1
ORDER BY 1
""").df()
print(result)
Schedule Notebook Execution via Cron
sudo crontab -e
# Run a notebook daily at 6 AM and export as HTML
0 6 * * * /opt/miniconda3/envs/datascience/bin/jupyter nbconvert \
--to html --execute /home/deploy/notebooks/daily_report.ipynb \
--output /var/www/reports/daily_$(date +\%Y\%m\%d).html
JupyterHub for Multi-User Teams
pip install jupyterhub
# JupyterHub creates separate kernel environments per Linux user
# Each user logs in with their Linux username and password
jupyterhub --generate-config
# Edit jupyterhub_config.py for multi-user setup and run
Getting Started
JupyterLab runs well on 2–4 GB RAM for typical data science workloads. USA VPS plans at VPS.DO with NVMe storage keep pandas and DuckDB data loading fast — both benefit significantly from fast sequential disk I/O when processing large files.
Conclusion
JupyterLab on a VPS creates a cloud data science workbench that runs 24/7, handles datasets larger than laptop RAM, and is accessible from any browser. The setup — conda environment, password-protected JupyterLab, Nginx SSL reverse proxy, and systemd for auto-start — is production-appropriate and runs reliably without ongoing maintenance. For teams, JupyterHub extends this to multi-user environments with separate authentication and kernel spaces.