Hysteria2 link format: every hy2:// parameter
A Hysteria2 link is hysteria2://auth@host:port/?key=value#name, and hy2:// means the same. The official scheme has six query parameters: obfs, obfs-password, sni, insecure, pinSHA256 and ech. The port can be a list or range for port hopping. Most rejected links come down to an unencoded character, a port format the app does not read, or a certificate setting that does not match the server.
Tahir Mehmood9 min read
This is a field-by-field reference for Hysteria2 share links. For how the protocol works (QUIC, the /auth exchange, Brutal, Salamander and the server config), read Hysteria2 protocol explained first. This post only covers the link.
I checked everything against three sources: the URI scheme page in the official docs repository (HyNetworks/hysteria-website, last changed 22 August 2026), the client code of Hysteria 2.13.0 (5 October 2026), and the link parser in v2rayN 7.25.6, a widely used desktop client that imports these links. The old apernet/hysteria address now redirects to HyNetworks/hysteria.
What does a hysteria2:// link look like?
It is a URL with the password in the user part, the server in the host part, and the TLS and obfuscation settings as query parameters:
hysteria2://[auth@]hostname[:port]/?[key=value]&[key=value]...[#name]
Here is a link that uses every standard parameter except ech (split over lines to read; a real link is one line):
hysteria2://s3cretPass@vpn.example.com:443/
?insecure=1
&obfs=salamander
&obfs-password=Ob5Passw0rd
&pinSHA256=0f8a3c...e91b
&sni=vpn.example.com
#fra-01
And the shortest valid one. With no port it means 443, and with no sni the client uses the host name:
hy2://s3cretPass@vpn.example.com
The scheme can be hysteria2 or hy2; they are the same. The official client's hysteria share command always writes hysteria2://, puts / before the ?, and sorts the parameters alphabetically, so a link it generated looks like the first example.
There is also a separate hysteria2+realm:// scheme for servers that use Realms. Its user part is the rendezvous server's token, not the password, and the password moves to an auth parameter. Do not mix the two up.
What does each parameter do?
Each part of the link maps to one field of the client config. Everything else about the client stays local.
| Part | Required | Default | Format | Client config field |
|---|---|---|---|---|
| Scheme | Yes | hysteria2 or hy2 |
||
auth (before @) |
No in the spec, yes in practice | Empty | Percent-encoded; username:password for userpass auth |
auth |
| Host | Yes | Domain, IPv4, or IPv6 in brackets: [2001:db8::1] |
server |
|
| Port | No | 443 |
One port, or a multi-port list for port hopping | server |
obfs |
No | None | salamander or gecko |
obfs.type |
obfs-password |
With obfs |
Must match the server | obfs.salamander.password or obfs.gecko.password |
|
sni |
No | The host | Server name sent in TLS | tls.sni |
insecure |
No | 0 |
1 or 0 |
tls.insecure |
pinSHA256 |
No | None | SHA-256 of the server's leaf certificate, in hex | tls.pinSHA256 |
ech |
No | None | ECH config list, base64; must match the server | tls.ech |
#name |
No | Display name only; carries no settings |
The spec is just as clear about what a link must not carry: bandwidth values and client modes (SOCKS5, HTTP, TUN and so on). Those depend on each user's network. The hop interval is not in the link either.
Apps may add their own parameters, and the spec allows it, but it also says no other app has to understand them. Two you will meet:
- v2rayN reads
mportfor port-hopping ports, andminPacketSizeandmaxPacketSizefor Gecko. Its own code notes that the packet size names are not in the official scheme and may change. - TunnelHQ's checker also accepts
peeras another name forsni, andobfspwdforobfs-password.
How are obfs, SNI and insecure written?
Write obfs with its type, obfs-password with the same password as the server, sni with the name on the certificate, and insecure=1 only together with pinSHA256.
Salamander:
hysteria2://s3cretPass@vpn.example.com:443/?obfs=salamander&obfs-password=Ob5Passw0rd&sni=vpn.example.com
Gecko uses the same two parameters with obfs=gecko. An app that does not know Gecko yet will refuse the link, so check your users' clients before you switch.
Self-signed certificate with a pin. With insecure=1 alone, the client accepts any certificate, so anyone between the user and the server could pose as it. With pinSHA256 as well, the client still skips the normal check but accepts only the certificate with that exact hash:
hysteria2://s3cretPass@203.0.113.10:443/?insecure=1&pinSHA256=0f8a3c...e91b&sni=vpn.example.com
To get the hash, run this on the server against the certificate file:
openssl x509 -noout -fingerprint -sha256 -in /etc/hysteria/server.crt
It prints the hash with colons (sha256 Fingerprint=0F:8A:3C:...). You can paste it like that: Hysteria 2.13.0 lowercases the value and strips : and - before comparing. The pin is checked against the leaf certificate only, the first one the server sends, not a CA in the chain. So when the certificate is renewed, the pin changes and every old link fails.
v2rayN 7.25.6 treats any link that has a pinSHA256 as insecure, even without insecure=1, because some providers issue links that way. Its code comment says this breaks compatibility between clients, and it does: the official client verifies the certificate normally unless insecure=1 is set, so the same link can work in v2rayN and fail in the official client.
Values with special characters. The password and every value must be percent-encoded. In the query part, a + means a space, so an obfs-password that contains + has to be written %2B. The same goes for & (%26), # (%23) and = (%3D). In the password before the @, encode @, /, ? and #.
insecure takes 1 or 0. The official client also accepts true and false, because it parses the value as a Go boolean, but v2rayN only treats 1 as true. Write 1.
How are port-hopping ranges written in a link?
In the port position, after the host, using the same multi-port format as the client config: single ports, ranges, or both, separated by commas.
hysteria2://s3cretPass@vpn.example.com:20000-50000/?sni=vpn.example.com
hysteria2://s3cretPass@vpn.example.com:443,5000-6000,7044/?sni=vpn.example.com
The client picks a random port from the list for the first connection and moves to another one every 30 seconds by default. The interval is client config (transport.udp.hopInterval, at least 5 seconds), not part of the link. The server has to accept every port in the range, which the protocol explainer covers.
This format is the one place where apps differ the most:
- The official client reads it. Go's standard URL parser rejects a port that is not a number, so Hysteria ships a modified copy of it just for this.
- v2rayN 7.25.6 reads hopping ports from the separate
mportparameter instead, and writes them that way when it exports a link. The official client ignoresmportand connects to the single port in the host part, without hopping. - Generic URL libraries in most languages fail on
host:20000-50000, so a script or bot that parses links that way will choke on it.
If a link has to work in several apps, the safest form is one port in the host part. If the server accepts the whole range, any single port from it works.
Why does a client reject my link?
Usually for one of six reasons: an unencoded character, a port format the app does not read, an obfuscation type it does not know, the wrong scheme, a certificate check that fails, or a password that does not match.
| What you see | Likely cause | Fix |
|---|---|---|
| The app will not import the link at all | A raw #, @, & or space in the password, or a port range the app's parser cannot read |
Percent-encode the password; use one port, or the app's own hopping field |
invalid config: server: ... (official client) |
The address could not be read; often a # in the password cut the link short, so the "host" is part of the password |
Encode # as %23 |
invalid config: obfs.type: unsupported obfuscation type |
obfs is not salamander or gecko, often a typo or an old value |
Copy the type from the server config |
The link starts with hysteria:// |
That is the Hysteria 1 format, which 2.x clients do not read; the two versions are not compatible | Get a hysteria2:// link for a 2.x server |
no certificate matches the pinned hash |
The server's certificate changed, or the pin is for a CA, not the leaf | Recompute pinSHA256 from the current certificate |
| A certificate error with no pin involved | Self-signed certificate without insecure=1, or sni does not match the certificate |
Fix sni, or add a pin with insecure=1 |
authentication error, HTTP status code: 404 |
Wrong or badly encoded password, or the server uses userpass and the link has only one part |
Check the encoding; use username:password |
| A timeout, with no error at all | Wrong obfs-password, or obfuscation set on only one side |
Make the link match the server's obfs block |
The last row trips up the most people. A wrong obfuscation password does not cause an error, because the server cannot read the packets and drops them. To the client that looks exactly like a server that is down.
How do I test a hysteria2 link works?
Load the link into a real client and send a request through it. Importing a link only proves it parses. Only a connection proves the server accepts it.
The official client can take a link as its whole server setting. Put the link in the server field and add a local proxy:
server: hysteria2://s3cretPass@vpn.example.com:443/?sni=vpn.example.com
socks5:
listen: 127.0.0.1:1080
Run hysteria -c client.yaml, wait for connected to server, and then in a second terminal:
curl --socks5-hostname 127.0.0.1:1080 -sS -o /dev/null -w '%{http_code}\n' https://www.gstatic.com/generate_204
204 means the whole path works: the QUIC handshake, the password, obfuscation and the server's outbound connection. To get a clean link back from a working config, run hysteria share -c client.yaml, and add --qr to print it as a QR code for phones.
For a quick check without installing anything, paste the link into the free TunnelHQ tester, five tests a day with no account. It reads hy2:// and hysteria2:// links with sni (or peer), insecure, obfs and obfs-password, then runs the official Hysteria client against the server. Two limits are worth knowing:
- It needs one explicit port in the link. A link with no port or with a port range does not parse, and the result reads Hysteria2 handshake failed with
Config error: failed to parse Hysteria2 URIunder "What the checker saw". To test a port-hopping server, write one port from its range. - It does not read
pinSHA256orech. For a server with a self-signed certificate, the link needsinsecure=1to pass, the same way the official client does without a pin.
When the link passes and you want to know the moment it stops working, Hysteria2 monitoring runs the same check on a schedule and alerts you when it fails.


