ZNHOO Whatever you are, be a good one!


  1. Outline
  2. Installation
  3. Server Configuration
    1. Nginx Template
    2. proxy_intercept_errors
    3. Customize HTTP Header
    4. SELinux and Firewall
    5. Test the Server
    6. Systemd
  4. Client Configuration
    1. sniffing
    2. DNS
    3. Domain List
    4. Vmess and Clock
  5. Windows
    1. Global Proxy
  6. Cloudflare CDN
  7. Refs


Firefox                                youtube.com
|                                      |
Socks V2                               Freedom
|                                      |
Vmess                                  Vmess
|                                      |
Websocket                              Websocket V2
|                                      |
HTTP =========== Cloudflare ========== HTTP Nginx proxy_pass
|                                      |
TLS                                    TLS
|                                      |
TCP                                    TCP
.                                      .
.                                      .
.                                      .
> .................................... <
  1. JSON comments (//foo or /* bar */) are accepted by V2.
  2. Encryption between Nginx and backend V2 can be abandoned.
  3. Cloudflare now provides free websocket caching service. You cannot camouflage the Host header when Cloudflare CDN is turned on.
  4. Before launching the service, use V2 -test -config config.json to check JSON syntax.


Just extract the Zip archive.

~ # unzip /path/to/v2ray.zip -d /opt/v2ray/
~ # mkdir -p /opt/v2ray/log
~ # chown -R nobody: /opt/v2ray/

As we will run the service as nobody, so make sure the directory and files onwership is correct.

Server Configuration

Refer to backup.

  1. The 'listen' address of 'inbounds' should be '' instead of '', disallowing external direct access.

Nginx Template

location /V2 {
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $http_host;
    #proxy_intercept_errors on;
    #error_page 400 502 = /;

A side effect when browsing '/V2', brwoser reports "400 Bad Request" as the backend V2 is a websocket server instead of a bare HTTP server. We may also receive "502 Bad Gateway" as the backend V2 is not started yet.


Add the following code to '/V2' location:

proxy_intercept_errors on;
error_page 400 /404.html;
error_page 502 /50x.html;

The code will return '404.html' page when the backend code is 400. We can also return other URI:

error_page 400 502 = /;

The code does internal redirect and the symbol = makes the new response code equal to that of the new page. This method is always preferred. A more robust method would be:

proxy_intercept_errors on;

error_page 400 404 /404.html;
location = /404.html {

error_page 500 502 503 504 /50x.html;
location = /50x.html {

The internal specifies that a given location can only be used for internal redirects. External browser requests to the '/V2' URI would respond "404 (Not Found)" to end users.

Alternatively, try the next method in section Customize HTTP Header.

Customize HTTP Header

Unlike the section Nginx Template, we can proxy_pass based on customized HTTP headers under the '/' location, using the if directive. This method requires V2 client to be 2.40.2 or above.

ssl_certificate      /etc/letsencrypt/live/www.example.com/fullchain.pem;
ssl_certificate_key  /etc/letsencrypt/live/www.example.com/privkey.pem;

location / {
    root /var/www;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $http_host;
    if ( $http_host = "howareyou" ) {

The template above uses customized 'Host' header to redirect requests to backend. The customized Host header must not be the website domain. Otherwise, all normal HTTP traffic goes to backend as well. Usually, we set it to a public domain like www.amazon.com. As you will, it can be any arbitrary strings, like howareyou.

Modify V2 configuration accordingly:

"wsSettings": {
  "path": "/",
  "headers": {
    "Host": "howareyou"

Manually manipulate the 'Host' header is not recommended as it is crucial for vhost matching. If V2 supports self-defined header, then it is far more desirable. By the way, it rerquires the evil if directive. Actually, we can also redirect based on the 'Upgrade' header:

if ( $http_upgrade = "websocket" ) {

This method does not require special V2 wsSettings but still uses the if directive.

SELinux and Firewall

On servers with SELinux on, V2ray may report websocket: bad handshake. By default, SELinux does not permit Nginx operations, such as proxying to upstream locations or communicating with other processes through sockets.

Then make sure:

~ # grep 'denied.*nginx' /var/log/audit/audit.log
~ # getsebool -a

~ # setsebool -P httpd_can_network_connect 1

Also make sure, port 443 is open to the Internet.

Test the Server

~ # /opt/v2ray/v2ray -test -c /opt/v2ray/config.json
~ # sudo -u nobody -g nobody -- /opt/v2ray/v2ray -config /opt/v2ray/config.json
~ # ss -nplt
~ # tail -F /opt/v2ray/log/{access,error}.log
~ # tail -F /var/log/nginx/{access,error}.log


From now on, V2 provides built-in Systemd unit file. Before enable the unit, please update the following items:

  1. Use nobody account instead of the root.
  2. Add a line ExecStartPre=/usr/bin/chown -R nobody:nobody /opt/v2ray/.

Then we can enable and start the service:

~ # systemctl daemon-reload

~ # systemctl enable /opt/v2ray/systemd/v2ray.service
~ # systemctl start v2ray.service

# -or-
~ # systemctl enable /opt/v2ray/systemd/v2ray@.service
~ # systemctl enable v2ray@4.27.4
~ # systemctl start v2ray@4.27.4

Client Configuration

Refer to backup. It's the part we update the first.

As TLS encryption is adopted, extra Vmess encryption is unnecessary unless Cloudflare CDN is adopted. Details refer to Cloudflare CDN section.


This option is used by inbound or inbounddetour to retrieve domains upon receiving IP packets. Usually, users connect to a domain instead of an IP. Most of the time V2 inbound or inbounddetour receives the domain (as DNS query first) such that relevant traffic is directed in accord with domain rules of the routing section.

However, it happens that a client may connect to an IP directly (i.e. Telegram). What is worse, IP packets can bypass domain rules.

Additionally, if a user forgets to select:

Proxy DNS when using SOCKS v5

in Firefox setting, DNS queries would be sent to local DNS service and then IP packets go to V2. Obviously, local DNS replies are usually poisoned.

Therefore, sniffing comes to help such that it siniffs domains from IP packets, reset the connection, and route against domain rules. DNS poision issue is resolved as well.

From the discussion, we find routing based on IP packet sniffing is more reliable and robust. In the meantime, dokodemo transparent proxy redirects IP packets based on Iptables.


V2 has built-in DNS support, mainly for two purposes:

  1. Support the routing rules based on domainStrategy setting.

    For example, in order to match an IP rule in routing, the target domain must be resolved to IP first by the dns section. That IP will only be used to re-reroute the request, namely deciding which outbound to use!

  2. DNS service for the freedom outbound.

    When a target domain is routed to the freedom outbound, it should be directly accessed. V2ray will consult the dns section to resolve the domain. Once got the host IP to connect to, it should be routed again in the routing section. This is a little bit confusing!

Apart from the the built-in support, V2ray has a dns outbound that can be used to serve as a open DNS service, similar to Dnsmasq.

  1. Disable "Proxy DNS when using SOCKS v5" in Firefox.
  2. At V2ray client side:
    1. Set up a dokodemo-door inbound at port 53, receiving DNS query.
    2. Set up a dns outbound.
    3. Set up a routing rule, routes the query from dokodemo-door inbound to the dns outbound.
    4. The dns outound intercept DNS queries and redirect the queries to the dns section.

We cal also combine V2ray and Dnsmasq. Queries are first sent to local Dnsmasq, then to V2ray dokodemo-door.

Domain List

V2ray has built-in domain list, like geosite.dat and geoip.dat, which can be used in the routing section and the dns section to achieve smart proxy. We can override the two files from 3rd parties like v2ray-rules-dat.

Besides, V2ray can also load domain list from external files with prefix ext, like h2y.dat. Similar to the built-in domain list, reference to external list can be:


Vmess and Clock

Vmess depends on system clock to do authorization, so make sure the clock delta between client and server fall within 90s.


Global Proxy

To enable global Socks proxy on Windows, the address part in "Windows Settings" should be http://socks= Alternatively and recommended, use the "Internet Options", "Connections", "LAN settings", "Proxy Server", "Advanced" and "Bypass proxy server for local addresses".

Alternatively, use 3rd-party tools like Clash as the client side of V2. Whatever methods you choose, make sure to bypass the remote domain and VPS IP.

However, Windows UWP app like Mail, Calendar etc. have their own sandbox (namely App Container) and does not respect system proxy, including the "Microsoft Store" itself. The container prevents App from connecting to system's loopback interface, such that Wireshark cannot capture UWP traffic and UWP does not use global proxy. Clash supports UWP loopback access and enjoys the proxy.

It is recommended to use Windows' built-in CheckNetIsolation.exe.

PS C:\Users\jim> CheckNetIsolation.exe -?
PS C:\Users\jim> CheckNetIsolation.exe LoopbackExempt -?
PS C:\Users\jim> CheckNetIsolation.exe LoopbackExempt -s

PS C:\Users\jim> CheckNetIsolation.exe LoopbackExempt -a -n=<package-family-name>
PS C:\Users\jim> CheckNetIsolation.exe LoopbackExempt -a -p=<sid>

How to find out an App's "Package Family Name"?

PS C:\Users\jim> Get-AppxPackage > lst  # list full list
PS C:\Users\jim> Get-Content .\lst.md | Select-String -Pattern "wechat"
PS C:\Users\jim> Get-Content .\lst.md | Select-String -Pattern ".*windowscommunicationsapps.*"  # Mail and Calender

Cloudflare CDN

Turn on CDN to hide server IP from censorship and probably accelerate speed.

Cloudflare supports caching websocket traffic for free accounts. To enable Cloudflare CDN on V2, just click the orange symbol on DNS record.

You might wait for a while before the DNS setting comes into effect. Check by dig your.domain.com and find the returned IP be that of Cloudflare's CDN servers.

A few notes:

  1. Cloudflare's free CDN service actually degrade instead of accelerate your service.

    This can be caused by ISP dropping IP packets to its servers. What's worse, Cloudflare may do this on purpose for paid service.

  2. Once enabled, you must reset Customized HTTP headers the real domain name instead of howareyou.

    Otherwise, V2 client cannot establish TLS session with Cloudflare as CDN servers does not have a howareyou vhost.

  3. Vmess encryption must be enabled besides TLS, thus Cloudflare cannot decrypt Vmess payload.

    You'd better not use V2 do confidential communication such as Email, Login etc.


  1. Bai hua wen
  2. V2Ray的Websocket模式使用CDN加速并隐藏VPS真实IP