Nginx正向代理nginx正向代理https
使用 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 日志:
192.168.73.26 - - [06/Dec/2018:19:42:27 +0800] "CONNECT acs.m.taobao.com:443 HTTP/1.1" 400 179 "-" "-" "-" 192.168.73.26 - - [06/Dec/2018:19:42:27 +0800] "CONNECT acs.m.taobao.com:443 HTTP/1.1" 400 179 "-" "-" "-" 192.168.73.26 - - [06/Dec/2018:19:42:27 +0800] "CONNECT acs.m.taobao.com:443 HTTP/1.1" 400 179 "-" "-" "-"NGINX 的error 日志:
2018/12/06 19:42:27 [info] 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" 2018/12/06 19:42:27 [info] 79953#1783043: *17 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" 2018/12/06 19:42:27 [info] 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: :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.gzDockerfile
FROM alpine:3.9 ENV NGINX_VERSION 1.15.12 # https:///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 \ \ hkp://keyserver.ubuntu.com:80 \ hkp://: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 \ && mv objs/ngx_http_image_filter_module.so objs/ngx_http_image_filter_module \ && mv objs/ngx_http_geoip_module.so objs/ngx_http_geoip_module \ && mv objs/ngx_stream_geoip_module.so objs/ngx_stream_geoip_module \ && ./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 /usr/lib/nginx/modules/ngx_http_xslt_filter_module \ && install -m755 objs/ngx_http_image_filter_module /usr/lib/nginx/modules/ngx_http_image_filter_module \ && install -m755 objs/ngx_http_geoip_module /usr/lib/nginx/modules/ngx_http_geoip_module \ && install -m755 objs/ngx_stream_geoip_module /usr/lib/nginx/modules/ngx_stream_geoip_module \ && 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; } }