Introduction

As a self-hosting enthusiast, I have a home infrastructure:

  • Orange Pi—a media server;

  • Synology—a file dump;

  • Neptune 4—a 3D printer with a web interface and a camera feed.

And I'd like to have secure remote access to it via my phone and PC, while also having internet access from outside of Russia.
I used to use OpenVPN for these purposes, but it's no longer reliable. So I started studying the documentation for an excellent tool from our Chinese comrades—Xray!

What you'll need:

  • A server with an external IP and the infrastructure. In my case, it's an Orange Pi, hereinafter—Bridge

  • The server you want to access—Server

  • A server outside of Russia for internet access. Hereinafter—Proxy

  • A client of your choice. Hereinafter—Client

  • Client and server on Linux—Xray-core, which can be installed via the official Xray installation script

  • Client on Android—v2rayNG

You can find more clients in the Xray-core repository

We'll use the VLESS-TCP-XTLS-Vision-REALITY configuration file as a base and start reading the Xray documentation

Routing happens on the client. If the client accesses the xray.com domain, for example, we direct the traffic to the Bridge, and for all other connections—to the Proxy. The Bridge then directs the traffic to the Server if the client accessed server.xray.com.
It looks like this:

The Xray configuration file is located at /usr/local/etc/xray/config.json, and after each change to this file, the service must be restarted via systemctl restart xray.service

Installation

  1. Install Xray on the Bridge and Proxy:

bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install
  1. Open port 443 on the Bridge and Proxy. For example, using ufw:

ufw allow 443
  1. Generate server keys on the Bridge or Proxy:

xray x25519

The private key is added to the Xray server's configuration file, and the public key—to the client's config

  1. Generate a UUID for each client and come up with a shortId for it:

xray uuid

The UUID and shortId are added to both the server and client configs
shortId must be a hex number no longer than 16 characters and with a length that is a multiple of 2, for example, a0, b1, ff

  1. Add the private key, UUID, shortId, and the server hostname you will use to connect to the Bridge's configuration file at /usr/local/etc/xray/config.json:

`/usr/local/etc/xray/config.json`
 {
     "log": {
         "loglevel": "debug"
     },
     "inbounds": [Add to the file
         {
             "tag": "external",
             "port": 443,
             "protocol": "vless",
             "settings": {
                 "clients": [
                     {
                         "id": "client_uuid_0", // run `xray uuid` to generate
                         "flow": "xtls-rprx-vision"
                     },
                     {
                         "id": "client_uuid_1",
                         "flow": "xtls-rprx-vision"
                     }
                 ],
                 "decryption": "none"
             },
             "streamSettings": {
                 "network": "tcp",
                 "security": "reality",
                 "realitySettings": {
                     "dest": "google.com:443", // You can also use `1.1.1.1:443` as dest
                     "serverNames": [
                         "google.com" // If you use `1.1.1.1:443` as dest, then you can leave `serverNames` empty, it is a possible ways to bypass Iran's internet speed restrictions.
                     ],
                     "privateKey": "bridge_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
                         "client_shortId_0", // If this item exists, client shortId can be empty
                         "client_shortId_1",
                         "0123456789abcdef" // 0 to f, length is a multiple of 2, maximum length is 16
                     ]
                 }
             },
             "sniffing": {
                 "enabled": true,
                 "destOverride": [
                     "http",
                     "tls",
                     "quic"
                 ],
                 "routeOnly": true
             }
         }
     ],
     "outbounds": [
         {
             "tag": "out",
             "protocol": "freedom"
         },
         {
             "tag": "server",
             "protocol": "freedom",
             "settings": {
                 "redirect": "server_lan_ip:0" // Forward all traffic to web server
             }
         }
     ],
     "routing": {
         "rules": [
             {
                 "type": "field",
                 "domain": [
                     "server.xray.com"
                 ],
                 "outboundTag": "server"
             }
         ]
     }
 }
  1. For the Proxy, it's a bit simpler—just add the private key, UUID, and shortId:

spoiler
  {
      "log": {
          "loglevel": "debug"
      },
      "inbounds": [
          {
              "tag": "proxy",
              "port": 443,
              "protocol": "vless",
              "settings": {
                  "clients": [
                      {
                          "id": "client_uuid_0", // run `xray uuid` to generate
                          "flow": "xtls-rprx-vision"
                      },
                      {
                          "id": "client_uuid_1",
                          "flow": "xtls-rprx-vision"
                      }
                  ],
                  "decryption": "none"
              },
              "streamSettings": {
                  "network": "tcp",
                  "security": "reality",
                  "realitySettings": {
                      "dest": "google.com:443", // You can also use `1.1.1.1:443` as dest
                      "serverNames": [
                          "google.com" // If you use `1.1.1.1:443` as dest, then you can leave `serverNames` empty, it is a possible ways to bypass Iran's internet speed restrictions.
                      ],
                      "privateKey": "proxy_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
                          "client_shortId_0", // If this item exists, client shortId can be empty
                          "client_shortId_1",
                          "0123456789abcdef" // 0 to f, length is a multiple of 2, maximum length is 16
                      ]
                  }
              },
              "sniffing": {
                  "enabled": true,
                  "destOverride": [
                      "http",
                      "tls",
                      "quic"
                  ],
                  "routeOnly": true
              }
          }
      ],
      "outbounds": [
          {Add to the file
              "protocol": "freedom",
              "tag": "direct"
          }
      ]
  }
  1. Restart the xray service on the Bridge and Proxy:

systemctl restart xray.service

Connecting with a Client

The configuration file is common for all clients.
Add the UUID, shortId, the Proxy and Bridge hostnames or IPs, and the domain to which you will redirect traffic to the configuration file:

spoiler
{
    "log": {
        "loglevel": "debug"
    },
    "routing": {
        "rules": [
            {
                "type": "field",
                "domain": [
                    "xray.com"
                ],
                "outboundTag": "bridge"
            }
        ]
    },
    "inbounds": [
        {
            "listen": "127.0.0.1",
            "port": 10808,
            "protocol": "socks",
            "settings": {
                "udp": true
            },
            "sniffing": {
                "enabled": true,
                "destOverride": [Add to the file
                    "http",
                    "tls",
                    "quic"
                ],
                "routeOnly": true
            }
        }
    ],
    "outbounds": [
        {
            "protocol": "vless",
            "settings": {
                "vnext": [
                    {
                        "address": "proxy_hostname_or_ip",
                        "port": 443,
                        "users": [
                            {
                                "id": "client_uuid_0", // Needs to match server side
                                "encryption": "none",
                                "flow": "xtls-rprx-vision"
                            }
                        ]
                    }
                ]
            },
            "streamSettings": {
                "network": "tcp",
                "security": "reality",
                "realitySettings": {
                    "fingerprint": "chrome",
                    "serverName": "google.com", // If your dest is `1.1.1.1:443`, then leave it empty
                    "publicKey": "proxy_publicKey", // run `xray x25519` to generate. Public and private keys need to be corresponding.
                    "spiderX": "", // If your dest is `1.1.1.1:443`, then you can fill it with `/dns-query/` or just leave it empty
                    "shortId": "client_shortId_0" // Required
                }
            },
            "tag": "proxy"
        },
        {
            "protocol": "vless",
            "settings": {
                "vnext": [
                    {
                        "address": "bridge_hostname_or_ip",
                        "port": 443,
                        "users": [
                            {
                                "id": "client_uuid_0", // Needs to match server side
                                "encryption": "none",
                                "flow": "xtls-rprx-vision"
                            }
                        ]
                    }
                ]
            },
            "streamSettings": {
                "network": "tcp",
                "security": "reality",
                "realitySettings": {
                    "fingerprint": "chrome",
                    "serverName": "google.com", // If your dest is `1.1.1.1:443`, then leave it empty
                    "publicKey": "bridge_publicKey", // run `xray x25519` to generate. Public and private keys need to be corresponding.
                    "spiderX": "", // If your dest is `1.1.1.1:443`, then you can fill it with `/dns-query/` or just leave it empty
                    "shortId": "client_shortId_0" // Required
                }
            },
            "tag": "bridge"
        }
    ]
}

Linux

  1. Install Xray:

bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install
  1. Write the configuration file to /usr/local/etc/xray/config.json

  2. Restart the service:

systemctl restart xray.service

A local SOCKS5 proxy is now running on the client, which you can connect to through a browser. For example, in Firefox: Settings -> Network Settings -> Settings -> Manual proxy configuration:
SOCKS Host: 127.0.0.1 Port: 10808
Check the SOCKS v5 option

Android

  1. Install v2rayNG

  2. Upload the configuration file to your phone

  3. Add the configuration file in the app via Custom config -> Import custom config from locally

  4. Turn it on

You can choose which apps to route through Xray via Settings -> VPN Settings -> Per-app proxy

Conclusion

Now we have access to our infrastructure by domain name and a normal internet connection.
As a bonus:
You can connect to the Server via SSH through Xray. In a single line:

ssh root@server.xray.com -o "ProxyCommand=nc -X connect -x 127.0.0.1:10808 %h %p"

Or add the following to ~/.ssh/config:

Host server
    Hostname server.xray.com
    User root
    ProxyCommand nc -X 5 -x 127.0.0.1:10808 %h %p
    ServerAliveInterval 10