Hysteria2 server setup and config
Hysteria2 server setup and config on a VPS: the official install script, TLS via ACME or pinSHA256, masquerade, Salamander obfs, port hopping and clients.
Below: an install that follows the official layout, the config options that actually matter, and the signs that a network is cutting UDP. Everything was checked against the project's docs and source code and is current as of September 2026: Hysteria 2.12.3, sing-box 1.14, 3x-ui 3.8.
What you'll need
- A ForestsNet Cloud VPS running Ubuntu 22.04/24.04, Debian 11/12 or AlmaLinux/Rocky Linux 8/9. The install script needs systemd,
bashandcurl. - SSH access as
rootor asudouser, ideally with key-based login — see how to harden SSH. - A domain with an A record pointing to the server's IP, for a Let's Encrypt certificate — see pointing a domain at your server. Without a domain it still works, with the caveats in Step 4.
- An app that supports Hysteria2 — see the list in Step 9.
Step 1. Decide whether Hysteria2 fits
How it works
Hysteria2 runs on QUIC, the transport behind HTTP/3. The client authenticates with an HTTP/3 request that carries the password in a header. If the password is wrong, or the visitor is a scanner, the server behaves like the website configured under masquerade. The client's TCP connections travel in QUIC streams, UDP in QUIC datagrams.
If the client doesn't set a speed (bandwidth), plain BBR is used. If it does, Brutal takes over: it holds the given rate, doesn't back off on packet loss and resends to make up for it. That's why Hysteria2 outruns TCP-based protocols on lossy, high-latency links — mobile data, congested international routes, weak Wi-Fi. The flip side: without UDP it doesn't work at all, and userspace QUIC costs more CPU than kernel TCP.
How DPI censors treat UDP and QUIC
- SNI filtering inside QUIC. Anyone on the path can decrypt the first QUIC packet, so censors read the domain name from it. China's Great Firewall has blocked QUIC by domain since April 2024 (Zohaib et al., USENIX Security 2025). Russia's TSPU does the same, and not only on port 443: after a match it keeps blocking the same address-and-port pair for 420 seconds (Paderborn University, FOCI 2026, measured in March–April 2026). Your own domain, if it's not on a blocklist, is unaffected.
- Blanket QUIC blocking has happened. From March to June 2022, Russia dropped all QUIC to foreign addresses on port 443, recognising it by the first packet. Salamander-obfuscated packets (Step 6) carry no QUIC markers.
- Per-port UDP throttling. Chinese ISPs are known to block or throttle long-lived UDP flows on a single port — this is why Hysteria supports port hopping (Step 7).
- Iran is harsher on UDP. After the June 2025 shutdown, users reported QUIC disabled on all ISPs and "unidentified UDP", Hysteria2 with obfs included, blocked on most of them depending on the destination IP. During major blackouts UDP and QUIC are cut entirely.
- Mobile networks are a lottery. In October 2025 a Russian carrier was documented where Hysteria2 wouldn't connect and QUIC failed even to domestic services, while VLESS Reality worked. Since autumn 2025 mobile internet in parts of Russia, and since March 2026 in Moscow, is periodically switched to whitelist-only mode: foreign addresses are unreachable and no protocol helps.
- Brutal's traffic profile. Some argue that a flat, loss-insensitive send rate is atypical of browser HTTP/3 and could give the protocol away. There are no measurements showing censors use this.
Bottom line: Hysteria2 is the fast lane where UDP gets through, but keep a TCP-based protocol next to it.
Hysteria2, VLESS Reality or AmneziaWG
- Hysteria2 — for lossy or high-latency links where UDP passes. Best as a second profile alongside a TCP-based one.
- VLESS Reality — the main option where UDP is cut or unstable: TCP 443 with TLS 1.3 borrowed from your own site. See 3x-ui install and VLESS Reality setup.
- AmneziaWG — a device-wide VPN with simple clients: WireGuard with its signatures disguised (Russia's TSPU has recognised plain WireGuard since 2023). But it's UDP too. See Amnezia VPN on your own server.
A broader comparison: your own VPN on a VPS — which protocol to choose.
Step 2. Install the server
The official script installs the latest release and the systemd units. The code served at get.hy2.sh is identical to scripts/install_server.sh in the project repository — read it before running. If you're not root, the script re-runs itself via sudo.
bash <(curl -fsSL https://get.hy2.sh/)
hysteria versionWhat you get:
- the binary
/usr/local/bin/hysteriaand a sample config/etc/hysteria/config.yaml, which later runs don't overwrite; - a system user
hysteriawith the home directory/var/lib/hysteria, where ACME certificates are stored; - the unit
hysteria-server.serviceplus the template[email protected]for/etc/hysteria/<name>.yamlfiles; - the service runs as
hysteriawithCAP_NET_BIND_SERVICE(port 443 without root),CAP_NET_ADMINandCAP_NET_RAW(port-hopping rules) andNoNewPrivileges=true.
--version v2.12.3 pins a version; ARCHITECTURE=amd64-avx in front of the command installs the AVX2 build. Without the script: the binary from download.hysteria.network/app/latest/ or the Docker image tobyxdd/hysteria.
Step 3. Open the ports
sudo ufw allow 443/udp
sudo ufw allow 80/tcpUDP 443 is Hysteria2 itself; TCP 80 is only for issuing and renewing the certificate in option A. If ufw isn't enabled yet, allow SSH first — see the ufw firewall: managing ports. On AlmaLinux and Rocky Linux: sudo firewall-cmd --permanent --add-port=443/udp --add-port=80/tcp && sudo firewall-cmd --reload.
Step 4. Set up TLS
TLS is mandatory in Hysteria2. There are two routes.
Option A. A domain and ACME
Hysteria obtains and renews the Let's Encrypt certificate itself. The block for the config in Step 5:
acme:
domains:
- hy.example.com
email: [email protected]
type: httptype: http validates over TCP 80, which must be open and free. Without type, TLS-ALPN on TCP 443 is allowed as well; setting it explicitly helps when TCP 443 is taken, say by Xray running VLESS Reality — which doesn't clash with UDP 443. If nginx holds port 80, use type: dns (Cloudflare, DuckDNS, GoDaddy, Namecheap and other providers).
If certbot already issues the certificate: files under /etc/letsencrypt are readable only by root, and the service will fail with permission denied. Instead of HYSTERIA_USER=root, which the docs suggest, copy the files into /etc/hysteria, owned by hysteria, from a certbot deploy hook — see free SSL with Let's Encrypt. No restart is needed after renewal: Hysteria rereads the files when they change.
Option B. A self-signed certificate and pinSHA256
Without a domain, Hysteria 2.9+ can make the certificate itself. In --host and in the client's sni, use only your server's IP (here 203.0.113.10) or your own domain, never someone else's:
cd /etc/hysteria
sudo hysteria cert --host 203.0.113.10 --valid-for 87600h
sudo chown hysteria:hysteria server.crt server.keyThe command creates an ECDSA P-256 key and prints the pinSHA256 fingerprint along with ready-made config blocks. The default validity is one year; --valid-for 87600h makes it ten, so you don't have to hand clients a new fingerprint. On the server:
tls:
cert: /etc/hysteria/server.crt
key: /etc/hysteria/server.key
sniGuard: disableOn the client, insecure: true goes together with pinSHA256: the chain isn't validated, but the certificate is checked against the fingerprint. insecure without a pin lets anyone impersonate the server unnoticed.
A self-signed certificate is weaker camouflage: real websites don't use them.
Step 5. Write the config and start the server
Generate the password with openssl rand -hex 16: a hex string has no characters that would need escaping in the share link. Replace the contents of /etc/hysteria/config.yaml:
listen: :443
acme:
domains:
- hy.example.com
email: [email protected]
type: http
auth:
type: password
password: YOUR_PASSWORD
masquerade:
type: file
file:
dir: /var/www/masqFor option B, use the tls block instead of acme. listen: :443 listens on IPv4 and IPv6 at once.
Why masquerade matters
A censor can connect to the server itself and see what comes back — this is active probing. Without masquerade, Hysteria answers every request with 404 Not Found; with it, the server behaves like a website. The example uses file mode: your own simple site from /var/www/masq (it needs at least an index.html the hysteria user can read). If your own web server already runs on the machine, proxy to it works too, e.g. url: http://127.0.0.1:8080.
ForestsNet Cloud servers must not impersonate someone else's site — by proxying it or putting its domain in SNI or the certificate: such setups get addresses banned, and the bans hit the whole network. Details are in the Terms of Service.
Start the server and check the log:
sudo systemctl enable --now hysteria-server.service
sudo journalctl -u hysteria-server.service -e --no-pagerLook for server up and running. To check the masquerade, quit Chrome completely, start it with --origin-to-force-quic-on=hy.example.com:443 and open https://hy.example.com.
Step 6. Enable Salamander obfs if QUIC is blocked
Salamander turns every packet into random-looking bytes. The docs recommend it when a network blocks QUIC or HTTP/3 specifically rather than UDP as a whole:
obfs:
type: salamander
salamander:
password: SECOND_PASSWORDThe cost: without the obfs password the server stays silent, so masquerade becomes pointless, clients without obfs can't connect, and a wrong obfs password looks like a timeout. To keep existing clients working, run a second instance: /etc/hysteria/obfs.yaml on another port (open it in the firewall) plus [email protected]. Since 2.9.2 there's also the experimental gecko, which on top of Salamander splits handshake packets into randomly sized chunks; the official client and sing-box 1.14 support it.
Step 7. Set up port hopping
The client periodically switches to another port within a range. Per the docs, this helps when an ISP blocks or throttles a long-lived UDP flow on one port; if all UDP is blocked, hopping won't help.
Since 2.8.0 the server can listen on a range by itself on Linux:
listen: :20000-50000Hysteria binds the first port of the range, redirects the rest to it with nftables or iptables, and removes the rules on shutdown; the unit from the script already has the rights for this. Use 2.12.3 or later: it fixed a bug where these rules also captured outgoing UDP.
sudo ufw allow 20000:50000/udpufw sees packets after the redirect, so strictly only the first port is needed, but opening the range does no harm.
To keep the server on 443 and redirect the range to it, use the rules from the docs (replace eth0 with your interface from ip -br addr):
sudo iptables -t nat -A PREROUTING -i eth0 -p udp --dport 20000:50000 -j REDIRECT --to-ports 443
sudo ip6tables -t nat -A PREROUTING -i eth0 -p udp --dport 20000:50000 -j REDIRECT --to-ports 443These rules don't survive a reboot. Saving them with iptables-persistent only works without ufw: on Debian and Ubuntu the packages conflict, and apt will offer to remove ufw. With ufw, put the rule into the *nat section of /etc/ufw/before.rules — see iptables basics.
On the client, replace the port with the range: hy.example.com:20000-50000, or hy.example.com:443,20000-50000 for the manual setup. The interval is hopInterval: 30 seconds by default, 5 at minimum; since 2.8.0 it can be random between minHopInterval and maxHopInterval.
Step 8. Tune speed: Brutal, BBR and UDP buffers
The algorithm is negotiated on connect:
- no
bandwidthon the client — both sides use BBR; - the client sets
bandwidth— that direction uses Brutal; if the server setsbandwidthtoo, the lower value wins, so server values act as a per-client cap; ignoreClientBandwidth: trueon the server — BBR for everyone, whatever the clients say.
For a personal server, the docs suggest setting nothing on the server and putting the speed in the client, no higher than the link's real speed without the VPN: Brutal sends exactly what it's told, and inflated numbers turn into loss and retransmissions. If several people share the server, cap them:
bandwidth:
up: 300 mbps
down: 300 mbps
# or instead, BBR for everyone:
# ignoreClientBandwidth: trueMind the traffic: a ForestsNet Cloud VPS package counts outgoing traffic, Brutal's retransmissions included. If on a lossy link traffic goes out but speed doesn't improve, add disableLossCompensation: true to the server's bandwidth block (2.10+), and Brutal will stop exceeding the set rate to compensate for loss.
The docs recommend raising UDP buffers to 16 MB:
sudo tee /etc/sysctl.d/99-hysteria.conf >/dev/null <<'EOF'
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
EOF
sudo sysctl --systemThe symptom of small buffers is the quic-go message failed to sufficiently increase receive buffer size in the log. On the server it's rare: with CAP_NET_ADMIN from the unit, quic-go forces the buffer size itself; on Linux clients without that capability the setting is a must. QUIC also hits the CPU ceiling before TCP does — if the link is idle but speed is low, check the CPU, see high load on a VPS.
Step 9. Connect clients
The hysteria2:// link
hysteria2://[email protected]:443/?sni=hy.example.com#forestsnetFor obfs add obfs=salamander&obfs-password=..., for a self-signed certificate insecure=1&pinSHA256=..., for hopping put the range in place of 443. Speed is deliberately left out of the link — it's set in the client. The server can print the link and a QR code (for obfs and a pin, add obfs and tls blocks to the file):
cat > ~/hy2-client.yaml <<'EOF'
server: hy.example.com:443
auth: YOUR_PASSWORD
EOF
hysteria share -c ~/hy2-client.yaml --qrClients
- The official client
hysteriafor Windows, macOS and Linux — SOCKS5, HTTP and TUN modes. New features land here first: Chrome handshake mimicry since 2.11,geckosince 2.9.2. - sing-box and its apps for Android, iOS and macOS: port hopping (
server_ports) since 1.11,geckosince 1.14. - Hiddify (Windows, macOS, Linux, Android, iOS) and NekoBox for Android — built on sing-box.
- v2rayN (Windows, macOS, Linux) — the core for Hysteria2 is selectable in settings: Xray or sing-box. v2rayNG on Android has used Xray for it since 2.0.
- iOS: Shadowrocket, Streisand, Stash, Loon, Surge, Egern — all from the official third-party list.
Third-party implementations may lag behind in features, as the docs warn. pinSHA256 in particular: sing-box has its own fields and format (base64). After importing a link, check that the fingerprint made it into the profile — otherwise insecure=1 simply turns server verification off.
3x-ui, Remnawave and sing-box as the server
3x-ui has had a Hysteria2 inbound since 2.9.0 (April 2026; the current release is 3.8.5), and Remnawave supports it too. Both panels use Xray-core's implementation (Hysteria 2 inbound since Xray 26.3.27), not the Hysteria server; Salamander, Brutal and hopping are configured there through finalmask, quicParams and the udphop mask. A panel suits many users with traffic accounting; for a personal server the official binary is simpler. sing-box can be the server too (a hysteria2 inbound with users, obfs and masquerade), with hopping done through iptables or nftables.
Troubleshooting
timeout: no recent network activity. The server isn't running, the UDP port is closed, the domain points elsewhere, or obfs settings don't match. Checksudo systemctl status hysteria-serverandsudo ss -ulpn | grep hysteria.- Works on Wi-Fi, not on mobile data. The carrier is cutting UDP or QUIC. Try Salamander, then port hopping; if neither helps, move that client to VLESS Reality. ForestsNet Cloud IPs are checked daily against blocking by Russia's TSPU, so the carrier's network is the more likely culprit; whitelist mode can't be fixed server-side. Mimic (UDP disguised as TCP, since 2.12) only works between Linux machines and can't be combined with hopping.
authentication error, HTTP status code: 404. Wrong password or wrong server; before 2.9.3 a+in a password taken from a link turned into a space.certificate signed by unknown authorityorno certificate matches the pinned hash. A self-signed certificate withoutinsecureandpinSHA256on the client, a reissued certificate, or a pinned CA fingerprint — since 2.6.4 only the leaf certificate is checked.tls: internal error. The client's SNI doesn't match the certificate and the server aborts the handshake (sniGuard). Use your own domain, not someone else's popular one; a self-signed certificate on an IP needssniGuard: disable.- The server won't start:
invalid config: tls.cert ... permission denied. Thehysteriauser can't read the certificate files — see Step 4. - ACME fails to issue a certificate. TCP 80 is closed or busy, or the A record hasn't propagated yet. Alternative:
type: dns. - Slow. Client
bandwidthset too high, the server capping speed with its ownbandwidth, or not enough CPU or buffer space. To measure Hysteria alone:speedTest: trueon the server andhysteria speedteston the official client; with Brutal the test shows the configured speed, not the maximum. - Drops. If they coincide with port hops (every 30 seconds by default), some ports in the range are unreachable or the redirect rules are missing: check
sudo nft list tablesandsudo iptables -t nat -S, and narrow the range. Slow reconnection after a phone wakes up is fixed by a 2.12.1+ server, which sends QUIC stateless resets. - TCP through the proxy works, UDP (calls, games) doesn't. The server is older than 2.8.2 and the client newer — update the server.
- An Xray-based client connects but no traffic flows. Such a case on a Russian network is described in Xray-core issue #6717 (September 2026): sing-box worked with the same server. Try a sing-box client or core.
Security and maintenance
Updates. Re-running the script installs the new version and restarts running instances; --check only looks for an update:
bash <(curl -fsSL https://get.hy2.sh/) --check
bash <(curl -fsSL https://get.hy2.sh/)Stay on 2.9.2 or later: 2.8.2 and 2.9.2 fixed four high-severity vulnerabilities — a server crash triggered by client QUIC parameters, denial of service with sniff enabled, and a UDP ACL bypass. Update the server before clients: 2.8.2+ clients can't relay UDP through older servers.
ACL. Without rules, anyone with the password can reach services listening on the server's 127.0.0.1, as well as private networks, through the proxy. Close them, and outgoing SMTP too, so your IP isn't used for spam:
acl:
inline:
- reject(127.0.0.0/8)
- reject(::1/128)
- reject(10.0.0.0/8)
- reject(172.16.0.0/12)
- reject(192.168.0.0/16)
- reject(fc00::/7)
- reject(all, tcp/25)Users and permissions. auth.type: userpass gives everyone their own password, so access can be revoked individually; the link then carries name:password@. The script creates the config world-readable, and it holds passwords:
sudo chown root:hysteria /etc/hysteria/config.yaml
sudo chmod 640 /etc/hysteria/config.yamlEnable the stats API (trafficStats) only with a secret and never expose it publicly. General measures are in the VPS security checklist.
Sources
- Hysteria 2 docs: Installation, Server Installation Script, 3rd-party apps, Full Server Config, Full Client Config, URI Scheme, Port Hopping, Performance, Troubleshooting, Mimic
- apernet/hysteria releases, install_server.sh, security advisories
- sing-box — Hysteria2 inbound and outbound
- 3x-ui releases, Xray-core v26.3.27, Xray-core issue #6717
- FOCI 2026 — On Russia's Early Introduction of QUIC SNI Censorship
- USENIX Security 2025 — Exposing and Circumventing SNI-based QUIC Censorship of the Great Firewall of China
- net4people: Blocking of HTTP/3 (QUIC) in Russia, Iran networks after the shutdowns, Internet analysis in the shutdown in Iran
- ntc.party, in Russian: Hysteria2 on a mobile carrier, VPN protocol blocking by TSPU
- Habr, in Russian: freezing by fingerprint
- Wikipedia, in Russian: Russia's website whitelists