v2ray-plugin: its own warning says skip the HTTP mode, and the nightly link pins build 20
A SIP003 plugin based on v2ray
At a glance
- What is it?
- A single-binary shadowsocks plugin that hands transport to v2ray-core, configured entirely through one semicolon-separated option string. The README is short enough to read in a minute and contains a warning against its own first mode, a nightly download link frozen at build number twenty, and three separate ways to supply a certificate.
- Who is it for?
- v2ray-plugin suits someone running a shadowsocks client and server pair who wants the traffic carried over websockets or QUIC with TLS terminated by a hostname they control, and who is comfortable passing options as a single string on the command line. Before configuring it, check five things.
- Can I use it commercially?
- Yes. MIT 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 4 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.
Star history
- shadowsocks/v2ray-plugin2,845 ★ · -3
| Repository | 2026-10-04 | 2026-11-13 |
|---|---|---|
| shadowsocks/v2ray-plugin | 2.8k | 2.8k |
-3 Stars in the last 40 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The warning on the first mode advises against using that mode
The README documents three transports, and the first one carries a warning before any command appears. Shadowsocks over websocket on plain HTTP is described as providing only moderate, though lightweight, traffic obfuscation, and cautious users are told to refrain from using it.
That is the project telling its own readers not to pick the first option in the list, and it does so without a replacement recommendation in that spot. The two documented alternatives are websocket with TLS and QUIC, both on port 443 in the examples.
The commands themselves are asymmetric in a way worth noticing. The server invocation for the plain HTTP case passes a plugin option string containing a single token, while the matching client invocation passes no plugin options at all. In the TLS and QUIC cases both sides carry option strings, with the same values on each side apart from the token that marks which end is which.
So the pattern across all three modes is that the server is told it is the server, and the client's options carry only the transport and host details. A reader moving from the HTTPS example to the QUIC one changes a mode token and keeps the rest.
The README offers no guidance on choosing between websocket and QUIC, and no performance or reliability comparison. The only comparative statement in the file is the obfuscation warning attached to HTTP.
The nightly download link is pinned to build number twenty
The build section has two parts. The first is a single command, `go build`, with no flags, no output name and no reference to the module file. The second offers a nightly build from the CI service instead, and describes two ways to get it: either sign in to the CI service, or append a fragment to the project's CI URL.
The example URL it gives ends in a path segment of 20 followed by the artifacts fragment. That number is a build identifier, not a symbolic name. So the URL the README presents as the way to get the latest nightly resolves to one specific build that existed when the line was written, and it will keep resolving to that build rather than tracking anything newer.
Both access routes also have a cost the release route does not. The sign-in route requires an account with the CI service. The fragment route is presented as a way to avoid that, but it still points at an artifact from the CI system rather than at a published release, and it is pinned to a build number rather than to a tag.
So there are three sources of a binary here, with different properties: the source tree, which builds whatever the current commits are; a tagged release, which is the most recent one at v1.3.2 from September 2022; and a CI artifact whose URL in the documentation is fixed to one build. None of them is described as preferable to the others.
Configuration is one semicolon-separated string, and one token marks the server
Everything is configured through a single plugin option string appended to the shadowsocks command, and the README passes it to both ends of the connection. The HTTPS example looks like this:
ss-server -c config.json -p 443 --plugin v2ray-plugin --plugin-opts "server;tls;host=mydomain.me"Reading the three example pairs together gives the grammar. The token `server` appears only in the server invocation. The token `tls` appears on both sides when TLS is in use. A mode token selects the transport, with a value of `quic` for the QUIC case and nothing for the websocket case. And a host token carries the hostname, given as a key and value pair.
That is four distinct shapes in one string: a bare flag, a key and value pair, a mode assignment, and the positional marker. Separators are semicolons rather than spaces, which is what allows the whole string to survive being quoted inside a shadowsocks command line.
The port follows the transport rather than being fixed. Plain HTTP is shown on 80, and both TLS and QUIC on 443. The QUIC example does not include the TLS token even though it also runs on 443, so the two settings are independent in the option string even though the example pairs them.
For anything beyond these four tokens, the README defers entirely to the program's command line arguments rather than to a document, so the full option set lives in the help output and in the source.
Three ways to supply a certificate, one of them a piped install script
TLS needs a certificate, and the plugin looks for one by default rather than asking for a path. The default is a certificate issued by the acme.sh client, and the README supplies two sample commands for obtaining one through Cloudflare:
curl https://get.acme.sh | sh
~/.acme.sh/acme.sh --issue --dns dns_cf -d mydomain.meThe first of those pipes a remote script directly into a shell. The second uses a DNS challenge, and the specific challenge named is the one for Cloudflare, which means this sample assumes the domain's DNS is hosted there. The README says commands for other DNS providers are to be found at the acme.sh project.
So the default certificate path is not provider-neutral: it depends on a particular DNS host, and the sample that demonstrates it only works for domains under that host. Anyone else has to leave the sample and read the upstream documentation.
There are then two explicit alternatives. A pair of options can name the certificate and key files directly, which is the ordinary path for a certificate obtained by any other means. And a third option takes the certificate itself as inline content in PEM format, specifically the text between the begin and end markers with the line breaks removed.
That last option is the unusual one. Passing a certificate as a single line inside a quoted option string is convenient when the certificate has to travel in configuration rather than on disk, and the requirement to strip line breaks is what makes it a string value rather than a file reference.
Both protobuf modules are required, including the superseded one
The module file lists three direct dependencies. Two are protobuf implementations and one is the core library the plugin is built around.
The core library is pinned to a specific minor version of the fifth major series of v2ray-core, which is where the protocol work actually lives. The plugin is a thin adapter around it, which is consistent with the repository being a handful of Go files at its root with no internal package tree.
The two protobuf entries are the interesting part. The module file requires both the older protobuf module for Go and the current one, at two different version lines. Having both present is the signature of a migration that has been started and not finished: the older module is the one that was superseded, and a project that had completed the move would depend only on the current one.
The consequence is not cosmetic. Both modules define overlapping generated code types, and code that mixes them needs conversion at the boundary. A dependency graph containing both is the state in which that conversion still exists.
The rest of the module file is indirect and describes where the transports come from. Websockets come from one library, QUIC from another, PROXY protocol handling from a third, and DNS resolution from a fourth. None of these are written in this repository.
The language floor and the pinned toolchain are different versions
The module file declares a language version and a toolchain directive, and they are not the same number. The language floor is 1.22.0 and the toolchain directive names 1.23.2.
These do different jobs. The language version sets which syntax and language features the module claims to need, so a compiler older than that should refuse the build outright. The toolchain directive is a request: when a newer toolchain is available it will be selected, and when the installed toolchain is older than the directive the usual behaviour is to fetch the named version.
So the practical requirement is the higher of the two. A contributor with Go 1.22 installed satisfies the language requirement but is below the toolchain directive, and depending on how their toolchain is configured they will either have 1.23.2 downloaded for them or will be told to install it.
That is a normal arrangement for a project that wants a specific patch release of a toolchain, and it is worth stating plainly because the two numbers in the file look like a contradiction and are not.
The rest of the module file pins every dependency to an exact version, including the indirect ones, and several of those pins are to pseudo-versions rather than releases, which is what a dependency without tagged releases looks like.
Transports come from four libraries and the layout is seven files at the root
The repository is flat. Every Go file in the project sits at the top level: the argument parsing, the entry point, the logging, a generated errors file, a utilities file, and two more that share the same names as two of those but with a platform suffix.
Those two suffixed files are the Android variants, one for logging and one for utilities, which means the same source tree builds for Android as well as for the desktop clients. There is no internal directory and no command directory, so the plugin is a single main package rather than a library with subpackages.
The generated errors file is the one artifact in the root that is produced rather than written, which suggests the error definitions come from a generator rather than from a person. A shell script for cutting releases sits beside it, and a security policy file and the CI configuration directory complete the non-source entries.
The transports are not implemented here. The indirect dependency list names a websocket library, a QUIC library, a PROXY protocol library and a DNS library, so the plugin's contribution is the option parsing, the process lifecycle, and the handoff to the core library. The plugin protocol itself is what the shadowsocks client and server invoke, which is why the configuration is passed as a single string appended to their own command lines rather than through a separate file.
That design is also why the option reference is thin. There is no configuration file format for this plugin, so the surface a user touches is the command line and the certificate files.
No release since 2022 while commits continued into 2026
Three release tags are visible. Version 1.3.2 was published in September 2022, 1.3.1 in June 2020, and 1.3.0 in February 2020.
The gaps between them are not uniform. The first two are four months apart, while the gap from 1.3.1 to 1.3.2 is more than two years, which means the recent cadence is not a continuation of the older one.
The last push to the default branch is dated 2026-07-09, three months before the present day, and the repository is not archived. So the two signals diverge again: commits are landing, and no tagged release has been cut since 2022.
For a plugin whose job is to sit between two processes that both change independently, that gap is the thing to weigh. The shadowsocks client and server it plugs into are separate projects on their own schedules, and the core library this plugin delegates to is versioned separately as well. A binary built from the current branch is therefore not pinned to any of the three combinations a tagged release would have been tested against.
The dependencies explain what would move underneath such a build. The core library is pinned exactly in the module file, so a rebuild from source is reproducible against a known dependency set, but the release binary is not, and the README offers no statement about which releases remain compatible with current client versions.
Editorial conclusion
v2ray-plugin suits someone running a shadowsocks client and server pair who wants the traffic carried over websockets or QUIC with TLS terminated by a hostname they control, and who is comfortable passing options as a single string on the command line. Before configuring it, check five things. Read the warning attached to the plain HTTP mode, which the project itself describes as only moderate obfuscation and advises cautious users against. Decide how you will obtain certificates, because the default looks for files issued by a specific client using DNS challenges, the alternative takes file paths, and the third takes inline certificate content with its line breaks removed. Prefer a tagged release over the nightly, since the documented nightly URL points at a fixed build number and requires signing in to the CI service to download. Note that the Go language floor and the pinned toolchain are different versions. And expect no option documentation beyond the program's own help output, since the README defers to it.
Frequently asked questions
How is v2ray-plugin configured?
Through a single semicolon-separated plugin option string appended to the shadowsocks command. The documented tokens are server to mark the server end, tls to enable TLS, a mode token whose value quic selects QUIC, and host to carry the hostname.
Does v2ray-plugin recommend websocket on plain HTTP?
It warns against it. The HTTP mode is described as providing only moderate, though lightweight, traffic obfuscation, and the README says cautious users should refrain from using that mode.
How does v2ray-plugin obtain a TLS certificate?
By default it looks for certificates issued by acme.sh. You can instead name certificate and key file paths, or pass the certificate inline in PEM format with its line breaks removed. The sample commands use a Cloudflare DNS challenge.
Which Go version does v2ray-plugin need?
The module file sets the language floor at 1.22.0 and a toolchain directive naming 1.23.2. In practice the toolchain directive is the higher requirement, so a build may need Go 1.23.2.
What is the newest v2ray-plugin release?
Version 1.3.2, published in September 2022, after 1.3.1 in June 2020 and 1.3.0 in February 2020. The last push to the default branch is dated 2026-07-09.
Does v2ray-plugin work on Android?
The repository carries Android variants of its logging and utilities files at the top level, so the same source tree is built for Android as well as for desktop shadowsocks clients.
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/shadowsocks-v2ray-plugin)Star history chart for your README
[](https://hysenlabs.com/projects/shadowsocks-v2ray-plugin)Was this useful?