Introduction

In an era where internet access is increasingly filtered or restricted, having your own secure tunnel to the open web can be a powerful tool.

This guide will walk you through setting up an Xray server — a modern, flexible proxy framework — to help you regain private, uncensored access to the global internet. Whether you’re trying to overcome national firewalls or simply want full control over your browsing privacy, this solution can serve as a reliable alternative to commercial VPNs.

What Is Xray

Xray is a next-generation proxy platform designed for building secure and flexible internet tunnels. It is based on V2Ray, a widely used open-source project, but includes additional features, better performance, and ongoing development by a more active community. Xray supports many protocols, such like VMess, VLESS, Trojan, etc. You can always find the one you like.

Prerequisites

  • A VPS outside mainland China
  • Optional: A domain name (with DNS access)
  • Basic Linux knowledge
  • SSH access to your server

Server Setup

There are several ways to install Xray on your VPS, as listed in the official guide: Install on Linux. I recommend to use official Docker image xray-core to install the Xray on VPS — it makes integration with other services like caddy via Docker Compose much easier. This approach also works well with Ansible (which I will cover in another post).

For now, create a simple Docker Compose file named compose.yml:

  services:
    xray:
      container_name: xray
      image: ghcr.io/xtls/xray-core
      restart: always
      ports:
        - "${REALITY_PORT}:${REALITY_PORT}"
      extra_hosts:
        - "host.docker.internal:host-gateway"
      volumes:
        - ./config.json:/etc/xray/config.json
        - caddy_data:/data

Next, write a minimal configuration file config.json. You can refer to the official examples at Xray-examples. Here’s a simplified configuration using VLESS-TCP-XTLS-Vision-REALITY:

  {
    "log": {
      "loglevel": "debug"
    },
    "routing": {
      "domainStrategy": "IPIfNonMatch",
      "rules": [
        {
          "domain": ["geosite:geolocation-!cn"],
          "outboundTag": "direct",
          "ruleTag": "Known Site"
        },
        {
          "ip": ["geoip:cn", "geoip:private"],
          "outboundTag": "blocked",
          "ruleTag": "Private/CN IP"
        }
      ]
    },
    "inbounds": [
      {
        "port": ${REALITY_PORT},
        "protocol": "vless",
        "settings": {
          "clients": [
            {
              "id": "${ CLIENT_ID }", // run `xray uuid` to generate
              "flow": "xtls-rprx-vision"
            }
          ],
          "decryption": "none"
        },
        "streamSettings": {
          "network": "tcp",
          "security": "reality",
          "realitySettings": {
            "target": "", // A TLS 1.3 + HTTP/2-capable website (e.g. 1.1.1.1:443). This filed was called `dest` in old versions.
            "serverNames": [
              ""    // A server name from the target's certificate; can be left empty if using 1.1.1.1
            ],
            "privateKey": "", // run `xray x25519` to generate. Public and private keys need to be corresponding.
            "shortIds": [// Required, list of shortIds available to clients, can be used to distinguish different clients
              "", // If this item exists, client shortId can be empty
              "${ REALITY_SHORT_ID }" // 0 to f, length is a multiple of 2, maximum length is 16
            ]
          }
        },
        "sniffing": {
          "enabled": true,
          "destOverride": [
            "http",
            "tls",
            "quic"
          ],
          "routeOnly": true
        }
      }
    ],
    "outbounds": [
      {
        "protocol": "freedom",
        "tag": "direct"
      },
      {
        "protocol": "blackhole",
        "tag": "blocked"
      }
    ]
  }

You can see there's some difference with the official one, which block the traffic back to CN, this is a trick to prevent the IP get banned.

You may also need to install the xray binary on your local machine to run the command to generate the public and private keys, you may also use it to generate a uuid for client identification.

Here are some examples for the commends

  xray x25519
Privatekey:UM5Gl3GkcDfbaVjAYM1vsaWkw5qns1-pi-PNfojdvEg
Publickey:qWyB3TJzKNpWy9TWFt8COpc5qruIlfiM0sC5zQwFHQs
  xray uuid
65cf2486-44dc-4875-9188-da4274f3a9d7

For REALITY_SHORT_ID, you can use this commend to generate:

  openssl rand -hex 8
b9ce4115b8b311e9

Remember to replace the ${REALITY_PORT} a port number of your choice. Avoid using common ports like 443 to reduce detection risk.

After editing your files, start the container:

  docker compose up -d

Client Setup

Popular clients include Shadowrocket and v2rayN. Most modern clients support the VLESS share link standard, as defined in VMessAEAD / VLESS 分享链接标准提案. Here’s an example link format for the VLESS-TCP-XTLS-Vision-REALITY:

  vless://${ CLIENT_ID }@${ ADDRESS }:${ REALITY_PORT }?type=tcp&security=reality&flow=xtls-rprx-vision&pbk=${ REALITY_PUBLIC_KEY }&sni=${ REALITY_TARGET }&sid=${ REALITY_SHORT_ID }#${ DESCRIPTIVE_TEXT }
ADREESS
Your VPS's public IP.
REALITY_PUBLIC_KEY
Generated via xray x25519.
CLIENT_ID
UUID from xray uuid.
DESCRIPTIVE_TEXT
Any friendly name to identify this server and protocol.

Once you import the link into your client, test the connection by visiting a real website. Avoid using TCP ping or delay tests — these may trigger detection and result in your VPS IP being blocked.