Development
26 min read
29 views

Exposing Jupyter & Model APIs: The Python Data Science Workflow with Pagekite

IT
InstaTunnel Team
Published by the InstaTunnel team | Editorial policy
Exposing Jupyter & Model APIs: The Python Data Science Workflow with Pagekite

Quick answer

Pagekite: The Python Localhost Tunnel for Data Science Workf: webhook testing answer

For local webhook testing, run your app locally, expose it with a public HTTPS tunnel, and paste the stable callback URL into the provider dashboard.

How do I test webhooks on localhost?

Start your local server, open a public HTTPS tunnel to that port, configure the provider webhook URL, and inspect events in your local logs.

Why does a stable webhook URL matter?

Stable URLs prevent provider dashboards from needing manual callback updates every time you restart a tunnel.

Data science and machine learning development happens predominantly on local machines or dedicated GPU workstations. Whether you’re iterating on exploratory analysis inside a Jupyter notebook, building a prototype with Streamlit, or serving predictions via FastAPI, your local environment is the primary workbench.

A recurring challenge shows up the moment you need to share that work. Demonstrating an interactive model to a remote stakeholder, testing an inbound webhook from a payment gateway, or letting a mobile app hit an endpoint running on your laptop usually means deploying to a remote server — and cloud deployment introduces friction: containerization overhead, cost, deployment delay, and configuration drift between environments.

This is the gap a localhost tunnel is built to close. While ngrok dominates general web development, Pagekite is a much older, still-maintained project worth knowing about specifically because its reference client is a single, dependency-light Python script rather than a compiled binary — a property that matters more than it sounds like it should in regulated or security-conscious environments.

The Local Development Friction in Modern Data Science

+-----------------------------------------------------------------------+
|                         Local Workstation                             |
|                                                                       |
|  +-------------------+    +--------------------+    +--------------+  |
|  | JupyterLab / Notebook | | Streamlit / Gradio | | FastAPI / ML |  |
|  |   (Port 8888)     |    |    (Port 8501)     |    | (Port 8000)  |  |
|  +---------+---------+    +---------+----------+    +-------+------+  |
|            |                        |                   |             |
+------------+------------------------+-------------------+-------------+
                                      |
                           ( Blocked by NAT / Firewall )
                                      |
                                      x
                          [ Public Internet / Clients ]

Three sharing bottlenecks show up constantly:

  • Demonstrating interactive notebooks. A .ipynb file shared over email or GitHub only renders static output. Giving a client or PM a live, interactive session means exposing your running Jupyter server.
  • Testing webhooks and external callbacks. Slack bots, GitHub triggers, and Stripe/Twilio integrations all need a public URL that routes inbound POST requests straight to your machine.
  • Remote API integration. Frontend engineers building against a locally hosted model endpoint need a reachable URL before the model is staged anywhere.

Manually provisioning an EC2 box, hand-rolling an SSH reverse tunnel, or installing a compiled binary that trips corporate application-whitelisting are all real friction points that a tunnel is meant to remove.

What Is Pagekite? Architecture of a Python-Powered Tunnel

Pagekite is an open-source tunneling project originally written by Bjarni Rúnar Einarsson and maintained under The Beanstalks Project ehf., an Icelandic company — the project has been running since around 2010, which makes it one of the older tools in the localhost-tunneling space, predating ngrok’s public launch. It creates a tunnel from a public relay server (a “front-end,” commonly hosted at pagekite.net) to a local service running behind NAT or a restrictive firewall.

                                PAGEKITE ARCHITECTURE

 +------------------------+              +------------------------+
 |   Local Machine        |              |  Pagekite Public Relay |
 |                        |              |   (Front-end / Cloud)  |
 | +--------------------+ |              |                        |
 | | Local App          | |              |                        |
 | | (Jupyter/FastAPI)  | |              |                        |
 | +---------+----------+ |              |                        |
 |           | Local HTTP |              |                        |
 | +---------v----------+ |  Encrypted   |                        |
 | | Pagekite Client    |<==============>| Public HTTP/HTTPS      |
 | | (pagekite.py)      | |  Tunnel      | Front-end Listener     |
 | +--------------------+ |              +-----------+------------+
 +------------------------+                          |
                                                     |
                                            +--------v--------+
                                            | Remote Client / |
                                            | Web Browser     |
                                            +-----------------+

Key architectural characteristics

  • Reference client is plain Python. pagekite.py is a single Python script with no compiled C extensions, Rust, or Go runtime required for the back-end/client role. Note one nuance: if you run your own front-end relay rather than using the hosted pagekite.net service, terminating TLS on that relay needs OpenSSL and either a modern Python 3 or the pyOpenSSL module — so “zero dependencies” is really “zero dependencies for the common client use case.”
  • Python 3 is supported today. The current pagekite.net homepage documents installation with python3 pagekite.py 80 yourname.pagekite.me. Some of the project’s older wiki pages (the “QuickStart Guide” for the 0.3.x/0.4.x line) still instruct readers to install Python 2.x — that’s stale documentation left over from the project’s early years, not a sign the tool itself is Python-2-only; Python 3 support landed as an explicit PyPagekite release years ago.
  • Protocol versatility. Beyond HTTP/HTTPS, Pagekite tunnels raw TCP, SSH, and other TCP-based services.
  • Self-hosting is real and native. You can use the hosted relay at pagekite.net, or run your own front-end (pagekitefront) inside your own infrastructure — the relay code itself is free software, not a paid-only feature gated behind a license key.
  • Built-in dynamic DNS. The client has native support for updating dynamic-DNS providers (dyndns.org, no-ip.com, or a custom HTTP(S) endpoint) so a public name keeps pointing at your current IP as it changes across networks.
  • A wider ecosystem than just the Python script. Beyond pagekite.py, the project also maintains libpagekite, a C implementation aimed at high-performance or embedded use, and upagekite, a MicroPython port intended for ESP32-class microcontrollers — relevant if your “local service” is closer to a sensor than a laptop.

Correcting the license: it’s AGPL, not GPLv2/Apache

pagekite.py itself is released under the GNU Affero General Public License (AGPL), copyright Bjarni Rúnar Einarsson and The Beanstalks Project ehf. Documentation is licensed CC BY-SA 3.0, and sample configuration files are public domain. The Apache-2.0-or-AGPL dual license you may see referenced belongs to libpagekite, the separate C library — not to the Python client most developers actually run. For a security review, this distinction matters: AGPL’s network-use clause is stricter than a permissive Apache license, and it’s the license that actually applies to the script running on your machine.

Python-Native Stack Advantages

Feature Benefit for ML / Data Teams
No compiled runtime to install The client is one .py file — drop it in a venv, a Docker image, or next to your training scripts with no binary to vet
Code auditability Plain, uncompiled Python source lets InfoSec review exactly what a tunnel does before approving it
Subprocess-level integration Launch and manage the tunnel from inside a training or serving script using standard Python process libraries
Virtual environment friendly The script itself has no packaging system to fight — it runs inside whatever venv/conda env already has Python 3

A correction worth flagging: there is no pip install pagekite

The original draft of this piece (and a number of other write-ups around the web) suggest pip install pagekite. As of this check, there is no actively maintained pagekite package on PyPI that corresponds to this tool — the closest name-match on PyPI is an unrelated static-site package called pagekit. The project’s own documentation consistently points to one of these instead:

# Download the script directly (the officially documented method)
curl -O https://pagekite.net/pk/pagekite.py
chmod +x pagekite.py

# Or, on Debian/Ubuntu, via the project's own apt repository
echo "deb http://pagekite.net/pk/deb/ pagekite main" | sudo tee -a /etc/apt/sources.list
sudo apt-get update && sudo apt-get install pagekite

Debian and Ubuntu also carry pagekite in their own official repositories (sudo apt install pagekite), though the project notes its own repo tends to ship newer releases than the distro-maintained package.

How to Expose a Jupyter Notebook with Pagekite

Exposing an interactive Jupyter environment to remote collaborators requires both connectivity and access control — an unauthenticated Jupyter session handed a public URL gives anyone who finds it full code-execution rights on your machine.

                  SECURE JUPYTER TUNNEL WORKFLOW

+----------------------+     +----------------------+     +----------------------+
| 1. Configure Jupyter |     | 2. Launch Local Server|    | 3. Create Pagekite   |
|    Set Strong Token  |---->|    Bind to 127.0.0.1    |---->|    Encrypted Tunnel  |
|    or Password       |     |    Port 8888         |     |    https://...       |
+----------------------+     +----------------------+     +----------------------+

Step 1: Get the Pagekite client

curl -O https://pagekite.net/pk/pagekite.py
chmod +x pagekite.py

Step 2: Secure Jupyter before you tunnel anything

jupyter notebook --generate-config
jupyter notebook password

Or launch with an explicit token and bind to loopback only:

jupyter lab --ip=127.0.0.1 --port=8888 --NotebookApp.token='a_very_long_secure_random_token_12345'

Step 3: Launch the tunnel

python3 pagekite.py 8888 mynotebook.pagekite.me

The first run walks you through creating a free account (or signing in), and stores an authentication key locally in ~/.pagekite.rc. On success you’ll see terminal output confirming the local port is now reachable at https://mynotebook.pagekite.me/.

Step 4: Lock the tunnel down further, correctly

The earlier version of this guide invented flags like --allow=<ip> and --opt/basicauth=user:pass. Those aren’t real Pagekite options. The actual, documented access-control mechanism is a pair of + flags appended to the kite definition itself:

# Only allow a specific IP (or /24 subnet) to reach this kite
python3 pagekite.py 8888 mynotebook.pagekite.me +ip/203.0.113.45=ok

# Require HTTP Basic Auth in addition to (or instead of) Jupyter's own token
python3 pagekite.py 8888 mynotebook.pagekite.me +password/admin=ComplexPassword123!

Multiple +ip/ or +password/ flags can be stacked to allow more than one address or credential pair. Separately, pagekite.py has shipped a basic built-in request firewall since version 0.5 that blocks common attack paths (like /wp-admin/) by default; it can be turned off with --insecure globally or +insecure per kite, and turns itself off automatically once you’ve set up +password/ or +ip/ access control anyway.

Exposing FastAPI & Streamlit Model Endpoints

Scenario A: A Streamlit dashboard

streamlit run app.py --server.port 8501 --server.address 127.0.0.1

To force HTTPS on the public side, prefix the protocol onto the kite name itself — there’s no separate --service=https flag:

python3 pagekite.py 8501 https:my-analytics.pagekite.me

Scenario B: A FastAPI inference endpoint, launched programmatically

Instead of two terminal windows, you can spawn the tunnel as a subprocess alongside your ASGI server:

import subprocess
import time
import uvicorn
from fastapi import FastAPI

app = FastAPI(title="Local Machine Learning Inference API")

@app.get("/")
def read_root():
    return {"status": "online", "model": "RandomForestClassifier_v2"}

@app.post("/predict")
def predict(features: dict):
    processed_val = sum(features.values()) if features else 0
    return {"prediction": processed_val * 1.5, "confidence": 0.94}

def start_pagekite_tunnel(port: int, subdomain: str):
    """Launches pagekite.py as a background process tied to the local port."""
    cmd = ["python3", "pagekite.py", str(port), f"https:{subdomain}.pagekite.me"]
    print(f"[*] Starting Pagekite tunnel on port {port}...")
    process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    time.sleep(2)  # give the tunnel time to establish
    print(f"[+] Tunnel online: https://{subdomain}.pagekite.me")
    return process

if __name__ == "__main__":
    PORT = 8000
    SUBDOMAIN = "my-ml-model-api"

    tunnel_process = start_pagekite_tunnel(port=PORT, subdomain=SUBDOMAIN)
    try:
        uvicorn.run(app, host="127.0.0.1", port=PORT)
    finally:
        print("[*] Terminating Pagekite process...")
        tunnel_process.terminate()

Worth noting: this is process management, not a dedicated SDK — Pagekite doesn’t ship an official importable Python client library separate from the CLI script, so “programmatic integration” here means spawning and supervising the same script you’d run by hand, not calling into a purpose-built API.

Pagekite vs ngrok: A Data Science Comparison

Metric Pagekite ngrok
Client language Pure Python (client role) Go
Client license GNU AGPL (pagekite.py); libpagekite C library is Apache-2.0/AGPL dual-licensed Closed-source, SaaS
Installation curl the script, or apt/rpm package — no official PyPI package Single precompiled binary
Self-hosting the relay Native — run your own front-end, the relay code is free software Not available on standard consumer/team plans
Custom domains CNAME-based custom domains supported directly; true apex/root-domain support isn’t clearly documented by the project and shouldn’t be assumed Subdomain custom domains via CNAME on the paid Pay-as-you-go plan; ngrok’s own docs state apex/root domains are not supported on any plan
Protocol support HTTP, HTTPS, raw TCP, SSH HTTP, HTTPS, TCP, TLS tunnels
Pricing model Pay-what-you-want (suggested $3/month), free tier for non-commercial/FOSS use, subscription/team plans from $5.99/month, white-label bulk from $199.95/month for up to 500 devices Free tier (limited), Hobbyist $10/month ($8/month billed annually), paid tiers scale up from there

The comparative points that actually hold up

Binary vs. script. ngrok ships a compiled Go binary; Pagekite’s client is an inspectable Python script. In environments where application whitelisting blocks unsigned executables, that’s a genuine practical difference, license technicalities aside.

Self-hosting. If your data includes PII or regulated healthcare/financial records, Pagekite’s ability to run its own relay entirely inside your infrastructure is real and documented — this isn’t a paid add-on, it’s the same open-source relay code the hosted service runs.

Pricing shape, not just price. ngrok’s free and Hobbyist tiers are metered on bandwidth and endpoint count. Pagekite’s pricing is structured around a “pay what you want” model with a suggested minimum, plus flat monthly subscription tiers rather than per-GB overage charges — a genuinely different pricing philosophy worth knowing about before you assume “cheaper” or “more expensive” in either direction.

Security Best Practices for a Pagekite-Based Reverse Proxy

+-------------------------------------------------------------------+
|                  TUNNEL SECURITY LAYER STRATEGY                   |
|                                                                   |
|  [ Public Internet ]                                              |
|         |                                                         |
|         v                                                         |
|  +-------------------------------------------------------------+  |
|  | Layer 1: Transport Encryption (prefix the kite with https:) |  |
|  +------------------------------+------------------------------+  |
|                                 |                                 |
|                                 v                                 |
|  +-------------------------------------------------------------+  |
|  | Layer 2: Access Control (+ip/ and +password/ flags)          |  |
|  +------------------------------+------------------------------+  |
|                                 |                                 |
|                                 v                                 |
|  +-------------------------------------------------------------+  |
|  | Layer 3: Application Auth (Jupyter Token / API Keys)         |  |
|  +------------------------------+------------------------------+  |
|                                 |                                 |
|                                 v                                 |
|  [ Local Application / Notebook / FastAPI ]                       |
  1. Force HTTPS. Prefix the kite name: python3 pagekite.py 8888 https:mynotebook.pagekite.me.
  2. Restrict by IP where you can. +ip/203.0.113.45=ok (single address) or +ip/203.0.113=ok (a /24 netblock).
  3. Add HTTP Basic Auth at the tunnel layer. +password/admin=ComplexPassword123! — useful as a second factor in front of an app that has weak or no auth of its own.
  4. Run the local service unprivileged. Keep Jupyter/FastAPI processes off root, and keep them out of directories containing .env files or SSH keys — Pagekite forwards whatever the local port serves, so process hygiene on the local side still matters.

Handling Large Data Payloads

If FastAPI is returning bulky JSON or image payloads, enable response compression before the data crosses the tunnel:

from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware

app = FastAPI()

# Note the capitalization: it's GZipMiddleware, not GzipMiddleware
app.add_middleware(GZipMiddleware, minimum_size=500)

Maintaining Persistent Background Tunnels

nohup python3 pagekite.py --logfile=/var/log/pagekite.log 8888 mynotebook.pagekite.me &

For anything you want surviving reboots, wrap this in a systemd unit rather than relying on nohup alone.

Troubleshooting Common Connection Issues

Symptom Likely Cause Fix
“Connection Refused” Local service isn’t actually listening on the port you specified Confirm with curl http://127.0.0.1:PORT or netstat before blaming the tunnel
“Subdomain Unavailable” The requested name.pagekite.me is already taken Choose a different prefix, or check that your existing kite credentials are in ~/.pagekite.rc
Slow responses Payload size or NAT/MTU mismatch Enable response compression (see above) or reduce payload size

Integrating Pagekite into MLOps Pipelines

                        MLOps INTEGRATION PIPELINE

 +-------------------+     +--------------------+     +---------------------+
 | GitHub Actions /  |     | Spawn Ephemeral    |     | Run Pagekite Tunnel |
 | Local CI Runner   |---->| Model Container    |---->| To Expose Webhook   |
 +-------------------+     +--------------------+     +----------+----------+
                                                                 |
                                                                 v
 +-------------------+     +--------------------+     +---------------------+
 | Teardown Tunnel   |     | Assert Webhook     |<----| Trigger External    |
 | & Report Results  |<----| Payload Delivery   |     | Service Test        |
 +-------------------+     +--------------------+     +---------------------+

In end-to-end integration tests that need to verify an external service can actually reach an internal webhook handler, spinning up a short-lived, uniquely-named kite (test-run-$GITHUB_RUN_ID.pagekite.me) inside the test runner, triggering the external call, asserting the payload arrived, and tearing the tunnel down again avoids paying for a permanent staging environment solely for webhook verification.

Beyond the Python Client: libpagekite and upagekite

Two lesser-known parts of the Pagekite project are worth knowing about if your work extends past notebooks and APIs into embedded or resource-constrained devices:

  • libpagekite is a from-scratch C implementation of the same protocol, aimed at high-performance or embedded deployments where spinning up a Python interpreter isn’t practical.
  • upagekite is a MicroPython port targeting ESP32-class microcontrollers, letting a sensor or IoT device fly its own kite without a general-purpose OS underneath it.

If you’re already using Pagekite for a data-science API and later need to pull telemetry off an ESP32 board, that’s the same underlying front-end infrastructure, not a different vendor.

Is Pagekite Still a Good Fit in 2026?

Worth being honest about, in the interest of not overselling this: pagekite.net’s own blog hasn’t posted a public update since October 2021, and the GitHub repository for the Python client carries a standing note that it’s “under active development” and “may at times be somewhat unstable.” That’s a slower cadence than ngrok or newer entrants like zrok or Pinggy, both of which ship changelog entries regularly.

That said, as of this check the core service is still live, the download and signup flow works, Python 3 is the documented path, and the pricing/FAQ pages are current — this reads less like an abandoned project and more like mature, low-churn infrastructure maintained by a small team. For a corporate environment where an auditable, self-hostable, script-based tunnel is the actual requirement — rather than the newest feature set — that stability profile can be a reasonable trade-off. For teams that want active support channels, a polished dashboard, or frequent feature releases, the newer tools already covered elsewhere in this series are a closer fit.

Streamlining the Python Tunneling Stack

A localhost tunnel solves a real bottleneck: bridging local development and the public web without standing up cloud infrastructure just to demo something. Pagekite’s pitch — a script-based, self-hostable, AGPL-licensed client backed by an Icelandic operator running since 2010 — is genuinely different from ngrok’s closed-source SaaS model, and the difference is real even after correcting the specific claims that didn’t hold up. Whether it’s the right tool depends more on whether your priority is auditability and self-hosting or feature velocity and polish.


Editorial changelog (fact-checked Sept 6, 2026)

Corrections made to the original draft:

  • License: corrected from an invented “GPLv2/Apache” to the actual GNU AGPL under which pagekite.py is released (Apache-2.0/AGPL dual license applies only to the separate C library, libpagekite, not the Python client).
  • Installation: removed the fabricated pip install pagekite — there is no actively maintained PyPI package for this tool; replaced with the project’s actual documented install paths (direct script download via curl, the project’s own apt repository, or distro packages).
  • Python 2 vs 3: added the nuance that some of the project’s own older wiki pages still reference Python 2.x, but the current homepage and PyPagekite release notes confirm Python 3 support.
  • CLI syntax for HTTPS: removed the invented --service=https flag; replaced with the real mechanism of prefixing the protocol onto the kite name (e.g., https:name.pagekite.me).
  • CLI syntax for security: removed the invented --allow=<ip> and --opt/basicauth=user:pass flags; replaced with the real, documented +ip/ and +password/ per-kite flags, and added the real detail about the built-in request firewall since v0.5 and the --insecure/+insecure opt-out.
  • Code correction: fixed GzipMiddleware to the correct FastAPI/Starlette class name, GZipMiddleware.
  • Added real pricing: the original draft had no pricing section; added Pagekite’s actual pay-what-you-want model, subscription tiers, and white-label bulk pricing from pagekite.net, and tightened the ngrok side of the comparison to match previously verified figures for this series (no session timeout on ngrok’s free tier; custom domains are subdomain-only via CNAME on ngrok’s paid tier, with no apex/root domain support on any plan).
  • Added company/creator attribution: named Bjarni Rúnar Einarsson and The Beanstalks Project ehf. (Iceland) as the project’s origin, which the original draft omitted.
  • Added ecosystem context: libpagekite © and upagekite (MicroPython/ESP32) weren’t mentioned in the original draft.
  • Added an honest maintenance-status assessment: noted the project’s public blog has been quiet since October 2021 and the GitHub repo’s own stability disclaimer, while confirming the service and downloads are still live and current as of this check.
  • Softened an unverified claim: the original comparison table asserted Pagekite has full custom apex/root domain support; no documentation was found to substantiate that beyond CNAME-based custom domains, so the claim was walked back to “not clearly documented” rather than presented as confirmed.
  • Removed the Meta Description line, per standard formatting for this series.

Continue from this article into the most relevant product guides and workflows.

Related Topics

#Python localhost tunnel, Pagekite vs ngrok, data science reverse proxy, expose Jupyter notebook localhost, machine learning localhost, FastAPI tunnel, Python reverse proxy, local dev environment Python, expose local server, data science tools, Python data engineering, pagekite python 3, pagekite alternative, ngrok alternative python, open source reverse proxy python, expose FastAPI localhost, Jupyter notebook remote access, secure tunnel python, localhost to internet python, data scientist workflow, MLOps local testing, machine learning webhook testing, python web server expose, route localhost internet, Pagekite tutorial data science, Pagekite configuration, bypass NAT python, expose machine learning API, local python API public URL, ngrok vs pagekite performance, Jupyter notebook public IP, python developer tools, reverse proxy for data engineers, self-hosted frontend pagekite, python tunneling solution, data science local environment, fastAPI public endpoint, testing local python API, pagekite python package, pagekite frontend backend, local web server public url, python localhost forward, machine learning dev environment, data science webhooks, pagekite proxy server, expose ML model localhost, Jupyter notebook port forward, python proxy tunnel, data engineering workflow, python backend tunnel, local API internet access, python localhost reverse proxy, remote access to Jupyter notebook, test FastAPI online, Pagekite machine learning

Keep building with InstaTunnel

Read the docs for implementation details or compare plans before you ship.

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles