Self-hosted service
abhinavsingh/proxy.py avatar
abhinavsingh/proxy.py

proxy.py: a Python proxy server that is really a plugin framework

💫 Ngrok FRP Alternative • ⚡ Fast • 🪶 Lightweight • 0️⃣ Dependency • 🔌 Pluggable • 😈 TLS interception • 🔒 DNS-over-HTTPS • 🔥 Poor Man's VPN • ⏪ Reverse & ⏩ Forward • 👮🏿 "Proxy Server" framework • 🌐 "Web Server" framework • ➵ ➶ ➷ ➠ "PubSub" framework • 👷 "Work" acceptor & executor framework

3,553 stars625 forksPythonBSD-3-Clause

At a glance

What is it?
The repository ships one installable package with no runtime dependencies and a long plugin list, and it now also carries Grout, its own tunnel service in place of ngrok. What is actually in the code is worth separating from what the badges promise.
Who is it for?
proxy.py makes sense when the job is a small scriptable proxy that has to run on a laptop, in a test, or on a machine with nothing but the Python standard library, and when you intend to write a plugin rather than configure a product. It does not make sense as a production edge proxy, because the TLS interception path needs certificates you generate yourself and an OpenSSL binary the container installs only if you ask for it.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 7, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One package, no runtime dependencies, and a plugin surface that does the work

The identity badge on the README says dependencies: 0, and the Dockerfile repeats the same claim in a comment rather than as marketing copy. Everything proxy.py needs to accept a connection, parse HTTP, and forward it comes from the Python standard library. The cost of that choice is visible in the badge row too: the Python versions listed are 3.6 through 3.12, and the project is checked with mypy, which is a sensible combination for a codebase that implements a protocol by hand.

What the project actually is, though, is not a proxy in the way most people mean the word. The repository description reads like a feature list because the author is describing four frameworks stacked in one repository: a proxy server, a web server, a pubsub framework, and a work acceptor and executor framework. The table of contents in the README backs that up. Instead of one screen of documentation there is a long list of named plugins, and the sections after them are arranged by transport role rather than by feature.

The directory tree supports the same reading. `proxy/` holds the package itself, `examples/` holds ten small programs including `https_connect_tunnel.py`, `pubsub_eventing.py`, `websocket_client.py` and `web_scraper.py`, and alongside them sit `tutorial/`, `benchmark/`, `dashboard/`, `helper/` and `skeleton/`. A `skeleton/` directory is a strong hint about how a contributor is expected to work here: start from a template rather than guess at a layout.

Installing it: pip, a container, or the make targets in the repository root

The container path is the one the project itself records, in a label inside the Dockerfile so the command shows up in image metadata:

bash
docker run -it --rm -p 8899:8899 abhinavsingh/proxy.py

Port 8899 is the one number worth remembering, because it appears in that label and in the README's own startup sections. The Dockerfile builds from a base image published at `ghcr.io/abhinavsingh/proxy.py:base`, sets `PYTHONUNBUFFERED` so logs appear immediately, and installs the project with `pip install --no-index --find-links file:///` against a locally built wheel. That last detail matters if you are reading the file to understand the build: the container does not download proxy.py from PyPI, it copies a wheel in and installs that.

The Makefile is the more informative file for a developer, because it names the whole workflow. It defaults `PYTHON` to `python`, builds the container image under the `abhinavsingh/proxy.py` namespace, and defines the certificate targets that TLS interception depends on:

bash
https-certificates:
	# Generate server key
	$(PYTHON) -m proxy.common.pki gen_private_key \
		--private-key-path $(HTTPS_KEY_FILE_PATH)
	$(PYTHON) -m proxy.common.pki remove_passphrase \
		--private-key-path $(HTTPS_KEY_FILE_PATH)

Note what `remove_passphrase` is doing there. A server key that has no passphrase is what you need to hand the process unattended, and that is also a reason to keep generated keys out of a repository. The same Makefile declares `container-buildx-all-platforms`, `dashboard`, `lib-lint`, `lib-pytest` and a `container-without-openssl` target, so the standard developer loop is a make target rather than a documented script.

The plugin list is the documentation, and it is unusually long

The README's table of contents lists the plugins by name, and reading that list is the fastest way to understand the project's centre of gravity. There is a ShortLink plugin that rewrites responses, a ModifyPostData plugin, a MockRestApi plugin that fakes an API without a backend, a RedirectToCustomServer plugin, FilterByUpstreamHost and FilterByClientIp plugins, a ModifyRequestHeader and a ModifyChunkResponse plugin, and both a CacheResponses plugin and a cache-by-response-type variant.

Several of those are small enough to be understood in one sentence each, and that is the point. A reverse proxy that is only a reverse proxy is a solved problem and a boring one to adopt. What proxy.py offers is a place to put thirty lines of Python that inspect or rewrite traffic in flight, in a process you can start with one command.

Two entries on that list stand out for what they tell you about scope. `ManInTheMiddlePlugin` is a documented feature rather than something you would have to build, and it is also the entry that raises the most operational questions, because intercepting TLS means generating certificates your clients will have to trust. `ProxyPoolPlugin` sits on the opposite end: it is a mechanism for distributing traffic across a set of upstream proxies, which is the kind of thing you build when a single upstream is rate limiting you.

There is also a `ProgramNamePlugin`, which is small but tells you how the framework is put together: the entry point is a name that resolves to a class, and the project uses that same indirection for plugins and routes. The docs list a section on plugin ordering, so what happens when two plugins both want to touch the same request is a documented question rather than an accident.

TLS interception needs two things the repository will not do for you

The README's own table of contents splits this into an ordinary section and an explicitly named one, which is an honest signal. The ordinary section covers TLS interception; the next section is titled Insecure TLS Interception and then TLS Interception With Docker. Nobody names a section that way unless the default path is unsafe.

The Dockerfile explains the dependency. proxy.py needs no external packages, but TLS interception needs OpenSSL, so the image build takes an argument named `SKIP_OPENSSL` and the comment says to use `--build-arg SKIP_OPENSSL=1` to leave it out. In other words the OpenSSL binary in that image is optional and off in some configurations, which means a container built the quick way can refuse to do the one feature most people install a proxy for.

The certificate story is where the Makefile earns its place. It defines a set of paths for a server key, a server certificate, a CSR, a signed certificate, and a separate CA key and CA certificate, then generates them through a `proxy.common.pki` command module. The README's TOC also has a section on end to end encryption, which is the honest counterweight: if you need the client to see plaintext, you are no longer providing end to end encryption, and the repository documents both worlds rather than pretending the second does not exist.

For a reader deciding whether to adopt this, the practical question is who generates and distributes the CA certificate. The project gives you the tool to make one and does not give you a policy for trusting it on client devices.

Grout: the repository now carries its own answer to ngrok

The TOC dedicates more space to Grout than to anything except the plugin list, and it gives it a subtitle: GROUT (NGROK Alternative). The section headings walk through Grout usage, Grout authentication, Grout paths, Grout wildcard domains, and then two routing strategies, one based on the Host header and one called dynamic routing. There is a Grout client plugin section and a Grout using Docker section, and the section How Grout works is the one a reader should look for.

The clearest fact in that list is Self-hosted Grout. The point of a tunnel service is that something on the public internet accepts connections and forwards them to a machine you cannot expose directly. Grout being self-hostable means that the public-facing half can live on a server you control, and a client half on the laptop behind NAT.

The release history shows this being built rather than announced. v2.4.8 added support for a custom `Upgrade: Derp` protocol and conditional TLS interception, and v2.4.9 added a `GroutClientBasePlugin` along with the example `GroutClientPlugin`, then a way for that base plugin to modify the request object, and finally documentation for host header and dynamic routing. Those three pull requests in one release read like a feature being assembled in public, which is a useful thing to know before you depend on it.

The TOC also lists Proxy Over SSH Tunnel, split into proxying remote requests locally and proxying local requests remotely. That is the pragmatic alternative to a tunnel service: it needs a host you can reach over SSH and nothing else. For many day to day debugging tasks it is the better answer, and the fact that both paths live in the same repository makes the comparison easy.

Where the code answers the question and where the documentation takes over

The repository itself is honest about its release cadence, which is a good sign. The last recorded push to the default branch, `develop`, was on 2026-08-31, and the most recent tagged release is v2.4.10 from 2025-02-18, after v2.4.9 in October 2024 and v2.4.8 in September 2024. Work is landing on develop between releases, which is the normal setuptools-scm arrangement and is confirmed by the build configuration writing the computed version into `proxy/common/_scm_version.py`.

That build configuration also says how the changelog is maintained. `pyproject.toml` configures towncrier to read fragments from `docs/changelog-fragments.d/` and write `CHANGELOG.md`, with typed sections for bugfixes, features, deprecations, backward incompatible changes, documentation, miscellaneous work and contributor-facing changes. The presence of a dedicated deprecation section labelled for removal in the next major release is a better maintenance signal than a release cadence alone.

toml
[tool.setuptools_scm]
write_to = "proxy/common/_scm_version.py"

[tool.towncrier]
directory = "docs/changelog-fragments.d/"
filename = "CHANGELOG.md"

What the repository does not settle is how any of this behaves at scale. There is a `benchmark/` directory and a `dashboard/` directory, but the README points elsewhere for the substance: the documentation is published at proxypy.readthedocs.io, the homepage is a written post describing the project as a lightweight single file HTTP proxy server in Python, and codecov is wired into the branch coverage badge. The TOC also has a section on unit testing with proxy.py, including a `proxy.TestCase` helper and a note about overriding startup flags, which is the piece most worth reading if you plan to test code that runs through the proxy rather than the proxy itself.

The licence is BSD-3-Clause, which is permissive enough that embedding the package in an internal tool is a straightforward decision. The 89 open issues are worth a look for a project at this stage, but the more useful number for an adopter is how many releases have shipped, and three tags over the recorded history suggests a small, deliberate maintainer rather than a crowded one.

Editorial conclusion

proxy.py makes sense when the job is a small scriptable proxy that has to run on a laptop, in a test, or on a machine with nothing but the Python standard library, and when you intend to write a plugin rather than configure a product. It does not make sense as a production edge proxy, because the TLS interception path needs certificates you generate yourself and an OpenSSL binary the container installs only if you ask for it. Grout is the more interesting half for anyone who wanted a self-hosted ngrok, and the README is candid that you can run the server side yourself. Start by running the container on port 8899 and reading the plugin ordering section before wiring anything into a pipeline.

Frequently asked questions

What is proxy.py in Python actually used for?

It is a proxy server, a web server and a plugin host in one package with no runtime dependencies. The README's plugin list is the clearest evidence: plugins for caching responses, mocking a REST API, rewriting headers, redirecting to a custom server, filtering by upstream host or client IP, and intercepting TLS all ship with the project. The typical use is a scriptable proxy that runs from one command and can be extended in Python.

Does proxy.py need any pip packages to run?

The dependency badge reads 0 and the Dockerfile states in a comment that proxy.py itself needs no external dependencies. The one exception is TLS interception, which needs OpenSSL, and the image build takes an argument called SKIP_OPENSSL for turning that installation off. Outside the proxy package, the repository's own development tooling uses setuptools-scm and towncrier, configured in pyproject.toml.

How do I generate certificates for proxy.py TLS interception?

The Makefile has an https-certificates target that calls the bundled PKI module, for example `python -m proxy.common.pki gen_private_key` followed by `remove_passphrase`, writing to paths such as HTTPS_KEY_FILE_PATH and CA_CERT_FILE_PATH. The README also documents an insecure TLS interception mode and a Docker-specific section, so read those before trusting the generated CA on client devices. Nothing in the repository ships a certificate policy for you.

What is Grout in proxy.py, and is it a replacement for ngrok?

Grout is a tunnel feature the repository now ships, described in the README as an ngrok alternative, and the documentation includes a section on self-hosting it. The release notes for v2.4.9 added a GroutClientBasePlugin, an example client plugin, and documentation for host header and dynamic routing. The repository also documents a simpler path, proxying over an SSH tunnel, for cases where you already have a reachable host.

Official sources

  1. abhinavsingh/proxy.py on GitHub
  2. License: BSD-3-Clause
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/abhinavsingh-proxy-py.svg)](https://hysenlabs.com/projects/abhinavsingh-proxy-py)