购买海外VPS部署v2ray服务

公司之前翻墙一直是购买的梯子,由于泄密原因,决定自己购买海外VPS来部署v2ray服务,在此记录下整个过程。

服务提供商

网上用的比较多的是Vultr搬瓦工

Vultr稍微优惠点,而且如果ip被墙了,可以直接销毁vps,重新创建,就可以获得新的ip,相当于免费换ip,搬瓦工换ip是收费的,折算成人民币大概是五十多块。

所以在此使用的是Vultr的服务,直接注册账号,充值之后选择对应配置,开通就行(这里有个坑,如果使用支付宝支付的话,国家选择中国,网络不可以使用代理,不然会报错,支付不了)

流量伪装介绍

直接使用tcp配置就相当于裸奔,估计没一会就得被墙,本文介绍的V2ray伪装便是将穿墙流量以常见的HTTPS/TLS包装,大大降低vps被墙或被干扰的可能性,在敏感时期提供稳如狗的上外网体验。

关于伪装技术的选择,V2ray web+websocket+tlsV2ray web+http2+tls 常用来做对比。理论上http2省去了upgrade的请求,性能更好。但实际使用中两者没有明显区别,加之某些web服务器(例如Nginx)不支持后端服务器为http2,所以websocket的方式更流行。如果你要上http2,记得web服务器不能用Nginx,要用支持反代http2的Caddy等软件。

理论上来说,证书不是必须的。但没有tls加持或不做加密,防火墙直接能看出来流量真实意图从而进行干扰,这也是为什么不建议伪装http流量的原因。本文给出的方法采用合法机构签发的证书对流量进行加密,不是做特征混淆得到的TLS流量,从而更难被检测和干扰。

下文介绍流量伪装的配置步骤,演示域名为tlanyan.pp.ua,服务器为Linux(CentOS),web服务器软件用Caddy,web+websocket+tls组合,最终效果为:http/https方式打开域名,显示正常的网页;V2Ray客户端请求特定的路径,例如https://itlanyan.com/awesomepath,能科学上网;浏览器直接请求https://itlanyan.com/awesomepath,返回”400 bad request”。即外部看起来完全是一个人畜无害的正规网站,特定手段请求特定网址才是科学上网的通道。

服务部署准备

域名

一个域名,无备案要求,可以在DigitalPlat平台注册一个免费的二级域名,可以解析,但是不能转让,可以免费续期。

cloudflare

在DigitalPlat注册的免费域名不可以直接在上面添加解析,需要托管在cloudflare平台。

在cloudflare添加一个域,选择免费的计划即可,会生成两个ns记录,添加到DigitalPlat平台申请的免费域名里即可。

生效后状态为活动:

V2ray服务部署

登录vps服务器,初始化环境:

1
2
3
4
5
systemctl stop firewalld
systemctl disable firewalld

setenforce 0 #临时
修改/etc/selinux/config文件SELINUX=disabled ##永久

配置安全组

安全起见,登录Vultr平台添加安全组,只放开22,80,443端口。

安装v2ray服务:

1
bash <(wget -qO- -o- https://git.io/v2ray.sh)

或者自己创建一个v2ray.sh脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
#!/bin/bash

author=233boy
# github=https://github.com/233boy/v2ray

# bash fonts colors
red='\e[31m'
yellow='\e[33m'
gray='\e[90m'
green='\e[92m'
blue='\e[94m'
magenta='\e[95m'
cyan='\e[96m'
none='\e[0m'
_red() { echo -e ${red}$@${none}; }
_blue() { echo -e ${blue}$@${none}; }
_cyan() { echo -e ${cyan}$@${none}; }
_green() { echo -e ${green}$@${none}; }
_yellow() { echo -e ${yellow}$@${none}; }
_magenta() { echo -e ${magenta}$@${none}; }
_red_bg() { echo -e "\e[41m$@${none}"; }

is_err=$(_red_bg 错误!)
is_warn=$(_red_bg 警告!)

err() {
echo -e "\n$is_err $@\n" && exit 1
}

warn() {
echo -e "\n$is_warn $@\n"
}

# root
[[ $EUID != 0 ]] && err "当前非 ${yellow}ROOT用户.${none}"

# yum or apt-get, ubuntu/debian/centos
cmd=$(type -P apt-get || type -P yum)
[[ ! $cmd ]] && err "此脚本仅支持 ${yellow}(Ubuntu or Debian or CentOS)${none}."

# systemd
[[ ! $(type -P systemctl) ]] && {
err "此系统缺少 ${yellow}(systemctl)${none}, 请尝试执行:${yellow} ${cmd} update -y;${cmd} install systemd -y ${none}来修复此错误."
}

# wget installed or none
is_wget=$(type -P wget)

# x64
case $(uname -m) in
amd64 | x86_64)
is_jq_arch=amd64
is_core_arch="64"
;;
*aarch64* | *armv8*)
is_jq_arch=arm64
is_core_arch="arm64-v8a"
;;
*)
err "此脚本仅支持 64 位系统..."
;;
esac

is_core=v2ray
is_core_name=V2Ray
is_core_dir=/etc/$is_core
is_core_bin=$is_core_dir/bin/$is_core
is_core_repo=v2fly/$is_core-core
is_conf_dir=$is_core_dir/conf
is_log_dir=/var/log/$is_core
is_sh_bin=/usr/local/bin/$is_core
is_sh_dir=$is_core_dir/sh
is_sh_repo=$author/$is_core
is_pkg="wget unzip"
is_config_json=$is_core_dir/config.json
tmp_var_lists=(
tmpcore
tmpsh
tmpjq
is_core_ok
is_sh_ok
is_jq_ok
is_pkg_ok
)

# tmp dir
tmpdir=$(mktemp -u)
[[ ! $tmpdir ]] && {
tmpdir=/tmp/tmp-$RANDOM
}

# set up var
for i in ${tmp_var_lists[*]}; do
export $i=$tmpdir/$i
done

# load bash script.
load() {
. $is_sh_dir/src/$1
}

# wget add --no-check-certificate
_wget() {
[[ $proxy ]] && export https_proxy=$proxy
wget --no-check-certificate $*
}

# print a mesage
msg() {
case $1 in
warn)
local color=$yellow
;;
err)
local color=$red
;;
ok)
local color=$green
;;
esac

echo -e "${color}$(date +'%T')${none}) ${2}"
}

# show help msg
show_help() {
echo -e "Usage: $0 [-f xxx | -l | -p xxx | -v xxx | -h]"
echo -e " -f, --core-file <path> 自定义 $is_core_name 文件路径, e.g., -f /root/${is_core}-linux-64.zip"
echo -e " -l, --local-install 本地获取安装脚本, 使用当前目录"
echo -e " -p, --proxy <addr> 使用代理下载, e.g., -p http://127.0.0.1:2333"
echo -e " -v, --core-version <ver> 自定义 $is_core_name 版本, e.g., -v v5.4.1"
echo -e " -h, --help 显示此帮助界面\n"

exit 0
}

# install dependent pkg
install_pkg() {
cmd_not_found=
for i in $*; do
[[ ! $(type -P $i) ]] && cmd_not_found="$cmd_not_found,$i"
done
if [[ $cmd_not_found ]]; then
pkg=$(echo $cmd_not_found | sed 's/,/ /g')
msg warn "安装依赖包 >${pkg}"
$cmd install -y $pkg &>/dev/null
if [[ $? != 0 ]]; then
[[ $cmd =~ yum ]] && yum install epel-release -y &>/dev/null
$cmd update -y &>/dev/null
$cmd install -y $pkg &>/dev/null
[[ $? == 0 ]] && >$is_pkg_ok
else
>$is_pkg_ok
fi
else
>$is_pkg_ok
fi
}

# download file
download() {
case $1 in
core)
link=https://github.com/${is_core_repo}/releases/latest/download/${is_core}-linux-${is_core_arch}.zip
[[ $is_core_ver ]] && link="https://github.com/${is_core_repo}/releases/download/${is_core_ver}/${is_core}-linux-${is_core_arch}.zip"
name=$is_core_name
tmpfile=$tmpcore
is_ok=$is_core_ok
;;
sh)
link=https://github.com/${is_sh_repo}/releases/latest/download/code.zip
name="$is_core_name 脚本"
tmpfile=$tmpsh
is_ok=$is_sh_ok
;;
jq)
link=https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-$is_jq_arch
name="jq"
tmpfile=$tmpjq
is_ok=$is_jq_ok
;;
esac

msg warn "下载 ${name} > ${link}"
if _wget -t 3 -q -c $link -O $tmpfile; then
mv -f $tmpfile $is_ok
fi
}

# get server ip
get_ip() {
export "$(_wget -4 -qO- https://one.one.one.one/cdn-cgi/trace | grep ip=)" &>/dev/null
[[ -z $ip ]] && export "$(_wget -6 -qO- https://one.one.one.one/cdn-cgi/trace | grep ip=)" &>/dev/null
}

# check background tasks status
check_status() {
# dependent pkg install fail
[[ ! -f $is_pkg_ok ]] && {
msg err "安装依赖包失败"
msg err "请尝试手动安装依赖包: $cmd update -y; $cmd install -y $pkg"
is_fail=1
}

# download file status
if [[ $is_wget ]]; then
[[ ! -f $is_core_ok ]] && {
msg err "下载 ${is_core_name} 失败"
is_fail=1
}
[[ ! -f $is_sh_ok ]] && {
msg err "下载 ${is_core_name} 脚本失败"
is_fail=1
}
[[ ! -f $is_jq_ok ]] && {
msg err "下载 jq 失败"
is_fail=1
}
else
[[ ! $is_fail ]] && {
is_wget=1
[[ ! $is_core_file ]] && download core &
[[ ! $local_install ]] && download sh &
[[ $jq_not_found ]] && download jq &
get_ip
wait
check_status
}
fi

# found fail status, remove tmp dir and exit.
[[ $is_fail ]] && {
exit_and_del_tmpdir
}
}

# parameters check
pass_args() {
while [[ $# -gt 0 ]]; do
case $1 in
online)
err "如果想要安装旧版本, 请转到: https://github.com/233boy/v2ray/tree/old"
;;
-f | --core-file)
[[ -z $2 ]] && {
err "($1) 缺少必需参数, 正确使用示例: [$1 /root/$is_core-linux-64.zip]"
} || [[ ! -f $2 ]] && {
err "($2) 不是一个常规的文件."
}
is_core_file=$2
shift 2
;;
-l | --local-install)
[[ ! -f ${PWD}/src/core.sh || ! -f ${PWD}/$is_core.sh ]] && {
err "当前目录 (${PWD}) 非完整的脚本目录."
}
local_install=1
shift 1
;;
-p | --proxy)
[[ -z $2 ]] && {
err "($1) 缺少必需参数, 正确使用示例: [$1 http://127.0.0.1:2333 or -p socks5://127.0.0.1:2333]"
}
proxy=$2
shift 2
;;
-v | --core-version)
[[ -z $2 ]] && {
err "($1) 缺少必需参数, 正确使用示例: [$1 v1.8.1]"
}
is_core_ver=v${2#v}
shift 2
;;
-h | --help)
show_help
;;
*)
echo -e "\n${is_err} ($@) 为未知参数...\n"
show_help
;;
esac
done
[[ $is_core_ver && $is_core_file ]] && {
err "无法同时自定义 ${is_core_name} 版本和 ${is_core_name} 文件."
}
}

# exit and remove tmpdir
exit_and_del_tmpdir() {
rm -rf $tmpdir
[[ ! $1 ]] && {
msg err "哦豁.."
msg err "安装过程出现错误..."
echo -e "反馈问题) https://github.com/${is_sh_repo}/issues"
echo
exit 1
}
exit
}

# main
main() {

# check old version
[[ -f $is_sh_bin && -d $is_core_dir/bin && -d $is_sh_dir && -d $is_conf_dir ]] && {
err "检测到脚本已安装, 如需重装请使用${green} ${is_core} reinstall ${none}命令."
}

# check parameters
[[ $# -gt 0 ]] && pass_args $@

# show welcome msg
clear
echo
echo "........... $is_core_name script by $author .........."
echo

# start installing...
msg warn "开始安装..."
[[ $is_core_ver ]] && msg warn "${is_core_name} 版本: ${yellow}$is_core_ver${none}"
[[ $proxy ]] && msg warn "使用代理: ${yellow}$proxy${none}"
# create tmpdir
mkdir -p $tmpdir
# if is_core_file, copy file
[[ $is_core_file ]] && {
cp -f $is_core_file $is_core_ok
msg warn "${yellow}${is_core_name} 文件使用 > $is_core_file${none}"
}
# local dir install sh script
[[ $local_install ]] && {
>$is_sh_ok
msg warn "${yellow}本地获取安装脚本 > $PWD ${none}"
}

timedatectl set-ntp true &>/dev/null
[[ $? != 0 ]] && {
msg warn "${yellow}\e[4m提醒!!! 无法设置自动同步时间, 可能会影响使用 VMess 协议.${none}"
}

# install dependent pkg
install_pkg $is_pkg &

# jq
if [[ $(type -P jq) ]]; then
>$is_jq_ok
else
jq_not_found=1
fi
# if wget installed. download core, sh, jq, get ip
[[ $is_wget ]] && {
[[ ! $is_core_file ]] && download core &
[[ ! $local_install ]] && download sh &
[[ $jq_not_found ]] && download jq &
get_ip
}

# waiting for background tasks is done
wait

# check background tasks status
check_status

# test $is_core_file
if [[ $is_core_file ]]; then
unzip -qo $is_core_ok -d $tmpdir/testzip &>/dev/null
[[ $? != 0 ]] && {
msg err "${is_core_name} 文件无法通过测试."
exit_and_del_tmpdir
}
for i in ${is_core} geoip.dat geosite.dat; do
[[ ! -f $tmpdir/testzip/$i ]] && is_file_err=1 && break
done
[[ $is_file_err ]] && {
msg err "${is_core_name} 文件无法通过测试."
exit_and_del_tmpdir
}
fi

# get server ip.
[[ ! $ip ]] && {
msg err "获取服务器 IP 失败."
exit_and_del_tmpdir
}

# create sh dir...
mkdir -p $is_sh_dir

# copy sh file or unzip sh zip file.
if [[ $local_install ]]; then
cp -rf $PWD/* $is_sh_dir
else
unzip -qo $is_sh_ok -d $is_sh_dir
fi

# create core bin dir
mkdir -p $is_core_dir/bin
# copy core file or unzip core zip file
if [[ $is_core_file ]]; then
cp -rf $tmpdir/testzip/* $is_core_dir/bin
else
unzip -qo $is_core_ok -d $is_core_dir/bin
fi

# add alias
echo "alias $is_core=$is_sh_bin" >>/root/.bashrc

# core command
ln -sf $is_sh_dir/$is_core.sh $is_sh_bin

# jq
[[ $jq_not_found ]] && mv -f $is_jq_ok /usr/bin/jq

# chmod
chmod +x $is_core_bin $is_sh_bin /usr/bin/jq

# create log dir
mkdir -p $is_log_dir

# show a tips msg
msg ok "生成配置文件..."

# create systemd service
load systemd.sh
is_new_install=1
install_service $is_core &>/dev/null

# create condf dir
mkdir -p $is_conf_dir

load core.sh
# create a tcp config
add tcp
# remove tmp dir and exit.
exit_and_del_tmpdir ok
}

# start.
main $@

安装完成

执行了上面的安装命令,并且没有错误提示的话,就能看到类似下面的图片

此时可以复制 URL 到相关软件 (例如 v2rayN) 去测试一下是否正常使用,记得安全组要放开对应端口,但是这个是没有伪装的,很容易被墙,所以我们不用这个,继续下面的步骤。

打开 BBR 优化

1
v2ray bbr

开启流量伪装

1
v2ray add ws

复制链接到客户端测试一下是否正常使用

至此,v2ray服务就部署完成了。

流量伪装+CDN

即使使用了流量伪装,还是可能会被封ip,可以利用 CDN 中转,cloudflare就有免费的,在cloudflare配置域名解析的时候打开代理(小云朵)即可。

这时流量传递的顺序是这样的:

主要实现就是两点:
一、借助 V2Ray 代理,将我们的流量被伪装成网站流量
二、利用 CDN 中转 V2Ray 的 WebSocket 流量

这样,GFW 只知道你与 CDN 之间的联系,不知道 VPS 的实际地址,并且 CDN 会有很多 IP 地址,GFW 也不会随意封这些 IP,毕竟也有很多正规网站在使用。

优点:不怕ip被封,因为CDN的ip是在海外的,即使我们的vps ip已经被封了也可以使用。

缺点:网速会慢很多。

测试ip端口是否被墙

网站1:https://www.toolsdaquan.com/ipcheck/#google_vignette

网站2:https://tcp.ping.pe/

V2ray客户端

下载地址:https://itlanyan.com/v2ray-clients-download/

后续使用

流量伪装方式也不是百分百万无一失,还是有可能被墙,如果ip被墙的话,我们就需要更换ip(重建vps),但是如果重建服务器就需要重新配置v2ray服务,那生成的配置也会变化,每次都得把生成的链接给同事们去替换的话就很麻烦,所以这里提供两种方式,无需客户端修改配置。

方式一:

v2ray服务部署好之后,登录vultr平台打个快照,如果原ip被墙,就使用此快照去创建一个新的服务器,新服务器的v2ray服务配置不会更改,只需要登录到cloudflare平台把域名解析记录修改为新的ip即可。

快照是收费的,0.05美元/G/月。

方式二:

搞个个人订阅,一般情况下 ss, ssr, v2ray 之类的分享链接去掉前缀以后都是一个 base64 的字符串
而且一般我们常见的订阅链接,里面的内容虽然一看都是乱码。但是它其实也是一条或多条分享链接再进行一次 base64 编码后的字符串,比如 ss, ssr, v2ray, clash, qv2ray。

假设分享链接为:

1
vmess://eyJob3N0IjoiamlrZS1henNnMDIuZGRucy1vbmx5Lnh5eiIsInBhdGgiOiIvamlrZSIsInRscyI6IiIsInZlcmlmeV9jZXJ0Ijp0cnVlLCJhZGQiOiJqaWtlLWF6c2cwMi5kZG5zLW9ubHkueHl6IiwicG9ydCI6MzA1MDMsImFpZCI6MiwibmV0Ijoid3MiLCJoZWFkZXJUeXBlIjoibm9uZSIsInYiOiIyIiwidHlwZSI6InZtZXNzIiwicHMiOiJWNC3mlrDliqDlnaHlvq7ova/kupEwMXx2MnJheSIsInJlbWFyayI6IlY0LeaWsOWKoOWdoeW+rui9r+S6kTAxfHYycmF5IiwiaWQiOiJlZDYyMDE2ZS1lM2NhLTM0N2YtOGY0Ni1iYzczMzRmYjliYmYiLCJjbGFzcyI6MX0=

把分享链接去掉前缀保存到一个文件中:

通过使用 base64 -d 对原本的链接进行解密,我们可以获得如下信息

可以看到解码后就是v2ray的节点配置信息。

所以我们可以把分享链接保存到一个文件file1里,对文件进行加密:

1
base64 -w 0 file1

然后把加密后的内容保存到另一个文件file2中并把file2放到一个web服务器上作为订阅链接,在客户端配置这个订阅链接就可以拉取节点配置信息。

如果ip被墙了,在新的服务器上配置了新的v2ray的话,会有新的配置信息:

把新的分享链接进行加密,然后把加密后的内容替换web服务器的file2的内容,然后在客户端更新订阅就可以更新为新的节点配置。

也可以在在线网站OSCHINA对内容进行加解密。

Thank you for your accept. mua!
-------------本文结束感谢您的阅读-------------