从零开始搭建后台服务 - Nginx(01)

382 阅读4分钟

介绍

Nignx是一个高性能Web服务器,实际投产过程中用途比较广泛,例如负载均衡、反向代理、动静分离等等,总之当需要搭建Web服务,基本上第一件事就是搭Nginx。

目前Nignx有三个流派,第一是开源版本的Nginx,第二是淘宝的Tengine,第三是OpenResty,这三个版本这次的实战中都简单的使用过,在实际运维过程中Nginx的使用和配置比较重要,正式跑业务时也需要配置Nginx代理业务,避免向外暴露服务及端口,集群分流也都需要Nginx,因此深入的了解Nginx是比较重要的。

学习

最开始学习时使用的Docker镜像直接安装了Nginx,监听80端口,浏览器访问正常,草草了事,顺带学习了下Docker的基础用法,属于初步了解和学习期。

现在细想这样的选择主要是快速和顺便学了Docker。这种模式下带来的问题也比较明显,通俗点讲就是套娃效应,实际投产过程中Docker间的访问会变得非常多。再就是本身云服务器就是物理机上的虚拟机,而作为流量入口追求的是统一的入口和性能,因此建议不基于Docker去搭,而是在本机上搭Nginx

这其中遇到了很多坑,比如印象较深的是初期我想给Nginx弄一个面板配置,网上搜索使用了NginxProxyManager,这个产品网上的教程很多,但都太简单了,实际投产中远远不够,官方文档也没告诉你如何实践,底层本质是什么,所以刚开始用的时候将信将疑的在用,面板编辑又时不时的报502错误,总之带了一堆疑问在探索。本着最终是本机上搭Ngnix且调优配置所以对NginxProxyManager进行了参考学习。

NginxProxyManager

安装应用程序

创建一个文件:docker-compose.yml

version: '3'
services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
      - '80:80'
      - '81:81'
      - '443:443'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt

执行:

docker-compose up -d

# If using docker-compose-plugin
docker compose up -d

运行效果

image.png

实话讲,这个开源软件的UI挺不错的,查了下前端框架使用了Tabler,之后有需求可以尝试学习使用。

入口配置:/etc/nginx/nginx.conf

# run nginx in foreground
daemon off;

user root;

# Set number of worker processes automatically based on number of CPU cores.
worker_processes auto;

# Enables the use of JIT for regular expressions to speed-up their processing.
pcre_jit on;

error_log /data/logs/fallback_error.log warn;

# Includes files with directives to load dynamic modules.
include /etc/nginx/modules/*.conf;

events {
        worker_connections  1024;
}

http {
        include                       /etc/nginx/mime.types;
        default_type                  application/octet-stream;
        sendfile                      on;
        server_tokens                 off;
        tcp_nopush                    on;
        tcp_nodelay                   on;
        client_body_temp_path         /tmp/nginx/body 1 2;
        keepalive_timeout             90s;
        proxy_connect_timeout         90s;
        proxy_send_timeout            90s;
        proxy_read_timeout            90s;
        ssl_prefer_server_ciphers     on;
        gzip                          on;
        proxy_ignore_client_abort     off;
        client_max_body_size          2000m;
        server_names_hash_bucket_size 1024;
        proxy_http_version            1.1;
        proxy_set_header              X-Forwarded-Scheme $scheme;
        proxy_set_header              X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header              Accept-Encoding "";
        proxy_cache                   off;
        proxy_cache_path              /var/lib/nginx/cache/public  levels=1:2 keys_zone=public-cache:30m max_size=192m;
        proxy_cache_path              /var/lib/nginx/cache/private levels=1:2 keys_zone=private-cache:5m max_size=1024m;

        log_format proxy '[$time_local] $upstream_cache_status $upstream_status $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] [Sent-to $server] "$http_user_agent" "$http_referer"';
        log_format standard '[$time_local] $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] "$http_user_agent" "$http_referer"';

        access_log /data/logs/fallback_access.log proxy;

        # Dynamically generated resolvers file
        include /etc/nginx/conf.d/include/**resolvers**.conf;

        # Default upstream scheme
        map $host $forward_scheme {
                default http;
        }

        # Real IP Determination
        
        # Local subnets:
        set_real_ip_from 10.0.0.0/8;
        set_real_ip_from 172.16.0.0/12; # Includes Docker subnet
        set_real_ip_from 192.168.0.0/16;
        # NPM generated CDN ip ranges:
        include conf.d/include/ip_ranges.conf;
        # always put the following 2 lines after ip subnets:
        real_ip_header X-Real-IP;
        real_ip_recursive on;

        # Custom
        include /data/nginx/custom/http_top[.]conf;

        # Files generated by NPM
        include /etc/nginx/conf.d/*.conf;
        include /data/nginx/default_host/*.conf;
        include /data/nginx/proxy_host/*.conf; #编辑代理时存储位置
        include /data/nginx/redirection_host/*.conf;
        include /data/nginx/dead_host/*.conf;
        include /data/nginx/temp/*.conf;

        # Custom
        include /data/nginx/custom/http[.]conf;
}

stream {
        # Files generated by NPM
        include /data/nginx/stream/*.conf;

        # Custom
        include /data/nginx/custom/stream[.]conf;
}

# Custom
include /data/nginx/custom/root[.]conf;

可以发现入口配置,基本上把经常改动的和通用配置项都挂载到/data/nginx/custom等位置了,通用基础配置挂载在/etc/nginx/conf.d/

代理配置:default.conf

server {
        listen 80;
        listen [::]:80;

        set $forward_scheme "http";
        set $server "127.0.0.1";
        set $port "80";

        server_name localhost-nginx-proxy-manager;
        access_log /data/logs/fallback_access.log standard;
        error_log /data/logs/fallback_error.log warn;
        include conf.d/include/assets.conf;
        include conf.d/include/block-exploits.conf;
        include conf.d/include/letsencrypt-acme-challenge.conf;

        location / {
                index index.html;
                root /var/www/html;
        }
}

# First 443 Host, which is the default if another default doesn't exist
server {
        listen 443 ssl;
        listen [::]:443 ssl;

        set $forward_scheme "https";
        set $server "127.0.0.1";
        set $port "443";

        server_name localhost;
        access_log /data/logs/fallback_access.log standard;
        error_log /dev/null crit;
        ssl_certificate /data/nginx/dummycert.pem;
        ssl_certificate_key /data/nginx/dummykey.pem;
        include conf.d/include/ssl-ciphers.conf;

        return 444;
}

后台配置项:production.conf

server {
        listen 81 default;
        listen [::]:81 default;

        server_name nginxproxymanager;
        root /app/frontend;
        access_log /dev/null;

        location /api {
                return 302 /api/;
        }

        location /api/ {
                add_header            X-Served-By $host;
                proxy_set_header Host $host;
                proxy_set_header      X-Forwarded-Scheme $scheme;
                proxy_set_header      X-Forwarded-Proto  $scheme;
                proxy_set_header      X-Forwarded-For    $remote_addr;
                proxy_pass            http://127.0.0.1:3000/;

                proxy_read_timeout 15m;
                proxy_send_timeout 15m;
        }

        location / {
                index index.html;
                if ($request_uri ~ ^/(.*)\.html$) {
                        return 302 /$1;
                }
                try_files $uri $uri.html $uri/ /index.html;
        }
}

基础配置项:assets.conf

location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|eot|ttf|svg|ico|css\.map|js\.map)$ {
        if_modified_since off;

        # use the public cache
        proxy_cache public-cache;
        proxy_cache_key $host$request_uri;

        # ignore these headers for media
        proxy_ignore_headers Set-Cookie Cache-Control Expires X-Accel-Expires;

        # cache 200s and also 404s (not ideal but there are a few 404 images for some reason)
        proxy_cache_valid any 30m;
        proxy_cache_valid 404 1m;

        # strip this header to avoid If-Modified-Since requests
        proxy_hide_header Last-Modified;
        proxy_hide_header Cache-Control;
        proxy_hide_header Vary;

        proxy_cache_bypass 0;
        proxy_no_cache 0;

        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_404;
        proxy_connect_timeout 5s;
        proxy_read_timeout 45s;

        expires @30m;
        access_log  off;

        include conf.d/include/proxy.conf;
}

head头配置项:proxy.conf

add_header       X-Served-By $host;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto  $scheme;
proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP          $remote_addr;
proxy_pass       $forward_scheme://$server:$port$request_uri;

其它配置项:block-exploits.conf

## Block SQL injections
set $block_sql_injections 0;

if ($query_string ~ "union.*select.*\(") {
        set $block_sql_injections 1;
}

if ($query_string ~ "union.*all.*select.*") {
        set $block_sql_injections 1;
}

if ($query_string ~ "concat.*\(") {
        set $block_sql_injections 1;
}

if ($block_sql_injections = 1) {
        return 403;
}

## Block file injections
set $block_file_injections 0;

if ($query_string ~ "[a-zA-Z0-9_]=http://") {
        set $block_file_injections 1;
}

if ($query_string ~ "[a-zA-Z0-9_]=(\.\.//?)+") {
        set $block_file_injections 1;
}

if ($query_string ~ "[a-zA-Z0-9_]=/([a-z0-9_.]//?)+") {
        set $block_file_injections 1;
}

if ($block_file_injections = 1) {
        return 403;
}

## Block common exploits
set $block_common_exploits 0;

if ($query_string ~ "(<|%3C).*script.*(>|%3E)") {
        set $block_common_exploits 1;
}

if ($query_string ~ "GLOBALS(=|\[|\%[0-9A-Z]{0,2})") {
        set $block_common_exploits 1;
}

if ($query_string ~ "_REQUEST(=|\[|\%[0-9A-Z]{0,2})") {
        set $block_common_exploits 1;
}

if ($query_string ~ "proc/self/environ") {
        set $block_common_exploits 1;
}

if ($query_string ~ "mosConfig_[a-zA-Z_]{1,21}(=|\%3D)") {
        set $block_common_exploits 1;
}

if ($query_string ~ "base64_(en|de)code\(.*\)") {
        set $block_common_exploits 1;
}

if ($block_common_exploits = 1) {
        return 403;
}

## Block spam
set $block_spam 0;

if ($query_string ~ "\b(ultram|unicauca|valium|viagra|vicodin|xanax|ypxaieo)\b") {
        set $block_spam 1;
}

if ($query_string ~ "\b(erections|hoodia|huronriveracres|impotence|levitra|libido)\b") {
        set $block_spam 1;
}

if ($query_string ~ "\b(ambien|blue\spill|cialis|cocaine|ejaculation|erectile)\b") {
        set $block_spam 1;
}

if ($query_string ~ "\b(lipitor|phentermin|pro[sz]ac|sandyauer|tramadol|troyhamby)\b") {
        set $block_spam 1;
}

if ($block_spam = 1) {
        return 403;
}

## Block user agents
set $block_user_agents 0;

# Disable Akeeba Remote Control 2.5 and earlier
if ($http_user_agent ~ "Indy Library") {
        set $block_user_agents 1;
}

# Common bandwidth hoggers and hacking tools.
if ($http_user_agent ~ "libwww-perl") {
        set $block_user_agents 1;
}

if ($http_user_agent ~ "GetRight") {
        set $block_user_agents 1;
}

if ($http_user_agent ~ "GetWeb!") {
        set $block_user_agents 1;
}

if ($http_user_agent ~ "Go!Zilla") {
        set $block_user_agents 1;
}

if ($http_user_agent ~ "Download Demon") {
        set $block_user_agents 1;
}

if ($http_user_agent ~ "Go-Ahead-Got-It") {
        set $block_user_agents 1;
}

if ($http_user_agent ~ "TurnitinBot") {
        set $block_user_agents 1;
}

if ($http_user_agent ~ "GrabNet") {
        set $block_user_agents 1;
}

if ($block_user_agents = 1) {
        return 403;
}

其它配置项:letsencrypt-acme-challenge.conf

# Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
# We use ^~ here, so that we don't check other regexes (for speed-up). We actually MUST cancel
# other regex checks, because in our other config files have regex rule that denies access to files with dotted names.
location ^~ /.well-known/acme-challenge/ {
        # Since this is for letsencrypt authentication of a domain and they do not give IP ranges of their infrastructure
        # we need to open up access by turning off auth and IP ACL for this location.
        auth_basic off;
        auth_request off;
        allow all;

        # Set correct content type. According to this:
        # https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29
        # Current specification requires "text/plain" or no content header at all.
        # It seems that "text/plain" is a safe option.
        default_type "text/plain";

        # This directory must be the same as in /etc/letsencrypt/cli.ini
        # as "webroot-path" parameter. Also don't forget to set "authenticator" parameter
        # there to "webroot".
        # Do NOT use alias, use root! Target directory is located here:
        # /var/www/common/letsencrypt/.well-known/acme-challenge/
        root /data/letsencrypt-acme-challenge;
}

# Hide /acme-challenge subdirectory and return 404 on all requests.
# It is somewhat more secure than letting Nginx return 403.
# Ending slash is important!
location = /.well-known/acme-challenge/ {
        return 404;
}

其它配置项:force-ssl.conf

if ($scheme = "http") {
        return 301 https://$host$request_uri;
}

开始使用

配置Details

image.png

image.png

其底层逻辑从上文入口配置项得知,其本质是写入一个文件,目录在/data/nginx/proxy_host

image.png

image.png

配置Custom locations

image.png

image.png

配置SSL

image.png image.png

配置Advanced

image.png

image.png

配置Stream

image.png

image.png

注意以下配置是在stream节点中增加: image.png 这里要特别注意一点,这是stream节点而不是http节点的upstream,比如这个可以用来通过域名直连mysql/redis等等

结束

这章主要是通过使用开源的NginxProxyManager,一探其Nginx底层的配置项,所以对Nginx的配置熟悉在实战过程中就变得特别重要,下一章将会针对这次的学习配置线上优化过后的Nginx。