Start with the response: a subscription URL doesn’t define the format
If a Clash subscription fails to import, check the response before switching clients. The same HTTPS URL may return a ready-to-use YAML config, Base64 text, or even a login page. The URL suffix is not reliable: an endpoint without a .yaml suffix can return YAML, while one with that suffix may return an error if authentication has expired.
It helps to think of subscriptions in three layers: the subscription URL is the access point, Base64 is a text encoding, and YAML or URIs describe the actual data. YAML also comes in two forms: a complete config or data containing only proxy nodes. Importing a list of nodes as a complete config is a common source of errors.
| What you see | What it usually means | What to do next |
|---|---|---|
proxies:、proxy-groups:、rules: |
Possibly a complete Clash config | Check the proxies, group references, and kernel compatibility |
Only proxies: and proxy entries |
A proxy list or provider data | Load it as a proxy provider or merge it into a complete config |
Multiple ss://, trojan://, or similar links |
A list of proxy URIs | Use an interface that supports proxy imports, or convert the format |
| A string of letters, numbers, and a few symbols | Possibly Base64-encoded content | Decode it locally, then identify the resulting format |
<html>, a login prompt, or a JSON error |
A web page or API error response | Check the subscription’s expiration, authentication, and access restrictions |
YAML configs: proxies, groups, and rules each have a role
YAML is a data serialization format, not something specific to Clash. Being parseable as YAML only means the syntax is valid; clients also check fields, proxy protocols, and references. For mihomo-based clients, actual support depends on the running kernel—not just whether the app’s name includes Clash.
Anatomy of a minimal example
This example shows how a local listener, a Shadowsocks proxy, a selector group, and rules fit together. node.example.invalid is a reserved example domain, and the password is for demonstration only; neither can be used to connect. Port 7890 is explicitly set here as the local mixed proxy port—it isn’t the default for every client.
mixed-port: 7890
allow-lan: false
mode: rule
log-level: info
proxies:
- name: "Demo SS"
type: ss
server: node.example.invalid
port: 443
cipher: aes-128-gcm
password: "demo-pass"
proxy-groups:
- name: "Manual Select"
type: select
proxies:
- "Demo SS"
- DIRECT
rules:
- IP-CIDR,127.0.0.0/8,DIRECT,no-resolve
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
- MATCH,Manual Select
proxies: Stores server addresses, protocols, ports, and authentication details.proxy-groups: Organizes proxies and other policies for user selection or automatic routing.rules: Matches traffic from top to bottom; the finalMATCHin this example catches all remaining connections.mixed-port: Provides an HTTP and SOCKS proxy listener. Client overrides may change this value, so check the active config.
This example explains the structure but leaves out DNS, TUN, and LAN access policies. It isn’t a general-purpose network optimization recipe, nor does it tell you how DNS requests are actually routed. Before importing a real config, preserve any transport and authentication fields required by your provider.
Three common YAML pitfalls
- Indentation: Use spaces, not tabs. In the example, list items are indented by 2 spaces and proxy properties by 4.
- Strings: Quote passwords and proxy names containing special characters. An unquoted
#can start a comment in certain positions, truncating the value. - References: Proxy and group names must match exactly. If you change
Demo SStoDemo-SS, update the reference inside the group too.
Base64 proxy lists: decode before importing
What people call a “Base64 subscription” is usually a set of URI links separated by newlines and then encoded as one Base64 string. Decoding it gives you a list of proxy nodes—not rules, groups, or DNS settings. Some services encode other content, so always identify the format from the decoded result.
Standard Base64 commonly uses + and /, and may end with =. The URL-safe variant uses - and _. Some endpoints omit trailing padding. Don’t assume a string isn’t Base64 just because it has no equals sign, and don’t try to decode the full HTTPS subscription URL.
Inspect the decoded content locally
If Python 3 is installed, save the encoded response body as subscription.txt, then run the following command from that file’s directory. The script reads the local file and writes the result to decoded.txt; nothing is uploaded. The output also contains sensitive credentials, so delete it securely when you’re done.
python3 - <<'PY'
from pathlib import Path
import base64
import binascii
raw = Path("subscription.txt").read_text(encoding="utf-8-sig")
encoded = "".join(raw.split())
encoded += "=" * (-len(encoded) % 4)
try:
decoded = base64.b64decode(
encoded, altchars=b"-_", validate=True
).decode("utf-8")
except (binascii.Error, UnicodeDecodeError, ValueError) as error:
raise SystemExit("Decoding failed: check that the input is Base64 text") from error
Path("decoded.txt").write_text(decoded, encoding="utf-8")
print("Wrote decoded.txt; inspect it locally")
PY
This command uses a Unix-style shell here-document and works in environments such as macOS and Linux. On Windows, save the Python code between the markers as a script and run it. If decoding fails, check that you didn’t save a web page, copy the input incompletely, or include quotes surrounding the file contents.
Proxy URIs: one link per proxy
A URI usually begins with a protocol name, such as ss://, vmess://, vless://, or trojan://. These protocols don’t share a single encoding scheme. Common VMess share links encode JSON in the URL, while other protocols may describe a connection using a combination of user info, host, port, and query parameters.
Shadowsocks example: only the credentials are encoded
ss://[email protected]:443#Demo%20SS
In this example, YWVzLTEyOC1nY206ZGVtby1wYXNz decodes to aes-128-gcm:demo-pass; node.example.invalid:443 is the server and port; and #Demo%20SS is the display name, percent-encoded. This corresponds to the example proxy shown earlier, but doesn’t include groups or routing rules.
- Percent encoding is not Base64:
%20represents a space. Decode it as a URI component, not as Base64. - A name isn’t a connection parameter: A trailing link fragment usually affects only the display name; transport settings in query parameters can determine whether the connection works.
- IPv6 addresses need square brackets: A URI host typically uses the structure
[IPv6 address]:port; don’t split it at every colon. - Shadowsocks share links come in different forms: Encoded credentials, legacy whole-link encoding, and plugin parameters aren’t universally supported. Converters need to identify which format they’re handling.
Don’t rely on a quick “split at the colon” trick. Passwords may contain special characters, IPv6 addresses include colons, and WebSocket paths may contain query parameters. A reliable converter parses each protocol instead of applying the same string replacements to every link.
Converting formats: what survives and what gets lost
Format conversion is about mapping fields, not just changing a file extension. A converter can preserve a field only if the source contains it and both the target format and target kernel support it. First decide whether you need a proxy list, provider data, or a complete config, then choose a conversion method.
| Conversion | What happens | Main limitation |
|---|---|---|
| Base64 → URI list | Decode the outer text | Doesn’t add rules or proxy groups |
| URI list → Base64 | Put the links on separate lines, then encode them together | Changes only the wrapper; compatibility stays the same |
| URI list → Clash YAML | Parse the proxies, then add groups and rules | The template determines routing behavior, and some protocol fields may not map cleanly |
| Complete YAML → URI list | Export proxies that can be represented as links | Usually loses rules, DNS, TUN, and proxy groups |
| Complete YAML → provider file | Extract proxies and output them in provider format | The main config still needs to define how to use these proxies |
Check these fields after conversion
- Basic connection details: Server, port, password or UUID, and protocol. Preserve the original port; don’t change every proxy to
443. - Encryption and TLS: Shadowsocks cipher, TLS settings, SNI, ALPN, and certificate verification. Don’t disable certificate verification just to make a test pass.
- Transport: WebSocket path and Host, gRPC service name, and similar settings. Proxies with the same address and password may still fail to connect if their transport settings differ.
- Protocol extensions: Reality public keys and short IDs, VLESS flow, and similar fields must be supported by both the converter and the kernel.
- Names and references: Handle duplicate proxy names explicitly. After renaming a proxy, update group references too, so nothing points to a name that no longer exists.
The original Clash and mihomo don’t support exactly the same features. A protocol unsupported by the target kernel won’t become supported just because it’s “converted to Clash format.” For newer protocols, check the running kernel version and its field documentation instead of repeatedly switching subscription templates.
Importing configs and proxy providers are different workflows
Complete YAML: import and activate it as a config
Clients usually list complete configs under “Config” or “Profiles.” Menu labels vary, so choose “Import config from URL” or “Import local config”—not “Add proxy.” After importing, activate the config and check the runtime logs. A file appearing in the list doesn’t mean it’s the active config.
Proxy nodes only: use a provider or merge them carefully
mihomo proxy providers keep the source of your proxies separate from the main config. For better cross-client compatibility, use a YAML provider file that explicitly includes proxies:; check your kernel version to confirm whether other input formats are supported. The example below references a local proxy file and uses use to add its proxies to a group.
proxy-providers:
local-nodes:
type: file
path: ./providers/nodes.yaml
proxy-groups:
- name: "Subscription Proxies"
type: select
use:
- local-nodes
rules:
- MATCH,Subscription Proxies
./providers/nodes.yaml should contain a proxy list like the proxies: section shown earlier. Where relative paths are resolved depends on the kernel’s working directory and how the client manages files—not necessarily the directory where the download was saved. Merge this snippet into the main config; don’t redeclare existing top-level keys or proxy groups with the same names.
Remote HTTP providers also need an update URL, cache path, and refresh interval. With interval: 86400, the interval is in seconds, so the provider refreshes every 24 hours. Updating a remote proxy list and updating the main config are separate processes: the former refreshes proxies, while the latter may replace the entire config, including rules and DNS settings.
Troubleshooting failed imports: check the response, syntax, kernel, and runtime
- Check the response. Download the content locally and inspect the first few lines. HTTP
200means the request returned a response, not that the body is a config; for401or403, check authentication and access restrictions first. Some services return different formats depending on the User-Agent, so a browser and client may receive different content. - Check for outer encoding. If the response is Base64, decode it once and inspect the result. Don’t decode repeatedly without checking, or mix up the subscription URL, a web page, and an encoded proxy list.
- Check YAML syntax. Errors mentioning “mapping” or “indentation” usually involve indentation, colons, or quotes. Note the reported line number and inspect the surrounding lines. Don’t paste a real config containing credentials into an online YAML validator.
- Check field support. “Unsupported proxy type” usually means the protocol isn’t supported by the kernel. First confirm which kernel the client is actually running, then check its version and supported protocols.
- Check name references. For errors such as “proxy not found,” verify that proxy, group, and provider names match. A complete config with proxies but no usable policies may also prevent the expected routing from working.
- Check runtime status. Once the config loads, confirm that the local proxy port, system proxy, or TUN mode is enabled as needed. Successful import, an active proxy listener, and a working proxy connection are three separate checks.
If proxies pass latency tests but websites won’t load after conversion, don’t blame encoding right away. A latency test covers only a specific request; real traffic also depends on DNS, routing rules, UDP support, and app proxy settings. Check which policy handled the failed connection and which proxy it selected, then pinpoint the problem.
Choosing a format: get data the target client can use natively
- Need a ready-to-use import: Prefer a complete Clash or mihomo config that the provider explicitly says works with your current kernel, then review its routing and DNS settings.
- Already have your own rules: Prefer proxy provider data to keep your main config separate and reduce the risk that subscription updates overwrite local settings.
- Moving proxies between clients: URI lists are convenient for sharing individual proxies, but check each protocol extension carefully. Don’t expect them to carry over the original policies.
- Received Base64 text: Decode it locally first to identify the contents. Base64 is just a wrapper; the internal data structure determines how to import it.
When conversion is necessary, prefer a locally run tool or a service you control. A remote conversion service can access subscription credentials or proxy details, and its output may introduce extra rules or settings. Keep the original input, converted result, and a working backup. Follow this sequence—check the structure, compare fields, then test with the config enabled—so you have a clear rollback point if something goes wrong.