Self-hosted service
bestnite/sub2clash avatar
bestnite/sub2clash

sub2clash turns subscription links into Clash or Clash.Meta configs over one HTTP endpoint

将节点和订阅转换为 clash(meta) 配置

632 stars97 forksGoMIT

At a glance

What is it?
sub2clash is a Go service that converts proxy subscription links and node share links into Clash or Clash.Meta configurations, grouping nodes into country strategy groups and serving the result as a URL. It is a converter and a web service, not a proxy client, and it caches subscriptions for five minutes by default.
Who is it for?
sub2clash fits someone who already has subscription links from several providers and wants one Clash or Clash.Meta config with automatic country grouping, custom rule providers and a stable URL their client can poll. It does not fit someone who wants a client, since it produces configuration only, and it does not fit anyone who cannot accept a service that fetches their subscription URLs on a schedule.
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 61 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 October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A converter service, not a proxy client

sub2clash converts subscription links into Clash and Clash.Meta configurations. That is the entire job, and stating it plainly avoids a common misreading.

The problem it solves is practical. If you have several subscription providers, each one hands you a link that returns a list of nodes in one format, and each Clash client wants a complete configuration file with rules, proxies and strategy groups. Assembling that by hand does not scale, and the strategy group layout is the tedious part.

So sub2clash takes subscription links in v2ray or clash format, plus node share links, and produces a ready configuration. The features listed are out-of-the-box rules and strategy group configuration, automatic grouping of strategy groups by country based on the node names, merging of multiple subscriptions, custom rule providers and rules, and support for a range of protocols.

The protocol list is specific and worth reading as a compatibility statement. Shadowsocks, ShadowsocksR, Vmess, Vless, Trojan, Hysteria, Hysteria2, Socks5, Anytls, and Tuic are all supported, with Vless, Hysteria, Hysteria2, Anytls and Tuic marked as requiring Clash.Meta. That marker matters: those five protocols are not understood by original Clash, so the clashType field in the API is the switch that decides which dialect the output is written in.

The primary language is Go, the licence is MIT, and the project description is in Chinese, which is worth noting for anyone looking for an English-language description of the tool.

One endpoint, Base64 URL safe JSON, and a set of query parameters

The whole interface is a single HTTP route. GET /convert/:config returns the Clash or Clash.Meta configuration link, and the path parameter config is a Base64 URL safe encoded JSON string.

Encoding the configuration into the path rather than a query body is the notable design choice. It means the output URL is self contained: you can paste it into a Clash client as a remote config subscription and the client will fetch it repeatedly. The JSON payload carries these fields:

clashType is an int and required, with 1 meaning Clash and 2 meaning Clash.Meta. subscriptions is a list of strings holding subscription links in v2ray or clash format, and you can append a #name to a link to give the nodes in that subscription a common prefix, and you can supply more than one. proxies is a list of node share links, also multiple.

Several fields are optional and control behaviour rather than content. refresh forces a regeneration of the configuration, since the default is a five minute cache. template points at an external template URL or names an internal template. ruleProviders and rules are arrays of objects for custom rules. autoTest turns on automatic speed testing for the country strategy groups, and lazy controls whether that speed testing uses lazy mode. sort chooses how the country groups are ordered, with values nameasc, namedesc, sizeasc and sizedesc, defaulting to nameasc.

The regex fields are the ones that turn a generated config into yours. replace is a map from regular expression to replacement for renaming nodes, and remove is a regular expression for deleting nodes. nodeList outputs only the nodes without the rules, ignoreCountryGroup suppresses the country grouping, userAgent sets the user agent used when fetching subscriptions, and useUDP turns on UDP.

The RuleProvider structure has a behavior field for the rule-set behavior, a url, the strategy group name the rule set applies to, a prepend flag that puts the rules at the top of the list rather than the bottom, and a name that must be unique.

Configuration comes from files, environment variables, or defaults

The service reads its own configuration from three sources, in priority order, and the environment variable naming is consistent enough to guess the rest.

First come configuration files, and the search order inside that is specific. The tool looks for config.yaml or config.yml, then config.json, then sub2clash.yaml or sub2clash.yml, and finally sub2clash.json. Example files for both formats are provided in the repository as config.example.yaml and config.example.json, which is the fastest way to see the shape of the settings.

Second come environment variables, all using the SUB2CLASH_ prefix. The mapping between a setting name and its variable is one to one: address becomes SUB2CLASH_ADDRESS, meta_template becomes SUB2CLASH_META_TEMPLATE, clash_template becomes SUB2CLASH_CLASH_TEMPLATE, request_retry_times becomes SUB2CLASH_REQUEST_RETRY_TIMES, request_max_file_size becomes SUB2CLASH_REQUEST_MAX_FILE_SIZE, cache_expire becomes SUB2CLASH_CACHE_EXPIRE, log_level becomes SUB2CLASH_LOG_LEVEL, and short_link_length becomes SUB2CLASH_SHORT_LINK_LENGTH.

Third are the built-in defaults. address defaults to 0.0.0.0:8011. The two template settings default to raw GitHub URLs pointing at template_meta.yaml and template_clash.yaml in the project's main branch, so by default the service fetches its templates from the network at runtime. request_retry_times is 3, request_max_file_size is 1048576 bytes, cache_expire is 300 seconds, log_level is info with debug, info, warn and error as the choices, and short_link_length is 6.

The request_max_file_size default of one megabyte is the one to watch. A large subscription with many nodes can exceed a megabyte, and the service will refuse it rather than process it, so if your provider returns a big file you will need to raise that value through the environment variable.

The container image mounts data and nothing else

There are two deployment routes: a docker compose file and a binary from the releases page.

The compose file is short enough to read in full, and it shows what the container needs:

yaml
name: sub2clash
services:
    sub2clash:
        restart: unless-stopped
        image: nite07/sub2clash:latest
        ports:
            - "8011:8011"
        volumes:
            - ./data:/app/data

Two things are worth pulling out of that. The image is nite07/sub2clash:latest, and the port mapping publishes 8011 on the host, matching the default listen address. The single volume mount is ./data to /app/data.

The compose file also carries two commented-out volume lines, for ./logs to /app/logs and ./templates to /app/templates. They are commented out in the shipped file, which tells you the log output and the templates directory are available to mount if you want them to persist, but the default deployment does not. The templates mount in particular is the one to consider if you intend to run your own template files rather than the default ones fetched from GitHub.

The binary route is the alternative for people who would rather not run a container, and the releases page carries the builds. The repository also has a .goreleaser.yaml, which is the configuration that produces those release artefacts.

Note that the container tag is latest, so a deployment pinned this way will pick up whatever image is current when it restarts. For a service that other machines will poll for their configuration, pinning a specific tag rather than latest avoids your clients silently receiving a different config format after an update.

Cache, retries and the request limits that shape behaviour

Three settings govern how the service talks to your subscription providers, and they interact.

cache_expire, default 300 seconds, is the subscription cache time. The API exposes this directly: the refresh parameter forces a refresh of the configuration, with the default being a five minute cache. That means a client polling the URL more often than every five minutes gets a cached copy unless refresh is set, which protects both the service and the provider from being hammered.

request_retry_times, default 3, is the number of request retries. That is a sensible default for a service whose whole function is fetching remote subscriptions that occasionally time out.

request_max_file_size, default 1048576 bytes, caps the size of a fetched subscription file. Anything larger is rejected. This is the setting most likely to need changing, because node counts have grown and provider payloads with full metadata can be large.

There is also log_level, defaulting to info, with debug, info, warn and error available, and short_link_length at 6, which controls the length of the generated short links.

Taken together, these are the knobs that decide whether a provider's subscription is accepted at all, whether it is fetched again soon, and how much of the service's internal behaviour you can see when something goes wrong. If a subscription silently fails to update, the first thing to change is log_level to debug, and the second is request_max_file_size.

The Go module shows the implementation choices behind these behaviours. The HTTP framework is gin, configuration is handled by viper, logging uses zap with lumberjack for rotation, and the HTTP client is resty. Persistence goes through gorm with a pure Go sqlite driver, and the dependency list also includes the mihomo project, which is the Clash.Meta core, along with yaml.v3 for parsing and golang.org/x/text for text handling. The module requires Go 1.25.

A Go service laid out by concern, with a web frontend of its own

The repository layout shows a service with clear internal boundaries, which matters because the top level of a project like this usually turns into a junk drawer.

There are separate directories for common code, the config package, constants, the logger, the model layer, the parsers, the server, the templates, tests and utilities, alongside main.go. The parser directory is the heart of it, since parsing is the job: turning v2ray subscription content, Clash subscription content and individual node share links into a normalised internal model is where the real work sits.

The templates directory holds the default Clash and Clash.Meta templates, and the config directory holds the built-in default configuration.

There is also a frontend. The top level contains build-frontend.sh, which builds the web assets that the preview at the project's own domain serves. That preview is described as a preview of the tool, so it doubles as a way to try the conversion without deploying anything yourself.

The model and server split follows the usual layering: model for the domain types, server for the HTTP handlers, utils for shared helpers, and logger for the logging setup that zap and lumberjack back. The test directory at the top level means the parsing and conversion logic is covered by the project's own tests rather than only exercised in production.

The mihomo dependency in go.mod is the piece that explains a lot. mihomo is the Clash.Meta core, so rather than implementing protocol parsing for Vmess, Vless, Trojan, Hysteria, Hysteria2, Tuic, Anytls and ShadowsocksR from scratch, the project leans on that library for the protocol layer and focuses on subscription handling, node grouping, rule assembly and configuration generation. That is a sensible division of labour, and it also explains the long indirect dependency list, since mihomo pulls in a large transitive tree.

Where sub2clash is the wrong tool, and what it does not do

Three limits are worth stating before you deploy it.

First, it does not proxy your traffic. It produces configuration files. If you were expecting a client that connects to nodes and handles your traffic, you will need Clash or a Clash.Meta build as well. The relationship is generator to consumer, and the consumer is a separate program with its own update cycle.

Second, it is a web service with a default listen address of 0.0.0.0:8011, which binds every interface. The configuration in the URL includes your subscription links, encoded as Base64 rather than encrypted, so anyone who can reach the endpoint can read your subscription URLs. There is no mention in the documented settings of an authentication mechanism, an API key, or a TLS setting. That means it belongs behind a reverse proxy or on a trusted network, and if you expose it to the internet you need to add your own access control.

Third, the templates are fetched from GitHub by default, and the cache is in memory with a five minute expiry, so the service depends on outbound network access to both your providers and to GitHub. A firewall that blocks either will surface as a conversion failure rather than as an obvious error.

On maintenance, the project is steady. The last push was on 2026-08-24 and v1.1.4 was tagged the same day, with v1.1.3 on 2026-04-26 and v1.1.1 on 2025-12-08 before it. The version is above 1.0, the licence is MIT, and the release cadence is a couple of months.

The comparison with the alternative approach is straightforward. A subscription provider can hand out a Clash config directly, and a desktop client can often import several of them and merge them itself. The difference is that those clients merge proxies and leave you to build the strategy groups and rules, whereas sub2clash does the grouping, the naming and the rule assembly for you and serves one URL. If you are happy to hand-edit a config, the client route has fewer moving parts. If you have a dozen subscriptions and want country groups that update themselves, this is the tool built for that.

Editorial conclusion

sub2clash fits someone who already has subscription links from several providers and wants one Clash or Clash.Meta config with automatic country grouping, custom rule providers and a stable URL their client can poll. It does not fit someone who wants a client, since it produces configuration only, and it does not fit anyone who cannot accept a service that fetches their subscription URLs on a schedule. Before deploying it, set SUB2CLASH_ADDRESS to a loopback address if the service is not behind a reverse proxy, because the default of 0.0.0.0:8011 exposes it on every interface, and check whether SUB2CLASH_CACHE_EXPIRE of 300 seconds matches how often your provider expects you to pull.

Frequently asked questions

What does sub2clash do and what does it not do?

It converts subscription links in v2ray or clash format, and node share links, into Clash or Clash.Meta configuration files served over HTTP. It does not proxy traffic itself, so you still need a Clash or Clash.Meta client to use the configuration it produces.

How do I deploy sub2clash?

Use the docker compose file in the repository, which runs the nite07/sub2clash:latest image with port 8011 published and ./data mounted at /app/data, or download and run a binary from the releases page.

Which protocols does sub2clash support?

Shadowsocks, ShadowsocksR, Vmess, Vless, Trojan, Hysteria, Hysteria2, Socks5, Anytls and Tuic. Vless, Hysteria, Hysteria2, Anytls and Tuic require Clash.Meta rather than original Clash.

How do I change the sub2clash configuration?

Three sources are used in priority order: configuration files searched as config.yaml, config.yml, config.json, sub2clash.yaml, sub2clash.yml then sub2clash.json, then environment variables prefixed with SUB2CLASH_, then the built-in defaults. Example files are provided as config.example.yaml and config.example.json.

What is the default cache duration and how do I force a refresh?

The cache_expire setting defaults to 300 seconds, and the refresh parameter in the request JSON forces the configuration to be regenerated instead of served from cache. The same value is available as the environment variable SUB2CLASH_CACHE_EXPIRE.

Official sources

  1. bestnite/sub2clash on GitHub
  2. Issues
  3. License: MIT
  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/bestnite-sub2clash.svg)](https://hysenlabs.com/projects/bestnite-sub2clash)

Was this useful?