使用 Nginx 穿透代理HTTPS的碎碎念

有的时候,我们希望使用Nginx来分发多个站点的HTTPS请求,但又希望HTTPS证书只部署在源站上。这个时候,我们会考虑使用 Nginx 的穿透代理

stream {
  server{
    listen 443;
    proxy_pass 10.0.1.5:443;
    proxy_connect_timeout 15s;
    proxy_timeout 30s;
    proxy_next_upstream_timeout 120s;
  }
}

但大多数时候,我们需要分发就意味着不止是1个后端服务,这个时候,我们就需要通过解析SNI获取域名来判断需要转发的目标服务,因此我们开启了 ssl_preread 。

stream {
  map_hash_bucket_size 64;

  map $ssl_preread_server_name $backend_pool {
    service_a.com backend_a;
    service_b.com backend_b;
    default backend_a;
  }

  upstream backend_a {
    server 10.0.1.5:443;
  }
  upstream backend_b {
    server 10.0.1.6:443;
  }

  server{
    listen 443;
    ssl_preread on;
    proxy_pass $backend_pool;
    proxy_connect_timeout 15s;
    proxy_timeout 30s;
    proxy_next_upstream_timeout 120s;
  }
}

当然,想要物尽其用的我们,在这一台用于转发服务的Nginx上,也打算启动 HTTPS 服务

如果我们这么配置

http {
  server {
    listen 443 ssl;
    server_name service_local.com;
    ssl_certificate /etc/nginx/certs/ssl.cert;
    ssl_certificate_key /etc/nginx/certs/ssl.key;

    ....
  }
}

这个时候就会发现,我们刚才的 stream server 已经占用了 443 端口 会产生报错

nginx: [emerg] bind() to 0.0.0.0:443 failed (98: Address already in use)

因此我们需要做一些调整,将本地的 https 绑定到未被占用的端口,再由前面的转发服务根据域名进行转发

http {
  server {
    listen 127.0.0.1:8443 ssl;
    ...
  }
}

stream {
  map $ssl_preread_server_name $backend_pool {
    service_a.com backend_a;
    service_b.com backend_b;
    service_local.com backend_local;
    default backend_a;
  }

  upstream backend_local {
    server 127.0.0.1:8443;
  }
  ...
}

是的 看起来 一切都很正常 但这个时候 如果我们通过上游服务器日志观察请求的话 会发现所有的请求客户端都是中转服务的IP 无法获得实际用户的IP

同时,由于我们是4层代理,并不能像http代理一样,通过修改HTTP头的方式来传递信息。

此时存在两个方案:

1. 通过 IP Transparent 修改3层中的源IP地址
L4(传输层)IP透明反向代理的实现(传递客户端真实IP)

在 nginx 中可以通过 proxy_bind 来实现

proxy_bind $remote_addr transparent;

这种方式对于上游来说,是没有感知正常处理的,但是因为对3层包进行了修改,为了能够正确处理数据流向,则需要配合进行路由配置

2. 使用 proxy protocol 协议

这是随着负载均衡类服务大量运用而产生的专门为了交换代理的信息的协议,最初由HAProxy提出。

要使用 proxy protocol 协议,需要上下游同时支持该协议。

目前主流的云服务商提供的 负载均衡 服务大多都支持该协议

如果你的上游是自行基于 Nginx 部署的话,那么可以通过 proxy_protocol 来开启

http {
    #...
    server {
        listen 80   proxy_protocol;
        listen 443  ssl proxy_protocol;
        #...
    }
}
   
stream {
    #...
    server {
        listen 4433 proxy_protocol;
        #...
    }
}

同时 proxy_protocol 可以配合 Real‑IP modules 来使用

server {
  ...
  set_real_ip_from 10.0.1.0/24;
  ...
}
http {
  server {
    ...
    real_ip_header proxy_protocol;
  }
}

在上游开启了 proxy_protocol 支持后 在进行分发的服务上配上 proxy_protocol on; 就可以了

stream {
  map_hash_bucket_size 64;

  map $ssl_preread_server_name $backend_pool {
    service_a.com backend_a;
    service_b.com backend_b;
    default backend_a;
  }

  upstream backend_a {
    server 10.0.1.5:443;
  }
  upstream backend_b {
    server 10.0.1.6:443;
  }

  server{
    listen 443;
    ssl_preread on;
    proxy_pass $backend_pool;
    proxy_connect_timeout 15s;
    proxy_timeout 30s;
    proxy_next_upstream_timeout 120s;
    proxy_protocol on;
  }
}

使用 proxy protocol 之所以是一个协议 那要求的就是请求和接受的双方都认识这个协议

对于转发 HTTP 来说,如果只有一方开启,一方未开启,我们能够直观的通过请求异常来发现问题

对于4层转发 HTTPS 来说,这个问题则会显得隐蔽。当我们在代理服务上开启了 proxy_protocol ,但上游不支持该协议时:

我们会发现,使用对应的SNI来访问上游不支持的业务时,由显得很隐蔽

Chrome 会提示 该网站不能提供安全的连接 ERR_SSL_PROTOCOL_ERROR

Firefox 会提示 SSL_ERROR_RX_RECORD_TOO_LONG

如果直接使用 openssl 检查则会看到 error:1408F10B:SSL routines:ssl3_get_record:wrong version number

而这些信息充满了迷惑性,只能得出 Nginx 的SSL配置不正确的结论,同时,如果你直接使用 openssl 检查上游时,openssl 是能够正常连接的,毕竟上游服务器的证书配置的并没有问题,只是没有开启 proxy_protocol 支持而已。

因此,在转发需要配合 proxy_protocol 使用的时候,一定需要先确认上游是否开启了 proxy_protocol 支持。

Vagrant下共享目录静态文件(js/jpg/png等)“缓存”问题

之前提到说通过Vagrant部署开发环境,使用目录共享模式,在本地磁盘进行开发,而通过虚拟机环境运行开发的页面。

是的,一切看起来都是那么的顺利,首先基于VirtualBox安装了Vagrant,接下来,按照以往部署环境的习惯,在VM中安装了nginx作为开发运行环境,并且将本地的共享目录作为nginx的web目录,然后打开页面,看上去似乎都很正常,但接下来,你发现了一个神奇的事情,你修改替换了一个css,一张图片,然后刷新浏览器,发现什么都没有变,然后你有非常猛烈、使劲的F5,依旧还是没有改变,是的,你看看编辑器,似乎替换是正常的,在看看VM上的文件,也都是对的,是的,尝试重启nginx,依旧没有任何变化,你开始怀疑php5-fpm甚至于毫不相干的memcached和mysql,但都无济于事。也不知道是什么让这些文件被“缓存”了呢。

当你尝试修改一个js,并且用同样的方法更新之后,会遇到类似的问题,是的,就算重启VM上任何服务,甚至重启VM,依旧没有用,当然,比起其他资源文件,浏览器的反应会强烈一些,因为浏览器会提示未知错误,而你通过浏览器查看你修改的JS文件,会看到文件尾巴有下面奇怪的随机字符:

�����������������

这到底是什么东西呢?编码错误?缓存异常?又或是其他什么?

是的,你尝试花费很多时间,试验各种各样的方法去解决这个问题,其实对于nginx来说,你只需要修改配置文件(nginx.conf)中的一行重启就能简单的解决这个问题:

sendfile off;

找到 nginx.conf ,把里面的 “sendfile on” 修改为 “sendfile off”。

当然,如果你使用Apache也可能遇到类似的问题,那么同样也有类似的配置需要修改:

EnableSendfile off

关于这个问题的参照:
https://github.com/mitchellh/vagrant/issues/351#issuecomment-1339640

http://stackoverflow.com/questions/9479117/vagrant-virtualbox-apache2-strange-cache-behaviour

IXWebhosting 的速度优化

相信很多朋友和我一样,当初选择海外主机的时候“不慎”选择了IXWebhosting。当然,其实IX的价格并不是很有优势,选择的原因一是当时Godaddy还未支持支付宝,而且IX的独立IP还是很让人眼红的。
IX的服务器其实并不差,性能测试来看相当好,带宽也很足,我在VPS下载IX上的数据可以达到8M/s的速度。但是他的缺点就是对于中国的速度并不快,两方面原因,一来是其机房在美国中部,物理距离造成不可避免的延时,再者就是其走到中国电信的线路的是传说中抽风大户Level 3(只是部分IP,我也碰到不是走L3的IP)。所以说用IX作服务于国外用户的英文站或是外贸站还是非常好的,但是如果要服务于天朝用户就有些吃力了。
于是当时既然买了(因为几个哥们合买的,他们觉得凑合啊),还是要用的,怎么用呢,首先,IX服务器性能还是很好的,跑站是非常流畅的,我将他作为后台支撑,然后在Las Vegas用Nginx + proxy_cache构建CDN,这样虽然等于放弃了IX的优势,独立IP,但也不用使用IX的共享IP,同样是自己几个站公用IP,只要自己不出问题就不怕墙,CDN本身对静态内容(伪静态也算)又具有缓存作用,减少原空间CPU负担,而主机走的线路(目前来说)电信是PCCW,虽然不比Peer1的电信直连,但总体速度稳定性上还是相当不错的。
顺便目前有一些闲置资源,你可以通过顶部AboutMe联系我咨询。