eooce/python-ws: vless, trojan and shadowsocks proxies on a plain Python server
build vless / trojan /shadowsocks proxies on python server,no need core
At a glance
- What is it?
- A Python-only proxy server that implements three protocols without a compiled core, meant for PaaS containers. Here is what the README documents, what it leaves out, and who should look elsewhere.
- Who is it for?
- Adopt eooce/python-ws if you already run a Python container on a PaaS such as Koyeb and want all three protocols from one app.py with no compiled core to deploy. Do not adopt it if you need a documented configuration reference, an upgrade path, or a deployment where a leaked UUID is acceptable; the README does not cover rotation or rollback.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 81 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 September 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What eooce/python-ws actually replaces
Most proxy stacks ship a compiled binary: Xray, sing-box, v2ray, or a Go program that you cross-compile for the target architecture. eooce/python-ws takes the opposite route. The repository contains app.py, index.html and requirements.txt, and the requirements file lists only aiohttp and requests. There is no vendored core, no binary to fetch at build time, and no architecture-specific artifact. The project description puts it plainly: build vless, trojan and shadowsocks proxies on a Python server, with no need for a core.
That matters on PaaS platforms where you cannot choose a base image freely or where pulling a release binary from GitHub at build time is awkward. The README frames the target as a toy and a container for Python environments, which is an honest description of the scope. If you want a hardened, audited proxy implementation, this is not it. If you want three protocols running from one Python file inside a container you already control, the trade is legible.
The repository also points to sibling ports: node-ws for Node.js, java-ws for Java, and a Golang branch under the node-ws tree. The same author maintains all of them, so the protocol coverage is consistent across runtimes and the choice between them is mostly about which language your container already speaks.
One process, three protocols, environment-driven configuration
The architecture visible in the repository is a single Python entry point. app.py is the whole server; index.html is served as the subscription page; requirements.txt pins nothing, which means aiohttp and requests resolve to whatever is current at build time. The Dockerfile runs python3 app.py as the container command after installing those two packages.
Configuration arrives entirely through environment variables, which is what makes the PaaS story work. The README documents eight of them. UUID has a hardcoded default, 5efabea4-f6d4-91fd-b8f0-17e004c89c60, and the README explicitly warns that if you enable Nezha v1 you must change it. PORT defaults to 3000 but the README says the default is to pick up the port the platform assigns. DOMAIN is marked required and must be the assigned domain or a reverse-proxied domain, without the https:// prefix. SUB_PATH defaults to sub and controls the subscription token. AUTO_ACCESS defaults to false and, when set to true, requires DOMAIN to be filled in as well. DEBUG defaults to false. The Nezha probe variables differ by version: NEZHA_SERVER takes nz.abc.com:8008 for v1 and nz.abc.com for v0, NEZHA_PORT exists only for v0, and NEZHA_KEY holds either the v1 NZ_CLIENT_SECRET or the v0 agent port.
Node information is served at domain/${SUB_PATH}, and on a non-standard port at domain:port/${SUB_PATH}. The README spells the variable as SUB_APTH in two places and SUB_PATH in the table, which is a typo in the documentation rather than a second variable; the table is the authoritative spelling. That kind of inconsistency is worth knowing about before you debug a subscription URL that will not load.
Installing eooce/python-ws and getting a subscription URL
There is no package on PyPI and no installer. You get the code from the repository and run it, either directly with Python or through the provided Dockerfile. The Dockerfile builds on python:3.12-alpine, sets the working directory to /tmp, copies app.py, requirements.txt and index.html in, exposes port 3000, installs openssl, bash and curl via apk, installs the Python requirements, and starts the server.
The Dockerfile is the only install path the repository defines, so the commands below are the ones it contains:
FROM python:3.12-alpine
WORKDIR /tmp
COPY app.py requirements.txt index.html ./
EXPOSE 3000
RUN apk update && apk --no-cache add openssl bash curl &&\
chmod +x app.py &&\
pip install -r requirements.txt
CMD ["python3", "app.py"]Those lines are the whole build: a Python 3.12 Alpine base, three packages added with apk, and the two entries from requirements.txt installed with pip. The container command is python3 app.py, and port 3000 is the one the image declares.
Once it is running, the subscription page is at the path formed from SUB_PATH, which defaults to sub. With the default, you open https://your.domain.example/sub in a client that supports subscription URLs. If you set SUB_PATH to something else, that token replaces sub in the path. On a non-standard port the URL takes the form domain:port/sub.
The README suggests pairing the deployment with a Cloudflare Worker or snippet that reverse-proxies the domain, and gives a worker script that picks a random hostname from an array and rewrites the request to it. The array holds a single entry, 'change.your.domain', which you replace with your own node's disguise domain. That is a CDN front, not part of the proxy itself, and the README notes a port-origin approach as an alternative.
Where the documentation stops
The README is a configuration table plus a worker snippet. It does not document the protocol internals, the wire format, or how the three protocols share the listening port. It does not say what happens when DOMAIN is missing beyond marking it required, and it does not describe error output or log format, even though a DEBUG variable exists. If the server fails to start on a PaaS platform, the README gives you no diagnostic path.
There is no upgrade procedure. The Dockerfile does not pin aiohttp or requests, so a rebuild can pull a newer version of either library than the one you tested against, and nothing in the repository records which versions were known good. There is no migration note for the environment variables, no changelog beyond a single release entry named wasmer dated 2026-02-25, and no rollback instructions. The last push to the repository was on 2026-07-11, so the code is recent, but recency of a push is not the same as a documented release process.
The UUID default is the sharpest limitation. A hardcoded default credential shipped in a public repository means any deployment that forgets to override it shares an identifier with every other deployment that forgot. The README flags this only in the context of Nezha v1. There is no guidance on rotating the UUID after deployment, and the README does not document whether changing it invalidates existing client configurations.
eooce/python-ws against a compiled proxy core
The obvious alternative is Xray or sing-box, the compiled cores that most vless, trojan and shadowsocks deployments use. The difference is not features, it is deployment shape. A compiled core gives you a mature protocol implementation, a configuration file format, and a large body of documentation and tooling. It also gives you a binary you must obtain, verify, and keep updated, and on some PaaS platforms you cannot run it at all without a custom build step.
eooce/python-ws trades that maturity for a single Python file and two pip dependencies. You lose the configuration file, the documented internals, and any expectation of protocol-level hardening. You gain a container that builds from a stock python:3.12-alpine base with no external binary download, which is exactly the constraint the project seems designed around. The README's own framing, a toy and a container for Python environments, sets the expectation correctly.
The sibling ports are the other comparison. node-ws, java-ws and the Golang branch implement the same three protocols in different runtimes. If your platform runs Node.js more comfortably than Python, or if you want the Go version for its concurrency model, the choice is about the runtime you can deploy, not about protocol coverage.
Licence and the cost of staying current
The repository is GPL-3.0. That is a copyleft licence, and it applies to the whole work. If you modify app.py and distribute the result, or run a modified version as a network service in a way your jurisdiction treats as distribution, the GPL-3.0 obligations attach. Running the unmodified code on your own container is the straightforward case. This is a description of the licence, not legal advice; if you plan to redistribute a modified version, read the licence text in the repository or talk to someone qualified.
The upgrade cost is low in one sense and unquantified in another. There is no version pinning to manage because there are no pinned versions, and the Dockerfile rebuilds from the current Python 3.12 Alpine image each time. That also means you cannot reproduce a previous build exactly. The project has one release entry, wasmer, dated 2026-02-25, and no documented compatibility matrix. Keeping current means rebuilding and watching the subscription page load, because the README describes no test suite or health endpoint to check instead.
Editorial conclusion
Adopt eooce/python-ws if you already run a Python container on a PaaS such as Koyeb and want all three protocols from one app.py with no compiled core to deploy. Do not adopt it if you need a documented configuration reference, an upgrade path, or a deployment where a leaked UUID is acceptable; the README does not cover rotation or rollback. Before deploying, verify that the platform injects a PORT value, that DOMAIN is set to the assigned or reverse-proxied hostname without the https:// prefix, and that the subscription page loads at /sub.
Frequently asked questions
What is eooce/python-ws and which protocols does it support?
It is a Python server that implements vless, trojan and shadowsocks proxies without a compiled core. The README describes it as lightweight and intended for Python serverless environments and containers.
How do I install eooce/python-ws?
There is no package to install. You build the provided Dockerfile, which uses python:3.12-alpine and runs python3 app.py, or you install the two dependencies from requirements.txt with pip and run app.py directly.
Which environment variables does eooce/python-ws require?
DOMAIN is the only variable the README marks as required, and it must be the assigned or reverse-proxied domain without the https:// prefix. UUID, PORT, SUB_PATH, AUTO_ACCESS, DEBUG and the three Nezha variables are optional with documented defaults.
Where do I find the subscription URL in eooce/python-ws?
The README says node information is at domain/${SUB_PATH}, or domain:port/${SUB_PATH} on a non-standard port. SUB_PATH defaults to sub, so the default URL path is /sub.
Official sources
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.
[](https://hysenlabs.com/projects/eooce-python-ws)