user root; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; error_log logs/warn.log warn; pid logs/nginx.pid; events { worker_connections 1024; } http { # DNS 服务器地址 resolver 1.1.1.1 ipv6=off; include mime.types; default_type text/html; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 60; keepalive_requests 1024; server_tokens off; more_clear_headers Server; # 限流配置 limit_req_log_level warn; limit_req_zone $binary_remote_addr zone=reqip:16m rate=100r/s; limit_req zone=reqip burst=200 nodelay; # 代理日志(分隔符 \t) log_format log_proxy '$time_iso8601 $remote_addr $request_time ' '$request_length $bytes_sent ' '$request_method $_proto $proxy_host $request_uri $status ' '$http_user_agent' ; # 普通日志 log_format log_access '$time_iso8601 $remote_addr $request_time ' '$request_method $uri $http_host $status ' '$http_user_agent' ; access_log logs/access.log log_access buffer=64k flush=1s; # 缓冲区配置 #(设置过低某些网站无法访问) proxy_buffer_size 16k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; proxy_send_timeout 10s; # 代理缓存配置 proxy_cache_path cache levels=1:2 keys_zone=my_cache:8m max_size=10g inactive=6h use_temp_path=off; # SSL 双证书 include include/cert.conf; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-CHACHA20-POLY1305:ECDHE+AES128:RSA+AES128:ECDHE+AES256:RSA+AES256:ECDHE+3DES:RSA+3DES; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 5m; proxy_cache my_cache; proxy_http_version 1.1; proxy_ssl_server_name on; underscores_in_headers on; merge_slashes off; # 非内置域名的编码(将 `.` 替换成 `-dot-`) # TODO: 由于 nginx map 不支持字符串替换,暂时先这么实现。。。 map $_rhost $_rhost_enc_ext { volatile; '~^([\w-]+)\.(\w+)$' '$1-dot-$2'; '~^([\w-]+)\.([\w-]+)\.(\w+)$' '$1-dot-$2-dot-$3'; '~^([\w-]+)\.([\w-]+)\.([\w-]+)\.(\w+)$' '$1-dot-$2-dot-$3-dot-$4'; } # 非内置域名的解码(将 `-dot-` 替换成 `.`) # TODO: 效率低而且级数有限,下次改成 lua 实现 map $_ext_src $_ext_dst { volatile; '~^([\w-]+?)-dot-([\w-]+?)-dot-([\w-]+?)-dot-([\w-]+?)-dot-([\w-]+?)-dot-(\w+)$' '$1.$2.$3.$4.$5.$6'; '~^([\w-]+?)-dot-([\w-]+?)-dot-([\w-]+?)-dot-([\w-]+?)-dot-(\w+)$' '$1.$2.$3.$4.$5'; '~^([\w-]+?)-dot-([\w-]+?)-dot-([\w-]+?)-dot-(\w+)$' '$1.$2.$3.$4'; '~^([\w-]+?)-dot-([\w-]+?)-dot-(\w+)$' '$1.$2.$3'; '~^([\w-]+?)-dot-(\w+)$' '$1.$2'; } map $_vhost $_vhost_dec_ext { volatile; #.example.com '~^(?<_ext_src>[^.]+)\.ext\.[\w-]+\.\w+$' $_ext_dst; } # 虚拟域名 -> 真实域名 map $_vhost $_vhost_to_rhost { volatile; hostnames; include include/vhost-rhost.map; } # 真实域名 -> 虚拟域名 map $_rhost $_rhost_to_vhost { volatile; hostnames; include include/rhost-vhost.map; } # TODO: 由于 hostnames 无法获取 * 部分,暂时先这样获取子域 # *.com -> * map $_rhost $_rhost_slice_0 { volatile; '~^(.+?)\.\w+$' $1; } # *.google.com -> * map $_rhost $_rhost_slice_1 { volatile; '~^(.+?)\.(?:[\w-]+\.){1}\w+$' $1; } # *.google.com.hk -> * map $_rhost $_rhost_slice_2 { volatile; '~^(.+?)\.(?:[\w-]+\.){2}\w+$' $1; } # *.wk -> * map $_vhost $_vhost_slice_0 { volatile; #.example.com '~^(.+?)\.\w+\.[\w-]+\.\w+$' $1; } # *.m.wk -> * map $_vhost $_vhost_slice_1 { volatile; #.example.com '~^(.+?)\.(?:[\w-]+\.){1}\w+\.[\w-]+\.\w+$' $1; } ########## # 静态资源站点 server { include include/host-root.conf; root ../www; charset utf-8; add_header cache-control max-age=300; add_header strict-transport-security 'max-age=99999999; includeSubDomains; preload'; # 需要编译 ngx_brotli 模块(参考 setup.sh) brotli_static on; } # 内置站点代理 # 格式为 https://[sub.]alias.example.com/path/to/?query server { include include/host-wild.conf; location / { set $_vhost $host; set $_site $_vhost_to_rhost; if ($_site = '') { return 404 "unknown site"; } include proc-hdr.conf; # 非 JS 发送的请求,返回安装 ServiceWorker 的页面 # 该请求为首次访问时发起,后续请求会被 SW 拦截并带上 JS 标记 if ($_hasSw = '0') { rewrite ^ /__setup.html; } set $_proto 'https'; if ($_isHttp = '1') { set $_proto 'http'; } if ($_port) { set $_port ':$_port'; } # CORS preflight if ($request_method = 'OPTIONS') { more_set_headers 'access-control-allow-origin: $_acao' 'access-control-allow-Methods: GET, POST, PUT, DELETE, HEAD, OPTIONS' 'access-control-allow-Headers: $http_Access_Control_Request_Headers' 'access-control-max-Age: 1728000' ; return 204; } # return 200 "[debug] # request_uri: $request_uri # host: $host # isHttp: $_isHttp # uri: $uri # args: $args # is_args: $is_args # _ref: $_ref # _ori: $_ori # _hasSw: $_hasSw # proxy: $_proto://$_site$_port; # "; access_log logs/proxy.log log_proxy buffer=64k flush=1s; proxy_pass $_proto://$_site$_port; # 将返回头 set-cookie 中的 domain 部分转换成我们的虚拟域名 proxy_cookie_domain ~^\.?(?<_rhost>.*)$ $_rhost_to_vhost; } location = /__setup.html { internal; brotli_static on; charset utf-8; root ../www; etag off; more_clear_headers Accept-Ranges Last-Modified; } # 由于 ServiceWorker 脚本必须位于同源站点, # 因此为了减少重复加载,此处只返回实际脚本的引用。 location = /__sw.js { add_header cache-control max-age=9999999; include include/x-js.conf; } } # 测试案例(暂未完成) #include test.conf; }