XHTTP Setup: Migrate One Xray Server Safely

XHTTP Setup: Migrate One Xray Server Safely

Marcus Reid
October 11, 2026· 11 min read

An XHTTP setup is a new transport path, not a configuration-field rename. Add a separate entry, prove that one client can send and receive through it, and keep the original entry available for rollback. This guide uses Ubuntu 24.04 LTS x64 at both ends and Xray v26.3.27, with direct TLS, HTTP/2, and explicit stream-up.[1][2]

Key Takeaways

  • Give the trial its own binary, service, configuration, TCP port, and UUID.
  • Keep the old server service and client profile intact during acceptance.
  • Match transport path, mode, TLS server name, and credentials at both ends.
  • Test upload, download, long requests, refusal, and rollback separately.
  • Do not add a CDN, reverse proxy, REALITY, H3, or split uplink routing to this first migration.

These are documentation reference versions; a complete end-to-end test has not been performed. Checks below state methods and expected behavior, not measured outcomes. Use the self-hosting task and recovery checklist to define your acceptance boundary first.

What should an Xray XHTTP migration preserve?

Preserve the old listener, its service identity, certificate workflow, firewall rules, and client profile. Record the current unit's executable/configuration paths and port ownership. Save administrator-only backups of the old configuration and a client export. Do not publish UUIDs, keys, addresses, or destination logs.

Use only a server you administer, with console recovery and SSH host keys verified through an independent trusted channel. Use strict host-key checking and a dedicated known_hosts; stop on mismatch, revocation, or an unverified key. Do not let a VPN experiment become your only management path.

This route is worthwhile when you need to operate Xray and investigate its transport behavior. If operating upgrades and certificates does not serve your task, compare VPS and managed VPN responsibilities before changing a working entry. XHTTP does not replace the distinction between protocol, transport, and obfuscation.

Freeze the trial identity

Choose an unused TCP port, here 8443, and a domain you own, here xhttp.example.com. Check sudo ss -lntup and provider firewall policy. Reserve a new unit named xray-xhttp.service, a separate local client listener 127.0.0.1:1081, and a new UUID. Keep an independent client process or application for old-path observation.

Allow TCP 8443 only for the intended clients where practical. No UDP listener is needed for this route. Keep the existing SSH rule and certificate-renewal challenge ports. A new TLS listener does not justify disabling the firewall or taking over the old ACME service.

Step 1: Stage the fixed version and trusted certificate

Download the Linux 64-bit archive from the official v26.3.27 release, inspect its release notes and published verification material, and unpack it locally. Install the executable as root at /opt/xray-xhttp/v26.3.27/xray with mode 0755. Preserve the archive's supporting data files in that directory when provided. Repeat the installation on the Ubuntu client.[1]

Run /opt/xray-xhttp/v26.3.27/xray version on both ends and record the output privately. Do not substitute a prerelease or silently combine different cores. Generate a new identity with /opt/xray-xhttp/v26.3.27/xray uuid; store it in a protected note and both private configuration files.

Create a dedicated system account, xray-xhttp, with no login shell. Create /etc/xray-xhttp as root:xray-xhttp, mode 0750. Obtain a publicly trusted certificate for xhttp.example.com through the server's existing authorized ACME workflow. Place the full chain and private key under that directory, owned by root:xray-xhttp, mode 0640. Grant access only to these copies, not to an entire unrelated certificate store.

The existing renewal mechanism must update the copies atomically and restart only this trial service after validation. Record that hook before switching a daily-use client. If no certificate workflow exists, establish one separately first; this article does not introduce a second ACME daemon that competes for ports 80 or 443.

Step 2: Write the XHTTP stream-up configuration

Create /etc/xray-xhttp/server.json with sudoedit. Replace REPLACE_NEW_UUID with the generated UUID. This pinned version uses clients in its VLESS inbound; its outbound uses users. Do not transplant the inbound field from a later documentation revision.[3]

{
  "log": {"loglevel": "warning"},
  "inbounds": [{
    "tag": "xhttp-trial",
    "listen": "0.0.0.0",
    "port": 8443,
    "protocol": "vless",
    "settings": {
      "clients": [{"id": "REPLACE_NEW_UUID"}],
      "decryption": "none"
    },
    "streamSettings": {
      "network": "xhttp",
      "security": "tls",
      "tlsSettings": {
        "alpn": ["h2"],
        "certificates": [{
          "certificateFile": "/etc/xray-xhttp/fullchain.pem",
          "keyFile": "/etc/xray-xhttp/privkey.pem"
        }]
      },
      "xhttpSettings": {
        "path": "/migration-stream/",
        "mode": "stream-up"
      }
    }
  }],
  "outbounds": [{"tag": "direct", "protocol": "freedom"}]
}

This example listens on IPv4. Point the domain's A record at the correct server; do not advertise an unconfigured IPv6 listener. The VLESS none fields do not supply transport encryption: TLS provides that boundary here. No Vision flow is configured.

On the client, create a private client.json, owned by the logged-in user with mode 0600, inside a mode-0700 directory:

{
  "log": {"loglevel": "warning"},
  "inbounds": [{
    "listen": "127.0.0.1",
    "port": 1081,
    "protocol": "socks",
    "settings": {"auth": "noauth", "udp": false}
  }],
  "outbounds": [{
    "tag": "new-entry",
    "protocol": "vless",
    "settings": {
      "vnext": [{
        "address": "xhttp.example.com",
        "port": 8443,
        "users": [{"id": "REPLACE_NEW_UUID", "encryption": "none"}]
      }]
    },
    "streamSettings": {
      "network": "xhttp",
      "security": "tls",
      "tlsSettings": {
        "serverName": "xhttp.example.com",
        "alpn": ["h2"],
        "allowInsecure": false
      },
      "xhttpSettings": {
        "path": "/migration-stream/",
        "mode": "stream-up"
      }
    }
  }]
}

The identical path and explicit mode keep this trial bounded. stream-up uses streaming upload with a separate download path; H2 support must exist at the actual TLS endpoint. Renaming a gRPC field does not establish that behavior.[2][4]

Step 3: Check both files and start an independent service

Set server.json ownership to root:xray-xhttp, mode 0640. Check the server file as the service account and the client file as its user:

sudo -u xray-xhttp /opt/xray-xhttp/v26.3.27/xray run -test -config /etc/xray-xhttp/server.json
/opt/xray-xhttp/v26.3.27/xray run -test -config ./client.json

Require successful exits on both sides. These checks catch configuration problems, not live certificate trust, remote authentication, or data transfer. Keep the placeholder out of the actual files.

Create the root-owned /etc/systemd/system/xray-xhttp.service:

[Unit]
Description=Independent Xray XHTTP trial
After=network-online.target
Wants=network-online.target

[Service]
User=xray-xhttp
Group=xray-xhttp
WorkingDirectory=/opt/xray-xhttp/v26.3.27
ExecStart=/opt/xray-xhttp/v26.3.27/xray run -config /etc/xray-xhttp/server.json
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
UMask=0077

[Install]
WantedBy=multi-user.target

Run sudo systemctl daemon-reload and sudo systemctl start xray-xhttp. Confirm its PID owns TCP 8443 and the old unit retains its original listener. Inspect restricted journal output if startup fails. This trial does not yet require enabling autostart.

Start the client in the foreground with xray run -config ./client.json using the full pinned executable path. Configure only the trial application for SOCKS5 127.0.0.1:1081. Confirm that another application or client still uses the old entry. Never stop all Xray processes as a troubleshooting shortcut.

How do you verify XHTTP setup in both directions?

Prepare an HTTPS test endpoint you administer with a known download, an upload receipt, and a deliberately long response. Use nonsensitive test data and a defined duration/size budget. Do not send an upload to a third-party diagnostic site without permission.

curl --fail --show-error --noproxy '' --proxy socks5h://127.0.0.1:1081 https://check.example.net/download -o ./download.bin
curl --fail --show-error --noproxy '' --proxy socks5h://127.0.0.1:1081 --data-binary @./trial.bin https://check.example.net/upload

Replace both URLs with your controlled endpoints. Compare the downloaded content and the upload receipt against your known files. Repeat the same workload through the old client using its actual local port. Record completion, direction, duration, and failures; a fast first page does not establish a sustained upload path.

For each client-copy negative test, stop the previous foreground trial client, start only the changed copy on the same local port, and confirm that this process owns the listener. Change one variable at a time; restore the valid file and repeat a positive request before the next case.

Isolated change or checkExpected behaviorStop or recovery action
Valid UUID, TLS name, path, and modeDownload and upload complete correctlyInvestigate the failed direction before switching
Replace only UUID in a client copy with another generated UUIDNew proxy request fails authorizationCheck bypass, process identity, and server client list if it succeeds
Replace serverName with a controlled name absent from the certificateCertificate-name validation failsRestore correct name; keep allowInsecure: false
Replace client path with /different-trial/Trial request fails transport matchingRestore exact path; a suffix change may still match a server prefix
Set only client mode to packet-upExplicit stream-up server refuses packet-up uploadRestore mode; mode checks do not replace UUID authentication
Run a long request while observing the old entry separatelyTrial remains usable; old service retains its listener and existing taskStop trial if old traffic is affected
Restart only the trial serviceFresh new-path requests recoverInvestigate persistence and certificate permissions

The pinned server implementation checks path prefixes and packet-up versus stream-up upload handling. It can accept some other combinations; do not claim every different mode must be rejected.[5] Use the disjoint path and packet-up negative cases above, then restore the working configuration after each one.

Open the IP address tool in the configured browser to compare that request with expected server egress. This is not acceptance for all DNS, IPv6, UDP, or applications. If the old route was gRPC and still has independent failures, use the gRPC timeout diagnosis instead of assuming migration repaired it. For core roles, see what Xray and V2Ray do.

When can you switch, and how do you roll back?

Switch one client's saved application setting only after both data directions, sustained traffic, negative checks, certificate renewal handling, and trial restart satisfy your acceptance criteria. Preserve the old profile. Existing connections do not automatically move between entries; allow them to finish or reconnect them deliberately. Test a fresh request after the setting change.

Rollback starts on the client: restore its old profile or local SOCKS endpoint and verify a new request. Stop the foreground trial client. On the server, sudo systemctl disable --now xray-xhttp stops this entry without editing the original unit. Remove only the trial's firewall rule, UUID, configuration, and unneeded certificate copies after preserving private recovery evidence. Check that TCP 8443 is no longer owned by this service and that the old entry still works.

For later upgrades, stage another version directory and validate both files before changing this unit. Repeat the same acceptance matrix. Retain a compatible rollback copy temporarily, but do not freeze a vulnerable core permanently. Do not remove an old entry used by other clients merely because your one-client trial succeeded.

Summary

A safe migration preserves the working path and adds a separately observable trial. Matching configuration, bidirectional traffic, deliberate refusal, and a tested recovery path are the acceptance conditions. The examples describe those checks without claiming an author-run deployment passed them.

FAQ

Can I rename gRPC settings to XHTTP?

No. Install a compatible core and build a matching transport configuration at both ends, then verify actual upload and download behavior independently.

Why use another port and service?

Independent ownership lets you stop the trial without restarting the working entry. It also makes listener, configuration, and rollback mistakes easier to identify.

Does configuration validation prove the UUID works?

No. Syntax checks cannot establish remote authorization. Make a controlled proxy request with the correct UUID, then repeat with a different valid UUID.

Should every mode mismatch fail?

No. Acceptance depends on the pinned implementation. This guide uses a specific packet-up mismatch against the explicit stream-up server as its refusal check.

Does one successful download establish migration readiness?

No. Check uploads, sustained requests, refusal cases, restart, and recovery too. Keep the old entry available until the one-client acceptance criteria are satisfied.

Do old connections move to the new entry?

Existing connections retain their established path. Change the client setting deliberately and use fresh requests to verify the new entry before retiring anything.

Can I remove the old server configuration afterward?

Only after checking its remaining users and your recovery requirements. This single-client procedure does not authorize removing an entry used by other clients.

Sources:

  1. Xray-core release v26.3.27 — https://github.com/XTLS/Xray-core/releases/tag/v26.3.27
  2. Project X: XHTTP mode discussion — https://github.com/XTLS/Xray-core/discussions/4113
  3. Pinned Xray VLESS configuration implementation — https://github.com/XTLS/Xray-core/blob/v26.3.27/infra/conf/vless.go
  4. Project X: transport configuration — https://xtls.github.io/en/config/transport.html
  5. Pinned Xray XHTTP server implementation — https://github.com/XTLS/Xray-core/blob/v26.3.27/transport/internet/splithttp/hub.go

Sources checked 11 October 2026.

Related Articles:

Start your 3-day free trial

Sign up to experience all premium features at no cost.

*Available only to new users. Each user is limited to one trial.

XHTTP Setup: Migrate One Xray Server Safely | AethoVPN