vercel-xhttp-relay
Vercel XHTTP Relay
Skill by ara.so — Daily 2026 Skills collection.
A minimal Vercel Edge Function relay that forwards XHTTP traffic from Xray/V2Ray clients to a backend Xray server. Vercel's *.vercel.app domain acts as a CDN front, hiding your origin VPS IP from censors.
Architecture
Client (v2rayN/Hiddify)
→ TLS (SNI=vercel.com) to *.vercel.app
→ Vercel Edge Function (relay, no buffer)
→ HTTP/2 to backend Xray XHTTP inbound
Supported transports: XHTTP only
Not supported on Vercel Edge: WebSocket, gRPC, TCP, mKCP, QUIC, Reality
Prerequisites
- Linux VPS outside Iran (Ubuntu 22.04/24.04 recommended), min 1 vCPU / 1 GB RAM
- A domain with an A record pointing to your VPS (DNS only, not proxied)
- Vercel account (free Hobby tier works for light personal use)
- Node.js + npm installed locally (for Vercel CLI)
- Xray v1.8.16+ installed on VPS
Step 1 — Install Xray on VPS
# Update system
apt update && apt upgrade -y
apt install -y curl socat cron ufw
# Install Xray via official script
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install
# Verify version (must be >= 1.8.16)
xray version
# Generate a UUID for your inbound
xray uuid
# Save this UUID — you'll need it in client and server configs
Step 2 — Obtain a TLS Certificate
# Install acme.sh
curl https://get.acme.sh | sh
source ~/.bashrc # or re-login
# Issue certificate (replace with your domain)
~/.acme.sh/acme.sh --issue --standalone -d xray.yourdomain.com
# Install certificate to Xray paths
~/.acme.sh/acme.sh --install-cert -d xray.yourdomain.com \
--key-file /etc/xray/private.key \
--fullchain-file /etc/xray/cert.crt \
--reloadcmd "systemctl restart xray"
Step 3 — Xray Server Config (XHTTP Inbound)
/etc/xray/config.json:
{
"log": { "loglevel": "warning" },
"inbounds": [
{
"port": 443,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "$YOUR_UUID",
"flow": ""
}
],
"decryption": "none"
},
"streamSettings": {
"network": "xhttp",
"security": "tls",
"tlsSettings": {
"certificates": [
{
"certificateFile": "/etc/xray/cert.crt",
"keyFile": "/etc/xray/private.key"
}
]
},
"xhttpSettings": {
"path": "/your-secret-path",
"mode": "auto"
}
}
}
],
"outbounds": [
{ "protocol": "freedom", "tag": "direct" }
]
}
# Apply and start
systemctl restart xray
systemctl enable xray
systemctl status xray
Step 4 — The Vercel Edge Relay (api/proxy.js)
The relay function streams request bodies and responses without buffering:
// api/proxy.js — Vercel Edge Function
export const config = { runtime: 'edge' };
const BACKEND = process.env.XRAY_BACKEND_URL;
// e.g. https://xray.yourdomain.com:443/your-secret-path
export default async function handler(req) {
if (!BACKEND) {
return new Response('Backend not configured', { status: 500 });
}
const url = new URL(req.url);
const targetUrl = BACKEND + url.pathname.replace(/^\/proxy/, '') + url.search;
// Forward all headers except host
const headers = new Headers();
for (const [key, value] of req.headers.entries()) {
if (key.toLowerCase() !== 'host') {
headers.set(key, value);
}
}
try {
const response = await fetch(targetUrl, {
method: req.method,
headers,
body: req.method !== 'GET' && req.method !== 'HEAD' ? req.body : undefined,
// @ts-ignore — duplex required for streaming body
duplex: 'half',
});
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
} catch (err) {
return new Response(`Relay error: ${err.message}`, { status: 502 });
}
}
Step 5 — vercel.json
{
"rewrites": [
{ "source": "/(.*)", "destination": "/api/proxy" }
]
}
Step 6 — package.json (minimal)
{
"name": "vercel-xhttp-relay",
"version": "1.0.0",
"private": true
}
Step 7 — Deploy to Vercel
Via CLI
# Install Vercel CLI
npm i -g vercel
# Login
vercel login
# Deploy (from project root)
vercel
# Set the backend environment variable
vercel env add XRAY_BACKEND_URL
# Enter: https://xray.yourdomain.com:443/your-secret-path
# Redeploy with env var active
vercel --prod
Via Dashboard (no CLI needed)
- Push repo to GitHub
- Go to vercel.com/new → Import repo
- Settings → Environment Variables → add
XRAY_BACKEND_URL - Deploy
Your relay URL will be: https://your-project.vercel.app
Step 8 — Client Configuration (v2rayN / Hiddify)
| Field | Value |
|---|---|
| Protocol | VLESS |
| Address | your-project.vercel.app |
| Port | 443 |
| UUID | $YOUR_UUID (from xray uuid) |
| Transport | XHTTP |
| Path | /your-secret-path |
| TLS | TLS |
| SNI | vercel.com |
| Fingerprint | chrome |
| Flow | (empty) |
VLESS link format:
vless://$YOUR_UUID@your-project.vercel.app:443?encryption=none&security=tls&sni=vercel.com&fp=chrome&type=xhttp&path=%2Fyour-secret-path#MyVercelRelay
Project File Structure
vercel-xhttp-relay/
├── api/
│ └── proxy.js # Edge Function relay
├── vercel.json # Rewrite rules
└── package.json
Environment Variables
| Variable | Description | Example |
|---|---|---|
XRAY_BACKEND_URL |
Full URL to your Xray XHTTP inbound | https://xray.yourdomain.com:443/secret-path |
Set via CLI:
vercel env add XRAY_BACKEND_URL production
vercel env add XRAY_BACKEND_URL preview
Or in vercel.json for non-secret config (not recommended for backend URLs):
{
"env": {
"XRAY_BACKEND_URL": "@xray-backend-url"
}
}
Firewall Setup on VPS
# Allow SSH, XHTTP port
ufw allow 22/tcp
ufw allow 443/tcp
ufw enable
ufw status
Common Patterns
Multiple Vercel Projects for Failover
Deploy the same relay to multiple Vercel accounts and configure load balance in v2rayN:
// v2rayN outbound balancer (simplified concept)
{
"tag": "balancer",
"selector": ["relay1", "relay2", "relay3"]
}
Check Vercel Usage
vercel billing
# Or monitor at: https://vercel.com/dashboard → Usage tab
Redeploy After Config Change
vercel --prod
View Logs
vercel logs your-project.vercel.app --follow
Troubleshooting
502 Bad Gateway from Vercel
# Check Xray is running
systemctl status xray
# Check Xray logs
journalctl -u xray -f
# Verify port 443 is open
ufw status
ss -tlnp | grep 443
# Test backend directly (from your local machine, not Iran)
curl -v https://xray.yourdomain.com:443/your-secret-path
DNS Not Resolving
# Mac/Linux
dig @8.8.8.8 xray.yourdomain.com +short
# Windows PowerShell
Resolve-DnsName xray.yourdomain.com -Server 8.8.8.8 -Type A
DNS record must be DNS only (grey cloud in Cloudflare), not proxied.
TLS Certificate Issues
# Check cert expiry
~/.acme.sh/acme.sh --list
# Renew manually
~/.acme.sh/acme.sh --renew -d xray.yourdomain.com --force
# Verify cert is valid
openssl x509 -in /etc/xray/cert.crt -noout -dates
Xray Config Validation
xray run -test -c /etc/xray/config.json
# Should output: Configuration OK
Client Can't Connect
- Confirm SNI is set to
vercel.com, not your project domain - Confirm transport is
xhttp(notws,grpc, etc.) - Confirm path matches exactly between server config and client
- Check UUID matches exactly
Vercel Account Paused (Hobby Bandwidth Exceeded)
Hobby plan counts bandwidth twice (client↔Vercel + Vercel↔origin). To avoid:
- Exclude 4K video and large downloads from the proxy
- Use multiple Hobby accounts with failover
- Upgrade to Pro ($20/month) and set Spend Management limits
Vercel Hobby Plan Limits (Reference)
| Resource | Limit |
|---|---|
| Edge Function invocations | 500,000 / month |
| Fast Origin Transfer | 10 GB / month (counts both directions) |
| Bandwidth (to client) | 100 GB / month |
| Edge Function duration | 30 seconds max per request |
Monitor at Vercel Dashboard → Usage. Vercel sends email alerts at 80% and 100%.
Key Commands Cheatsheet
# VPS: install/manage Xray
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install
xray uuid
xray version
xray run -test -c /etc/xray/config.json
systemctl restart xray && systemctl status xray
journalctl -u xray -f
# Local: Vercel CLI
npm i -g vercel
vercel login
vercel # deploy preview
vercel --prod # deploy production
vercel env add XRAY_BACKEND_URL
vercel logs your-project.vercel.app --follow
vercel billing