Self-hosted Xray Proxy

Bypass any censorship mechanisms and content blockers

More often than not, you may find your network provider blocking, censoring, or monitoring their network traffic. For various reasons, you may want to not be blocked, censored, or monitored while accessing the internet.

In this post, I’ll show you how you can do so by setting up your own obfuscated proxy server to circumvent this issue. In particular, we’ll use the xray proxy protocol and server, which is a nearly undetectable proxy server that uses encryption similar to Transport Layer Security (TLS) to have the proxy traffic blend in with regular web traffic. This means that it will work even if the network is configured to detect and block proxies.

You will need a Linux server with ports exposed to the internet to get started. In addition, you should be somewhat familiar with the Linux command line interface; however, no god-like Linux skills are necessary. This guide is a condensed version of this somewhat poorly translated tutorial found in the xray documentation.

Setup Nginx

Our proxy server requires a TLS encryption tunnel to securely function. In order to establish a TLS tunnel, we need a TLS certificate. To obtain a TLS certificate, we will need to port forward our server on ports 80 and 443.

In addition, we need a domain name and a web server on our machine to request the certificate (either top-level domains or subdomains work). If you do not have a domain name to use, you can obtain a free subdomain at FreeDNS by signing up and add a subdomain from the sidebar. Make sure your domain points to your public IP address.

For the web server, we will install nginx. This way our certificate gets approved correctly when the certificate authority (CA) gives us a challenge. It is recommended to host a website on the server for security purposes (i.e. to disguise the fact that you are running a proxy on your server, should someone decide to check by connecting to your domain name), but it is not necessary. Our goal right now is that we just need this website to expose certain directories on our machine to satisfy the TLS certificate challenge requirements from the CA.

$ sudo apt update
$ sudo apt install nginx

Then, edit the server configuration file for nginx. You’ll need to change the server block to handle requests to our domain to prove to the CA that we actually own our domain. Change the server_name1 below to your domain and root directory to somewhere you have write permissions to (you may need to create the directory first):

$ sudoedit /etc/nginx/sites-available/default
server {
    listen 80 default_server;
    listen[::]:80 default_server;
    # ...
    root /home/admin/webroot;
    server_name subdomain.domain.com;
}
$ sudo systemctl restart nginx
$ sudo systemctl enable nginx

You’ll also need to ensure that the nginx user, www-data by default, has read and write permissions to the web root. This way the web server can read and serve the files in the directories. You may also need to change the permissions of parent directories as well (for example, your home directory if you placed your web root inside it):

$ sudo chmod a+r /home/admin/webroot

TLS Certificate

Next, we can request our certificates. We can use the acme protocol to request and renew our certificate for free. This can be done by using acme.sh and Let’s Encrypt. Install acme.sh and its aliases:

$ curl https://get.acme.sh | bash
$ source .bashrc

Before actually requesting the certificate, we should first issue a test certificate. In this manner, we can avoid repeatedly requesting certificates due to local configuration errors, resulting in us hitting the rate limit for Let’s Encrypt. Afterward, if no issues occur during the test request, we can proceed to issue the actual certificate. Note that the domain and the path in the commands below are the ones that you entered in the nginx configuration file:

# Test issuing the certificate. 
$ acme.sh --issue --server letsencrypt_test -d subdomain.domain.com -w /home/admin/webroot
# If everything was successful, issue the actual certificate. Otherwise, try
# browsing through acme.sh's documentation to see if you can fix the issue:
# https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
$ acme.sh --set-default-ca --server letsencrypt
$ acme.sh --issue -d subdomain.domain.com -w /home/admin/webroot --keylength ec-256 --force

Install the certificate and save the paths for use later. Because the key file isn’t readable to other users, and our proxy server runs on the user nobody by default (feel free to change it), we will also need to edit its read permissions:

$ acme.sh --installcert -d subdomain.domain.com --key-file /home/admin/cert.key --fullchain-file /home/admin/fullchain.crt --ecc
$ chmod +r /home/admin/cert.key # Add read permissions

Proxy Server

Now we can finally get to setting up the xray proxy server. As alluded to earlier, xray uses encryption similar to TLS to obfuscate the traffic, thereby making it mimic regular web traffic characteristics to any observing middle person. This is the reason why we first needed an actual TLS certificate issued by a CA, this way the handshake process will look legitimate. To install the xray server:

$ curl -fsSL https://github.com/XTLS/Xray-install/raw/main/install-release.sh | sudo bash

Generate a UUID to save for later2:

$ xray uuid

Then, edit the xray configuration file. You can check out the configuration documentation for xray. However, the documentation, like the aforementioned tutorial, is poorly written. Therefore, I have provided a general template below on what you can customize below. In order to use it, put your UUID in the “inbound > clients” section:

$ sudoedit /usr/local/etc/xray/config.json
{
    // The DNS servers that your proxy server will use to query requests
    // from clients. For example, if a client connects to google.com
    // through your proxy, google.com will be resolved using the
    // following DNS servers.
    "dns": {
        "servers": [
            "localhost",
            "1.1.1.1",
            "8.8.8.8"
        ]
    },

    // Categorizing and tagging different types of client connections.
    // For example, the following tags private IP, known advertizers,
    // and torrenting protocol as "block". You can specify how to deal
    // with each of the categories in the outbounds object below. 
    "routing": {
        "domainStrategy": "IPIfNonMatch",
        "rules": [
            {
                "type": "field",
                "ip": ["geoip:private"],
                "outboundTag": "block"
            },
            {
                "type": "field",
                "domain": ["geosite:category-ads-all"],
                "outboundTag": "block"
            },
            {
                "type": "field",
                "protocol": ["bittorrent"],
                "outboundTag": "block"
            }
        ]
    },

    // Handling client connections to the proxy. You should not change
    // the port or the protocol, because they are integral to blending
    // your proxy connections in HTTPS traffic. You can also add config
    // for multiple clients in the client section below. This will be
    // useful if you want to share your proxy server with others. 
    "inbounds": [
        {
            "port": 443,
            "protocol": "vless",
            "settings": {
                "clients": [
                    {
                        "id": "",           // Put your UUID here! 
                        "flow": "xtls-rprx-vision",
                        "email": "admin",   // The email is mostly for
                        "level": 0          // identification purposes
                    }                       // in the logs. You don't
                ],                          // actually need to put
                                            // a valid email here. 
                "decryption": "none",
              
                // If the inbound traffic is not the vless protocol, we
                // want to fallback to the nginx web server. This can
                // help us disguise our proxy server as an actual
                // website.
                "fallbacks": [{
                    "dest": 80,
                    "xver": 1
                }]
            },

            // These should be pretty self-explanatory TLS encryption
            // settings. Make sure to change the certificate paths. 
            "streamSettings": {
                "network": "tcp",
                "security": "tls",
                "tlsSettings": {
                    "alpn": "http/1.1",
                    "minVersion": "1.2",
                    "maxVersion": "1.3",
                    "certificates": [{
                        "certificateFile": "/home/admin/fullchain.crt",
                        "keyFile": "/home/admin/cert.key"
                    }]
                }
            }
        }
    ],

    // The outbounds section dictates how client traffic is processed
    // based on the tags specified in the inbound section. Any traffic
    // that isn't tagged will be sent to the first outbound. For more
    // information, see the "outbounds" section on the sidebar here:
    // https://xtls.github.io/en/config/outbounds/blackhole.html
    "outbounds": [
        {
            "domainStrategy": "AsIs",
            "tag": "direct",
            "protocol": "freedom"
        },
        {
            "tag": "block",
            "protocol": "blackhole"
        }
    ]
}
$ sudo systemctl enable xray
$ sudo systemctl restart xray

Proxy Client

Now that the hard part is finished, we can get to connecting to your new proxy server with clients. The clients that I would suggest you use are:

Different clients will have different user interfaces, so it is unrealistic for me to cover how to use each and every client in this blog post. However, I will list out some common settings you may encounter and what those settings will be using our example configuration file:

Field Value
Address/SNI subdomain.domain.com1
Flow xtls-rprx-vision
ALPN http/1.1
Port 443
Protocol vless
Network TCP
Security TLS
UUID <Your UUID here>

Footnotes


  1. The domain subdomain.domain.com is used as an example domain throughout this post. Put your actual domain in these fields. ↩︎ ↩︎

  2. The UUID generated is not tied to xray. In other words, you can use any other UUID generators and it would work fine. ↩︎



0

Have a comment or a question about this post? Reply by email!