β‘ Fast. Simple. Long-lasting.
CΒ³ CELERITY by Click Connect β modern web panel for managing Hysteria 2 proxy servers with centralized HTTP authentication, one-click node setup, and flexible user-to-server group mapping.
Built for performance: Lightweight architecture designed for speed at any scale.
Dashboard β real-time server monitoring and statistics
Updating an existing installation? See Safe Production Updates.
1. Install Docker (if not installed):
curl -fsSL https://get.docker.com | sh2. Deploy panel (Docker Hub - recommended):
mkdir hysteria-panel && cd hysteria-panel
# Download required files
curl -O https://github.com/ClickDevTech/hysteria-panel/main/docker-compose.hub.yml
curl -O https://github.com/ClickDevTech/hysteria-panel/main/docker.env.example
# Create Greenlock SSL config (required for HTTPS)
mkdir -p greenlock.d
curl -o greenlock.d/config.json https://github.com/ClickDevTech/hysteria-panel/main/greenlock.d/config.json
cp docker.env.example .env
nano .env # Set your domain, email, and secrets
docker compose -f docker-compose.hub.yml up -dAlternative: Build from source (for development or customization)
git clone https://github.com/ClickDevTech/hysteria-panel.git
cd hysteria-panel
cp docker.env.example .env
nano .env # Set your domain, email, and secrets
docker compose up -d3. Open https://your-domain/panel
Required .env variables:
PANEL_DOMAIN=panel.example.com
ACME_EMAIL=admin@example.com
ENCRYPTION_KEY=your32characterkey # openssl rand -hex 16
SESSION_SECRET=yoursessionsecret # openssl rand -hex 32
MONGO_PASSWORD=yourmongopassword # openssl rand -hex 16- π₯ Web Panel β Full UI for managing nodes and users
- π HTTP Auth β Centralized client verification via API
- π Auto Node Setup β Install Hysteria, certs, port hopping in one click
- π₯ Server Groups β Flexible user-to-node mapping
- βοΈ Load Balancing β Distribute users by server load
- π« Traffic Filtering (ACL) β Block ads, domains, IPs; route through custom proxies
- π Statistics β Online users, traffic, server status
- π± Subscriptions β Auto-format for Clash, Sing-box, Shadowrocket
- π Backup/Restore β Automatic database backups
- π» SSH Terminal β Direct node access from browser
- π API Keys β Secure external access with scopes, IP allowlist, rate limiting
- πͺ Webhooks β Real-time event notifications with HMAC-SHA256 signing
βββββββββββββββββββ
β CLIENTS β
β Clash, Sing-box β
β Shadowrocket β
ββββββββββ¬βββββββββ
β
hysteria2://user:pass@host
β
ββββββββββββββββββββββββββΌβββββββββββββββββββββββββ
βΌ βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β Node β β Node CH β β Node DE β
β Hysteria 2 β β Hysteria 2 β β Hysteria 2 β
β :443 + hop β β :443 + hop β β :443 + hop β
ββββββββββ¬βββββββββ ββββββββββ¬βββββββββ ββββββββββ¬βββββββββ
β β β
β POST /api/auth β β
β GET /online β β
ββββββββββββββββββββββββββΌβββββββββββββββββββββββββ
βΌ
ββββββββββββββββββββββββββ
β HYSTERIA PANEL β
β β
β β’ Web UI (/panel) β
β β’ HTTP Auth API β
β β’ Subscriptions β
β β’ SSH Terminal β
β β’ Stats Collector β
βββββββββββββ¬βββββββββββββ
β
βΌ
ββββββββββββββββββββββββββ
β MongoDB β
ββββββββββββββββββββββββββ
- Client connects to Hysteria node with
userId:password - Node sends
POST /api/authto the panel - Panel checks: user exists, enabled, device/traffic limits
- Returns
{ "ok": true, "id": "userId" }or{ "ok": false }
Instead of rigid "plans", use flexible groups:
- Create group (e.g., "Europe", "Premium")
- Assign nodes to group
- Assign users to group
- User gets only nodes from their groups in subscription
All /api/* endpoints (except /api/auth and /api/files) require authentication via either an API key or an admin session cookie.
Create a key: Settings β Security β API Keys β Create Key
Usage:
# Option 1 β header
X-API-Key: ck_your_key_here
# Option 2 β Bearer token
Authorization: Bearer ck_your_key_here| Scope | Access |
|---|---|
users:read |
Read users |
users:write |
Create / update / delete users |
nodes:read |
Read nodes |
nodes:write |
Create / update / delete / sync nodes |
stats:read |
Read stats and groups |
sync:write |
Trigger sync, kick users |
Each key has a configurable rate limit (default: 60 req/min).
Exceeded requests return 429 with X-RateLimit-Limit / X-RateLimit-Remaining headers.
| Code | Reason |
|---|---|
401 |
Invalid, expired, or missing key |
403 |
Key valid but missing required scope / IP not in allowlist |
429 |
Rate limit exceeded |
Validates user on node connection.
// Request
{ "addr": "1.2.3.4:12345", "auth": "userId:password" }
// Response (success)
{ "ok": true, "id": "userId" }
// Response (error)
{ "ok": false }Universal subscription endpoint. Auto-detects format by User-Agent.
| User-Agent | Format |
|---|---|
shadowrocket |
Base64 URI list |
clash, stash, surge |
Clash YAML |
hiddify, sing-box |
Sing-box JSON |
| Browser | HTML page |
| Other | Plain URI list |
Query params: ?format=clash, ?format=singbox, ?format=uri
Required scope: users:read (GET) / users:write (POST, PUT, DELETE)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/users |
List users (pagination, filtering, sorting) |
| GET | /api/users/:userId |
Get user |
| POST | /api/users |
Create user |
| PUT | /api/users/:userId |
Update user |
| DELETE | /api/users/:userId |
Delete user |
| POST | /api/users/:userId/enable |
Enable user |
| POST | /api/users/:userId/disable |
Disable user |
| POST | /api/users/:userId/groups |
Add user to groups |
| DELETE | /api/users/:userId/groups/:groupId |
Remove user from group |
Required scope: nodes:read (GET) / nodes:write (POST, PUT, DELETE)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/nodes |
List nodes |
| GET | /api/nodes/:id |
Get node |
| POST | /api/nodes |
Create node |
| PUT | /api/nodes/:id |
Update node |
| DELETE | /api/nodes/:id |
Delete node |
| GET | /api/nodes/:id/config |
Get node config (YAML) |
| POST | /api/nodes/:id/sync |
Sync specific node |
| POST | /api/nodes/:id/update-config |
Push config via SSH |
| POST | /api/nodes/:id/setup |
Auto-setup node via SSH (long-running, ~1β2 min) |
Required scope: stats:read / sync:write
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/stats |
stats:read |
Panel statistics |
| GET | /api/groups |
stats:read |
List server groups |
| POST | /api/sync |
sync:write |
Sync all nodes |
| POST | /api/kick/:userId |
sync:write |
Kick user from all nodes |
Send real-time event notifications to any HTTP endpoint.
Configure: Settings β Security β Webhooks
POST https://your-endpoint.com/webhook
Content-Type: application/json
X-Webhook-Event: user.created
X-Webhook-Timestamp: 1700000000
X-Webhook-Signature: sha256=<hmac>
User-Agent: C3-Celerity-Webhook/1.0
{
"event": "user.created",
"timestamp": "2024-01-01T00:00:00.000Z",
"data": { ... }
}const crypto = require('crypto');
const expected = 'sha256=' + crypto
.createHmac('sha256', YOUR_SECRET)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
// compare with X-Webhook-Signature header| Event | Trigger |
|---|---|
user.created |
User created |
user.updated |
User updated |
user.deleted |
User deleted |
user.enabled |
User enabled |
user.disabled |
User disabled |
user.traffic_exceeded |
User traffic limit reached |
user.expired |
User subscription expired |
node.online |
Node came online |
node.offline |
Node went offline |
node.error |
Node sync/config error |
sync.completed |
Full sync cycle finished |
Leave the events list empty to receive all events.
Before adding a node, understand these key concepts:
- Main port (443) β The port Hysteria listens on. Use 443 for best compatibility (often allowed through firewalls)
- Port hopping range (20000-50000) β Additional UDP ports that redirect to the main port. Helps bypass QoS/throttling
- Stats port (9999) β Internal port for collecting traffic statistics from the node
| Field | Purpose | Example |
|---|---|---|
| Domain | Used for ACME/Let's Encrypt certificates. Must point to the node's IP | de1.example.com β 1.2.3.4 |
| SNI | What clients show during TLS handshake (for domain fronting). Can be any domain | www.google.com or bing.com |
Common scenarios:
- Simple setup: Set
domainto a subdomain pointing to your node (e.g.,node1.example.com). LeaveSNIempty. - Domain fronting: Set
domainfor certificates, setSNIto a popular domain (e.g.,www.bing.com) to disguise traffic. - Same VPS for panel and node: Use different subdomains (e.g.,
panel.example.comfor panel,node.example.comfor node).
Note: The panel domain and node domain(s) should be different subdomains, but can point to the same IP if running on the same VPS.
- Add node in panel (IP, SSH credentials)
- Click "βοΈ Auto Setup"
- Panel will automatically:
- Install Hysteria 2
- Configure ACME certificates
- Set up port hopping
- Open firewall ports
- Start service
# Install Hysteria
bash <(curl -fsSL https://get.hy2.sh/)
# Create config /etc/hysteria/config.yaml
listen: :443
acme:
domains: [node1.example.com]
email: admin@example.com
auth:
type: http
http:
url: https://panel.example.com/api/auth
insecure: false
trafficStats:
listen: :9999
secret: your_secret
masquerade:
type: proxy
proxy:
url: https://www.google.com
rewriteHost: true# Start
systemctl enable --now hysteria-server
# Port hopping (redirect 20000-50000 to 443)
iptables -t nat -A PREROUTING -p udp --dport 20000:50000 -j REDIRECT --to-port 443You can run both the panel and a Hysteria node on the same VPS. Panel uses TCP, node uses UDP on port 443 β they don't conflict.
Option 1: Use panel domain (recommended)
Set the node's domain to the same as the panel domain. Auto-setup will automatically copy the panel's SSL certificates to the node.
- DNS:
panel.example.comβ Your VPS IP - Add node with:
- IP: Your VPS IP
- Domain:
panel.example.com(same as panel!) - Port: 443
- Click "Auto Setup" β certificates will be copied automatically
Option 2: No domain (self-signed)
Leave the domain field empty. A self-signed certificate will be generated.
- Add node with:
- IP: Your VPS IP
- Domain: (leave empty)
- Port: 443
- Click "Auto Setup"
Why not use a different domain?
If you use a different domain (e.g., node.example.com), ACME/Let's Encrypt will fail because port 80 is already used by the panel for its own certificate renewal. The auto-setup will warn you about this.
| Field | Type | Description |
|---|---|---|
userId |
String | Unique ID (e.g., Telegram ID) |
subscriptionToken |
String | URL token for subscription |
enabled |
Boolean | User active status |
groups |
[ObjectId] | Server groups |
trafficLimit |
Number | Traffic limit in bytes (0 = unlimited) |
maxDevices |
Number | Device limit (0 = group limit, -1 = unlimited) |
expireAt |
Date | Expiration date |
| Field | Type | Description |
|---|---|---|
name |
String | Display name |
ip |
String | IP address |
domain |
String | Domain for SNI/ACME |
port |
Number | Main port (443) |
portRange |
String | Port hopping range |
groups |
[ObjectId] | Server groups |
maxOnlineUsers |
Number | Max online for load balancing |
status |
String | online/offline/error |
| Field | Type | Description |
|---|---|---|
name |
String | Group name |
color |
String | UI color (#hex) |
maxDevices |
Number | Device limit for group |
Control how traffic is routed on each node. Access via Panel β Node β Traffic Filtering.
| Action | Description |
|---|---|
reject(...) |
Block connection |
direct(...) |
Allow through server |
reject(suffix:doubleclick.net) # Block ads
reject(suffix:googlesyndication.com)
reject(geoip:cn) # Block Chinese IPs
reject(geoip:private) # Block private IPs
direct(all) # Allow everything else
One-click presets available:
- Block Ads β doubleclick, googlesyndication, etc.
- Block CN/Private β Chinese and private IP ranges
- RU Direct β Russian sites go through server directly
- All Direct β No restrictions
Route specific traffic through your own SOCKS5/HTTP proxy:
- Add proxy in "Proxy Servers" section (e.g.,
my-proxy, SOCKS5,1.2.3.4:1080) - Use in rules:
my-proxy(geoip:ru)ormy-proxy(suffix:example.com)
Configure in Settings:
- Enable balancing β Sort nodes by current load
- Hide overloaded β Exclude nodes at capacity
Algorithm:
- Get user's nodes from groups
- Sort by load % (online/max)
- Filter overloaded if enabled
- Fall back to
rankingCoefficient
Limit simultaneous connections per user.
Priority:
- User's personal limit (
maxDevices > 0) - Minimum limit from user's groups
-1= unlimited
On each POST /api/auth:
- Query
/onlinefrom all nodes - Count sessions for userId
- Reject if
>= maxDevices
- Auto backups β Configure in Settings
- Manual backup β Dashboard button, auto-downloads
- Restore β Upload
.tar.gzarchive
version: '3.8'
services:
mongo:
image: mongo:7
restart: always
volumes:
- mongo_data:/data/db
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER:-hysteria}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
backend:
image: clickdevtech/hysteria-panel:latest # or build: . for development
restart: always
depends_on:
- mongo
ports:
- "80:80"
- "443:443"
volumes:
- ./logs:/app/logs
- ./greenlock.d:/app/greenlock.d
- ./backups:/app/backups
env_file:
- .env
volumes:
mongo_data:| Variable | Required | Description |
|---|---|---|
PANEL_DOMAIN |
β | Panel domain |
ACME_EMAIL |
β | Let's Encrypt email |
ENCRYPTION_KEY |
β | SSH encryption key (32 chars) |
SESSION_SECRET |
β | Session secret |
MONGO_PASSWORD |
β | MongoDB password |
MONGO_USER |
β | MongoDB user (default: hysteria) |
PANEL_IP_WHITELIST |
β | IP whitelist for panel |
SYNC_INTERVAL |
β | Sync interval in minutes (default: 2) |
API_DOCS_ENABLED |
β | Enable interactive API docs at /api/docs (default: false) |
Pull requests welcome!
MIT