使用 Nginx 搭建 HTTPS 正向代理服务
NGINX 搭建 HTTP 正向代理
搭建一个代理服务器,要求当请求的请求头中包含dest_ip时,就将请求转发到这个目的地址,否则就正常请求。当自己用下面这种方式很快就实现 HTTP 正向代理,使用时,却发现这种配置无法正常代理 HTTPS 请求。
location / {
if ($http_dest_ip != "") {
proxy_pass http://$http_dest_ip/$request_uri;
}
proxy_pass https://$http_host$request_uri;
}
NGINX 代理 HTTPS 请求时 access 日志:
CONNECT acs.m.taobao.com:443 HTTP/1.1" 400 179 "-" "-" "-"
CONNECT acs.m.taobao.com:443 HTTP/1.1" 400 179 "-" "-" "-"
CONNECT acs.m.taobao.com:443 HTTP/1.1" 400 179 "-" "-" "-"
NGINX 的error 日志:
79953#1783043: *16 client sent invalid request while reading client request line, client: 192.168.73.26, server: localhost, request: "CONNECT gw.alicdn.com:443 HTTP/1.1"
79953#1783043: *16 client sent invalid request while reading client request line, client: 192.168.73.26, server: localhost, request: "CONNECT gw.alicdn.com:443 HTTP/1.1"
79953#1783043: *18 client sent invalid request while reading client request line, client: 192.168.73.26, server: localhost, request: "CONNECT acs.m.taobao.com:443 HTTP/1.1"
为什么 NGINX 不能做 HTTPS 正向代理服务器
HTTPS 现在已经被大范围的使用在网络数据安全传输领域,基于 HTTPS 的浏览器和服务器之间通信都是被加密的。所以,当浏览器通过代理发送一个 HTTPS 请求时,请求的地址和端口也是被加密的,代理服务器也无法知道这些信息。那么代理是如何知道请求是发到哪里呢?为了解决这个问题,浏览器会先发送一个明文的 HTTP 协议的 CONNECT 请求给代理服务器,告诉代理请求的目的地址和端口。CONNECT 请求的内容格式如下:
CONNECT ***:443 HTTP/1.1
Host: bayden.com:443
Connection: keep-alive
User-Agent: Chrome/47.0.2526.58
收到这个请求后,代理会和目标服务器建立一个 TCP 连接,并返回一个 HTTP 200 的响应给浏览器,告诉浏览器自己和目标服务器的 TCP 连接已建立。响应格式如下:
HTTP/1.1 200 Connection Established
Connection: close
之后,代理只会透明的来回传输浏览器和服务器之间经过 SSL 加密的数据包,并不知道也不需要知道传输的实际内容,直接通道关闭。
出现以上异常的具体原因是 NGINX 本身的设计就是作为一个反向代理服务器,而非正向代理服务器,并且在短期也没有打算支持正向代理,所以现在 NGINX 并不支持 CONNECT 请求方式,因此收到“CONNECT ***:443 HTTP/1.1”请求时会报“client sent invalid request while reading client request line”异常。这种情况并不是说 NGINX 无法处理 SSL,只是作为一个 forward proxy 不行。
安装扩展模块
那如何让 NGINX 可以正向代理 HTTPS 请求呢?我们需要借助一个第三方扩展模块 ngx_http_proxy_connect_module 来让 NGINX 支持 CONNECT 请求,建立一个 SSL 请求的通道。
ngx_http_proxy_connect_module 安装方式:
$ wget http://Nginx.org/download/Nginx-1.9.2.tar.gz
$ tar -xzvf Nginx-1.9.2.tar.gz
$ cd Nginx-1.9.2/
$ patch -p1 < /path/to/ngx_http_proxy_connect_module/patch/proxy_connect.patch
$ ./configure --add-module=/path/to/ngx_http_proxy_connect_module
$ make && make install
其中 “/path/to” 为 proxy_connect.patch 文件在服务器的存放地址。需要注意的是,对于使用 Mac 的同学,我目前还没有找到使用 brew install nginx 的方式安装 ngx_http_proxy_connect_module 扩展的方法。
编译安装完 ngx_http_proxy_connect_module 扩展模块后,使用如下配置即可以使 NGINX 正常代理 HTTPS 请求。
NGINX HTTPS 代理完整配置:
http {
...
resolver 8.8.8.8; # DNS 服务器可根据实际情况单独配置
...
server {
listen 80;
server_name proxy_server;
...
proxy_connect;
proxy_connect_allow all;
proxy_connect_connect_timeout 10s;
proxy_connect_read_timeout 10s;
proxy_connect_send_timeout 10s;
location / {
proxy_pass http://$host;
proxy_set_header Host $host;
}
NGINX proxy for docker
当然,如果你会使用 docker,那么可以直接使用已经编译了 ngx_http_proxy_connect_module 模块的 NGINX 镜像 Nginx forward proxy 快速搭建一个 HTTPS正向代理服务器。
wget http://software.yangyijing.cn/software/ngx_http_proxy_connect_module.tar.gz
Dockerfile
FROM alpine:3.9
ENV NGINX_VERSION 1.15.12
# https://github.com/chobits/ngx_http_proxy_connect_module下载的主分支包
# wget http://software.yangyijing.cn/software/ngx_http_proxy_connect_module.tar.gz
ADD ngx_http_proxy_connect_module.tar.gz /opt/
RUN GPG_KEYS=B0F4253373F8F6F510D42178520A9993A1C052F8 \
&& CONFIG="\
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--modules-path=/usr/lib/nginx/modules \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
--user=nginx \
--group=nginx \
--with-http_ssl_module \
--with-http_realip_module \
--with-http_addition_module \
--with-http_sub_module \
--with-http_dav_module \
--with-http_flv_module \
--with-http_mp4_module \
--with-http_gunzip_module \
--with-http_gzip_static_module \
--with-http_random_index_module \
--with-http_secure_link_module \
--with-http_stub_status_module \
--with-http_auth_request_module \
--with-http_xslt_module=dynamic \
--with-http_image_filter_module=dynamic \
--with-http_geoip_module=dynamic \
--with-threads \
--with-stream \
--with-stream_ssl_module \
--with-stream_ssl_preread_module \
--with-stream_realip_module \
--with-stream_geoip_module=dynamic \
--with-http_slice_module \
--with-mail \
--with-mail_ssl_module \
--with-compat \
--with-file-aio \
--with-http_v2_module \
# 对应上面添加的模块目录
--add-module=/opt/ngx_http_proxy_connect_module \
" \
&& addgroup -S nginx \
&& adduser -D -S -h /var/cache/nginx -s /sbin/nologin -G nginx nginx \
&& apk add --no-cache --virtual .build-deps \
gcc \
libc-dev \
make \
openssl-dev \
pcre-dev \
zlib-dev \
linux-headers \
curl \
gnupg1 \
libxslt-dev \
gd-dev \
geoip-dev \
# 编译ngx_http_proxy_connect_module依赖的
patch \
pcre \
zlib \
&& curl -fSL https://nginx.org/download/nginx-$NGINX_VERSION.tar.gz -o nginx.tar.gz \
&& curl -fSL https://nginx.org/download/nginx-$NGINX_VERSION.tar.gz.asc -o nginx.tar.gz.asc \
&& export GNUPGHOME="$(mktemp -d)" \
&& found=''; \
for server in \
ha.pool.sks-keyservers.net \
hkp://keyserver.ubuntu.com:80 \
hkp://p80.pool.sks-keyservers.net:80 \
pgp.mit.edu \
; do \
echo "Fetching GPG key $GPG_KEYS from $server"; \
gpg --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$GPG_KEYS" && found=yes && break; \
done; \
test -z "$found" && echo >&2 "error: failed to fetch GPG key $GPG_KEYS" && exit 1; \
gpg --batch --verify nginx.tar.gz.asc nginx.tar.gz \
&& rm -rf "$GNUPGHOME" nginx.tar.gz.asc \
&& mkdir -p /usr/src \
&& tar -zxC /usr/src -f nginx.tar.gz \
&& rm nginx.tar.gz \
&& cd /usr/src/nginx-$NGINX_VERSION \
# 对应版本的patch文件
&& patch -p1 < /opt/ngx_http_proxy_connect_module/patch/proxy_connect_rewrite_101504.patch \
&& ./configure $CONFIG --with-debug \
&& make -j$(getconf _NPROCESSORS_ONLN) \
&& mv objs/nginx objs/nginx-debug \
&& mv objs/ngx_http_xslt_filter_module.so objs/ngx_http_xslt_filter_module-debug.so \
&& mv objs/ngx_http_image_filter_module.so objs/ngx_http_image_filter_module-debug.so \
&& mv objs/ngx_http_geoip_module.so objs/ngx_http_geoip_module-debug.so \
&& mv objs/ngx_stream_geoip_module.so objs/ngx_stream_geoip_module-debug.so \
&& ./configure $CONFIG \
&& make -j$(getconf _NPROCESSORS_ONLN) \
&& make install \
&& rm -rf /etc/nginx/html/ \
&& mkdir /etc/nginx/conf.d/ \
&& mkdir -p /usr/share/nginx/html/ \
&& install -m644 html/index.html /usr/share/nginx/html/ \
&& install -m644 html/50x.html /usr/share/nginx/html/ \
&& install -m755 objs/nginx-debug /usr/sbin/nginx-debug \
&& install -m755 objs/ngx_http_xslt_filter_module-debug.so /usr/lib/nginx/modules/ngx_http_xslt_filter_module-debug.so \
&& install -m755 objs/ngx_http_image_filter_module-debug.so /usr/lib/nginx/modules/ngx_http_image_filter_module-debug.so \
&& install -m755 objs/ngx_http_geoip_module-debug.so /usr/lib/nginx/modules/ngx_http_geoip_module-debug.so \
&& install -m755 objs/ngx_stream_geoip_module-debug.so /usr/lib/nginx/modules/ngx_stream_geoip_module-debug.so \
&& ln -s ../../usr/lib/nginx/modules /etc/nginx/modules \
&& strip /usr/sbin/nginx* \
&& strip /usr/lib/nginx/modules/*.so \
&& rm -rf /usr/src/nginx-$NGINX_VERSION \
\
# Bring in gettext so we can get `envsubst`, then throw
# the rest away. To do this, we need to install `gettext`
# then move `envsubst` out of the way so `gettext` can
# be deleted completely, then move `envsubst` back.
&& apk add --no-cache --virtual .gettext gettext \
&& mv /usr/bin/envsubst /tmp/ \
\
&& runDeps="$( \
scanelf --needed --nobanner --format '%n#p' /usr/sbin/nginx /usr/lib/nginx/modules/*.so /tmp/envsubst \
| tr ',' '\n' \
| sort -u \
| awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \
)" \
&& apk add --no-cache --virtual .nginx-rundeps $runDeps \
&& apk del .build-deps \
&& apk del .gettext \
&& mv /tmp/envsubst /usr/local/bin/ \
\
# Bring in tzdata so users could set the timezones through the environment
# variables
&& apk add --no-cache tzdata \
\
# forward request and error logs to docker log collector
&& ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log
EXPOSE 80
STOPSIGNAL SIGTERM
CMD ["nginx", "-g", "daemon off;"]
nginx配置文件:
resolver 8.8.8.8;
server {
listen 80;
server_name 185.184.223.120;
proxy_connect;
proxy_connect_allow all;
proxy_connect_connect_timeout 600s;
proxy_connect_read_timeout 600s;
proxy_connect_send_timeout 600s;
location / {
proxy_pass http://$host;
proxy_set_header Host $host;
}
}