Openwrt 路由设置(三):使用Nginx作Web服务器
将openwrt默认uhttpd替换为Nginx;强制https;实现WebDAV功能;反向代理

Openwrt 路由设置(三):使用Nginx作Web服务器

(Nginx + WebDAV 配置 + 反向代理)


一、基本介绍

Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。其特点是占有内存少,并发能力强。

  • 实现WebDAV比较复杂,需要ngx_http_dav_module和nginx-dav-ext-module两个modules。但兼容性也有问题,不支持PROPPATCH;还需要headers-more-nginx-module参与修正兼容性。
    • dav_methods仅支持GET下载、PUT上传、DELETE删除、MKCOL新建文件夹、COPY复制、MOVE移动。
    • dav_ext_methods补充支持PROPFIND获取属性(查找和对比用)、OPTIONS检索服务、LOCK文件上锁、UNLOCK文件解锁。

使用 $ nginx -s reload 重新加载配置文件。
默认配置文件nginx.conf的基本框架:

events {

}
http {
    server {    # 不同server依照listen和server_name划分。
        # 不指定listen端口,默认80
        server_name example.com *.example.com www.example.*;
        # 指定 server_name 注意顺序。首要放第一个
        location / {  # 不同location依照URI地址划分。
            root /data/www;
        }
        location /img/ {
            root /data/images;
        }
        location ~ \.(gif|jpg|png)$ {
            root /data/images;
        }
    }

    # 反向代理:
    server {
        listen    8080;
        location  / {
            proxy_pass http://localhost:8080;
        }
    }
    # 为了防止空host或者无效host访问:
    server {
        listen       80  default_server;
        server_name  "" _;
        return       444;   # 返回报错
    }
}

通过输入,浏览器补充完整后得到"normalized request"地址;
头前删除https://,如剩余再无/,则剩余全部为host;如有,第一个/(不包含)之前的为host;
头前删除https://,如剩余再无/,则请求不包含路径;如有,第一个/(包含)之后的为路径;
头前删除https://,如剩余有且仅有一个/字符,则请求包含路径(注意包含!),但路径为空。
* 注意 https://aa.comhttps://aa.com/ 有区别!请求不包含路径VS请求包含路径

(一)server匹配规则

先匹配listen,再匹配server_name

  • listen可以带ip,如listen 192.168.1.1:8080;

    • listen可以跟参数default_serversslhttp2proxy_protocol
    • IPv6:例如listen [::]:8000;listen [::1];
  • server_name匹配(server_name与地址栏Host header比较)的优先级(高到低):

      1. 精准匹配
      2. * 开头的,最长的wildcard name,如*.example.com
      3. * 结尾的,最长的wildcard name,如www.example.*
      4. 第一个匹配上(按陈列顺序)的regular expression name
    • regular expression name以~开头,如~^(?<variable_name>\w\d{1,3}+)\.example\.net$
    • regular expression name中取变量:
server {
    server_name ~^(www\.)?(?<domain>.+)$;

    location / {
        root /sites/$domain;
    }
}
  • 匹配不上,默认用陈列的第一个server,除非某个server的listen加了default_server,例如listen 80 default_server; 即指定了该server为默认server。

(二)location匹配规则

当要在同个server内设置多个location时,先prefix:短的(笼统的)在前;长的(精准的)在后;再regular expressions。

匹配时,先根据prefix匹配,满足多个匹配时,长匹配优于短匹配,记录下最优匹配;继续regular expressions匹配,按照陈列顺序,如匹配上,即刻停止后续对比;如regular expressions全没有匹配上,则使用已记录下的prefix的最优匹配。

location 格式:

  1. 直接 prefix :常规。
  2. = prefix :定义一个精准匹配,如匹配上,即刻停止后续匹配。
  3. ^~ prefix :最长匹配上时,不在匹配regular expressions了。
  4. ~ regular expressions:~大小写敏感。
  5. ~* regular expressions:~*大小写不敏感。
  6. location可以嵌套。
  7. URI不匹照参数,如/index.php?page=1&something+else&user=john,匹配时只看/index.php

匹配后的剩余路径严格等于路径的文本删除掉locaiton条件。例如:
如路径为/abc/def?...locaiont /abc/,匹配后剩余为def?...
如路径为/abc/def?...locaiont /abc,匹配后剩余为/def?...
建议:location的条件以/结尾!

(三)限制客户端

location / {
    satisfy any;

    allow 192.168.1.0/32;
    deny  all;

    auth_basic           "closed site";
    auth_basic_user_file conf/htpasswd;
}

(四)https常规设置

https设置需3个要件:listen端口的ssl属性、证书、key。
默认设置有 ssl_protocols TLSv1 TLSv1.1 TLSv1.2; and ssl_ciphers HIGH:!aNULL:!MD5;

server {
    listen              443 ssl;
    server_name         www.example.com;
    ssl_certificate     www.example.com.crt;
    ssl_certificate_key www.example.com.key;
    ssl_protocols       TLSv1.3 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers   on; 
    # server ciphers should be preferred over client's
    ...
}

注意: 同一个listen,不同的server_name,用不同证书会出问题!

(五)反向代理设置

server {
    listen 8080;
    location / {
        proxy_pass http://localhost:8080;
    }
}
  1. proxy_pass 不包括路径(除://之外,没有/),代理后的路径包含location捕捉的路径。如:
location /api/ {
    proxy_pass http://node:8080;
}
# 访问:   /api/         后端:   /api/
# 访问:   /api/xx       后端:   /api/xx
# 访问:   /api/xx?aa    后端:   /api/xx?aa
  1. proxy_pass 中包含路径(即使不以/结尾),代理后的路径为location捕捉后剩余的路径。如:
location /api/ {
    proxy_pass http://node:8080/;   # 如无特殊要求,建议以`/`结尾。
}
# 访问:   /api/         后端:   /
# 访问:   /api/xx       后端:   /xx
# 访问:   /api/xx?aa    后端:   /xx?aa
  1. 如不设置proxy_set_header:当背后服务器取$remote_addr(客户端IP),得到最后一个反向代理的IP,并不是真实客户端IP;取$http_x_forwarded_for为空。
  • proxy_set_header X-Real-IP $remote_addr; 配置请求头的X-Real-IP(用户真实IP)。不设置则为反向代理的IP。
  • proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 在请求头X-Forwarded-For(该条请求是由谁发起的,客户来访所有路径的IP组)中添加本环节反向代理的IP。否则认为是反向代理发起的。
  • proxy_set_header Host $host; 配置请求头的Host(所请求的目的主机名)。
    • $http_host:host name from the “Host” request header field。为反向代理。可能有port。原汁原味的再转给背后server。
    • $host:值按照如下优先级获得:from the request line; from the “Host” request header field; server_name matching a request。注意,不带port。可防止无$http_host获取的情况,因为有server_name兜底。
    • $host:$proxy_port:加上目的地port。
    • $proxy_host:proxy_pass的目的地;包括host和port。除非特殊情况,一般用这个。
  • proxy_redirect a b; server返回的地址,把a重写成b。proxy_redirect default; 即可隐藏内网真实地址。

二、nginx设置优化

(一)https性能优化

  • worker_processes 4; # 在main中。建议等于CPU核心;ssl多线程提高效率;可设置auto,自动设为核心数。
  • keepalive_timeout 900s 600s; # 在http, server, location中。默认75s。第二个参数(可选)发送浏览器。
  • ssl_session_cache shared:SSL:10m; # 在http, server中。建议用shared,并大于等于10M。1M大约4000个sessions。注意:SSL别重名!
  • ssl_session_timeout 15m; # 在http, server中。默认5分钟,建议调长。

(二)内存与磁盘优化

  • client_max_body_size 0; # 在http, server, location中。默认1m;0不限制请求文件大小(建议仅在webdav中不限大小,全局设为2048M)。必设。
  • client_body_in_single_buffer on; # 在http, server, location中。http包一律写入到内存buffer中。
  • client_body_buffer_size 4096M; # 在http, server, location中。默认16k。如超过将写入临时文件。必设。
  • client_body_temp_path /tmp/nginxtemp 2; # 在http, server, location中。2级子目录可用。最好放在内存。在webdav上传时,先存在此路径,再转移到实际路径。
  • sendfile on; # 在http, server, location中。简化发送文件的流程,提高效率。
  • open_file_cache # 在http, server, location中。缓存文件的存储信息。默认关;注意,不要启动,启动后webdav文件刷新慢。

(三)传输文件性能优化

  • sendfile_max_chunk 10m; # 在http, server, location中。配套sendfile,默认2m。积累到10M强制send。0不限制会堵塞连接。

  • tcp_nopush on; # 在http, server, location中。配套sendfile。

  • tcp_nodelay on; # 在http, server, location中。keep-alive时尽快发包。

  • send_timeout 300s; # 在http, server, location中。默认60s。

  • client_body_timeout 300s; # 在http, server中。默认60s。

  • client_header_timeout 300s; # 在http, server中。默认60s。

  • client_header_buffer_size 4k; # 在http, server中。默认1k。

  • large_client_header_buffers 4 32k; # 在http, server中。默认4 8k。必设。

  • lingering_timeout 60s; # 在http, server, location中。默认5s。

  • keepalive_timeout 900s 600s; # 在http, server, location中。默认75s。缩小可以尽快释放连接。注意根据不同应用场景区别设置。第二个参数(可选)发送浏览器。

注意: 上述设置要根据应用场景灵活调整。如缩短timeout可以尽快释放连接,提高连接使用效率;延长timeout可以提高连接稳定性。

(四)反向代理优化

  • proxy_buffer_size 8k; # 在http, server, location中。默认8k。小点2k快。但如果关闭buffering,太小存不下会丢内容。
  • proxy_buffering off; # 在http, server, location中。默认on。如果关闭,将实时回复不缓存,快。
  • proxy_request_buffering off; # 在http, server, location中。默认on。如果关闭,将实时发送不缓存,快。
  • proxy_connect_timeout 600s; # 在http, server, location中。默认60s。
  • proxy_send_timeout 600s; # 在http, server, location中。默认60s。
  • proxy_read_timeout 600s; # 在http, server, location中。默认60s。

三、openwrt的nginx设置

核心思想:Openwrt 对外接口一律通过HTTPS加密传输;用CA证书或自签发SSL证书,不用预设内置证书。

(一)替换原uhttpd

  1. 先停用uhttpd,否则80端口占用冲突。
$ /etc/init.d/uhttpd stop
$ /etc/init.d/uhttpd disable
  1. 安装nginx
$ opkg install nginx-all-module luci-ssl-nginx  # 注意!严格先后顺序!

(二)openwrt的nginx配置文件的结构

openwrt启动后,将使用模板 /etc/nginx/uci.conf.template 在内存中自动生成配置文件,并生成该配置文件的软连接 /etc/nginx/uci.conf

  1. 模板中的 #UCI_HTTP_CONFIG 根据 /etc/config/nginx UCI配置文件替换。
  2. UCI默认配置的"_lan" server 中加载 /etc/nginx/restrict_locally 限制只允许从局域网访问。
  3. UCI默认配置的"_lan" server 中加载 /etc/nginx/conf.d/*.locations :加载到默认"_lan" server的{}内,建议存放各种自定义location。
  4. 最后加载 /etc/nginx/conf.d/*.conf :加载到http的{}内,建议存放各种自定义server。
    注意: 不推荐直接修改 uci.conf.template,因为该文件会随着系统升级。
    注意: *.locations*.conf文件设置如果和UCI设置冲突,将取代UCI设置。
    注意: 如果自定义 /etc/nginx/nginx.conf 文件作为主配置文件,屏蔽UCI配置文件:uci set nginx.global.uci_enable=false。不推荐。

(三)通过 /etc/config/nginx UCI配置

修改 /etc/config/nginx 文件,在其中添加供外网访问的HTTPS通道:

config server '_lan2'
    list listen '10443 ssl default_server'
    list listen '[::]:10443 ssl default_server'
    option server_name '_lan2'
    list include 'restrict_locally'
    list include 'conf.d/*.locations'
    option uci_manage_ssl 'acme'    # 必须将self-signed改为其他名字
    option ssl_certificate '/etc/ssl/aaa.cer'      # 公网域名证书
    option ssl_certificate_key '/etc/ssl/aaa.key'  # 公网域名KEY
    option ssl_session_cache 'shared:SSL2:32k'     # “SSL2”不能重名
    option ssl_session_timeout '64m'
    option access_log 'off; # logd openwrt'
  • 设置防火墙通信规则和端口转发,即刻实现外网访问。

(四)通过 /etc/nginx/conf.d/* 详细配置

可实现复杂功能:

  1. nginx配置文件
# 在 main{} 中
worker_processes auto;    # openwrt 的默认设置中已有
  1. http 设置
  • 新建 /etc/nginx/conf.d/http_head.conf
# 在 http{} 中

client_body_in_single_buffer  on;
client_body_buffer_size       128k;
client_body_temp_path         /tmp/log/nginx 3;
client_body_timeout           300s;
client_header_timeout         300s;
client_header_buffer_size     4k;
# large_client_header_buffers 4 32k; # openwrt 的默认设置中已有,但设的小。

# sendfile on; # openwrt 的默认设置中已有
sendfile_max_chunk 10m;
tcp_nopush   on;
tcp_nodelay  on;

# MacOS 的 WebDAV 客户端需要启用锁支持:
dav_ext_lock_zone zone=webdavlock:10m;  # 注意记下名
  1. HTTPS 设置
  • 新建 /etc/nginx/conf.d/https.conf
# 在 http{} 中

server {
    listen              10443 ssl http2;
    listen              [::]:10443 ssl http2;
    server_name         example.com;
    ssl_certificate     example.com.crt;
    ssl_certificate_key example.com.key;
    ssl_protocols       TLSv1.3 TLSv1.2;
    ssl_prefer_server_ciphers   on;

    ssl_session_cache shared:TLS:10m;
    ssl_session_timeout 30m;
    
    root /www;

    include /etc/nginx/conf.d/luci.locations;       # openwrt后台
    include /etc/nginx/conf.d/proxy_pass.locations; # 反向代理

    sendfile on;                        # 覆盖 openwrt 默认设置
    large_client_header_buffers 4 32k;  # 覆盖 openwrt 默认设置
}
  1. WevDAV 设置
  • 开两个端口、设两个server{},分别服务于局域网和公网。都用https,但对应不同的证书。
  • 可以设置多个挂载盘,对应不同权限。
  • 新建 /etc/nginx/conf.d/webdav.conf
    因为Windows默认只支持https的WebDAV,所以以下内外网均使用https。
#在 http{} 中

# mac 客户端需要启用锁支持
# dav_ext_lock_zone zone=webdavlock:10m; # 已在http_head.conf中设置

server {    # 外网webdav
    listen              10090 ssl http2;
    listen              [::]:10090 ssl http2;
    server_name         example.com;
    ssl_certificate     example.com.crt;
    ssl_certificate_key example.com.key;
    ssl_protocols       TLSv1.3 TLSv1.2;
    ssl_prefer_server_ciphers   on;

    ssl_session_cache shared:DAV:32k;
    ssl_session_timeout 30m;

    include /etc/nginx/conf.d/webdav;
}
server {    # 内网webdav。
    listen              90 ssl http2;
    listen              [::]:90 ssl http2;
    server_name         _webdavlan;
    include /etc/nginx/restrict_locally;
    ssl_certificate     /etc/nginx/conf.d/_lan.crt;  # Windows客户端要求证书准确,故必须使用自签发SSL证书,并让Windows信任CA根证书。
    ssl_certificate_key /etc/nginx/conf.d/_lan.key;

    ssl_session_cache shared:DAVLAN:32k;
    ssl_session_timeout 120m;

    include /etc/nginx/conf.d/webdav;
}
  • 新建 /etc/nginx/conf.d/webdav 供 include 复用:
# 在 server{} 里

sendfile on;                                # 覆盖 openwrt 默认设置
large_client_header_buffers 4 32k;          # 覆盖 openwrt 默认设置
# client_body_temp_path /tmp/log/nginx 3;   # 已在http_head.conf中设置

access_log /var/log/webdav_access.log;
error_log  /var/log/webdav_error.log;

client_max_body_size  0;     # 不限制文件大小
send_timeout          300s;
lingering_timeout     60s;
keepalive_timeout     900s 600s;

charset utf-8;

# 支持所有方法
dav_methods PUT DELETE MKCOL COPY MOVE;
dav_ext_methods PROPFIND OPTIONS LOCK UNLOCK;
dav_ext_lock zone=webdavlock;   # 注意名字已提前设置好
create_full_put_path  on;       # 默认只能用已存在目录,on后可新建子目录。

root /mnt/adb1/tmp;    # 设个空的地方。

location /aaa {    # 通过/aaa挂载;通过/aaa/网页访问,注意如通过/aaa网页访问,下载报错。
    # 访问 /mnt/sdb1/aaa,注意Nginx worker用户对该目录需有读写权限
    root /mnt/sdb1;  # 加不加尾/都一样。
    auth_basic            "Private Site";
    auth_basic_user_file  /etc/nginx/aaa.passwd;
    dav_access            user:rw group:rw;

    # 即使不用webdav客户端,直接浏览器访问也是能够下载对应目录和文件的
    autoindex            on;
    autoindex_localtime  on;
    autoindex_exact_size off;  # 不精确,四舍五入到整数k、m、g

    include /etc/nginx/conf.d/webdav_location_set;
}
location /bbb {
    # 访问 /mnt/sdb1/bbb
    root /mnt/sdb1;
    auth_basic            "Private Site";
    auth_basic_user_file  /etc/nginx/bbb.passwd;
    dav_access            user:rw group:rw;

    include /etc/nginx/conf.d/webdav_location_set;
}
# Mac挂载webdav后会自动写入很多文件,以下为屏蔽配置,保持webdav目录干净
location ~ \.(_.*|DS_Store|Spotlight-V100|TemporaryItems|Trashes|hidden)$ {
    access_log  off;
    error_log   off;
    if ($request_method = PUT) {
        return 403;
    }
    return 404;
}
location ~ \.metadata_never_index$ {
    return 200 "Don't index this drive, Finder!";
}
  • 新建 /etc/nginx/conf.d/webdav_location_set 供 include 复用:
# 在 location {} 里,为解决各平台webdav客户端的兼容性问题
set $dest $http_destination;
if (-d $request_filename) {    # 是目录
    rewrite ^(.*[^/])$ $1/;     # 第一个()里的内容是$1。
    set $dest $dest/;
}
if ($request_method ~ (MOVE|COPY)) {
    more_set_input_headers 'Destination: $dest';
}
if ($request_method ~ MKCOL) {
    rewrite ^(.*[^/])$ $1/ break;
}
if ($request_method ~ PROPPATCH) {    # Unsupported, allways return OK.
    add_header Content-Type 'text/xml';
    return 207 '<?xml version="1.0"?><a:multistatus xmlns:a="DAV:"><a:response><a:propstat><a:status>HTTP/1.1 200 OK</a:status></a:propstat></a:response></a:multistatus>';
}
  • 设置webdav用户和密码:
$ printf "user:$(openssl passwd -crypt 123456)\n" >> /etc/nginx/aaa.passwd

添加了"user:xyJkVhXGAZ8tM",即用户名为user,密码为123456。

注意: Windows系统对于同一个域名WebDAV只可记住一组用户名:密码。所以当如上设置,同一服务器提供多个挂载盘、不同密码时,建议设置一个共同的用户名和强密码,供Windows使用。

  1. 反向代理
  • 新建 /etc/nginx/conf.d/proxy_pass_set 供 include 复用:
proxy_buffer_size        8k;
proxy_buffering          off;
proxy_request_buffering  off;
proxy_connect_timeout    600s;
proxy_send_timeout       600s;
proxy_read_timeout       600s;
  • 新建 /etc/nginx/conf.d/nextterminal.conf
server {
    listen              10099 ssl http2;
    listen              [::]:10099 ssl http2;
    server_name         example.com;
    ssl_certificate     /etc/ssl/eeee.crt;
    ssl_certificate_key /etc/ssl/example.com.key;
    ssl_protocols       TLSv1.3 TLSv1.2;
    ssl_prefer_server_ciphers   on;

    location / {
        proxy_pass http://127.0.0.1:8077/;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $http_connection;

        include /etc/nginx/conf.d/proxy_pass_set;
    }

    sendfile on;
    large_client_header_buffers 4 32k;
}
  • 新建 /etc/nginx/conf.d/proxy_pass.locations
# 在 server{} 里
location /oc/ {    # Openclash GUI
    proxy_pass http://127.0.0.1:9089/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;

    include /etc/nginx/conf.d/proxy_pass_set;
}
location /files/ { # File Browser
    proxy_pass       http://127.0.0.1:8089/;
    proxy_set_header Origin http://127.0.0.1:8089;
    proxy_set_header Host $proxy_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;
}

附:Openwrt Nginx 安装后默认设置

worker_processes auto;  # 注意同一个位置不能重复设置。

user root;

events {}
http {
    access_log off;
    log_format openwrt
        '$request_method $scheme://$host$request_uri => $status'
        ' (${body_bytes_sent}B in ${request_time}s) <- $http_referer';

    include mime.types; # 略
    default_type application/octet-stream;
    sendfile on;    # 注意同一个位置不能重复设置。

    client_max_body_size 128M;
    large_client_header_buffers 2 1k;  # 注意同一个位置不能重复设置。openwrt设置比nginx默认值小。

    gzip on;
    gzip_vary on;
    gzip_proxied any;

    root /www;

    server { #see uci show 'nginx._lan'
        listen 443 ssl default_server;
        listen [::]:443 ssl default_server;
        server_name _lan;

        allow ::1;
        allow fc00::/7;
        allow fec0::/10;
        allow fe80::/10;
        allow 127.0.0.0/8;
        allow 192.168.0.0/16;
        deny all;
        
        location /cgi-bin/luci {
            index  index.html;
            include uwsgi_params;
            uwsgi_param SERVER_ADDR $server_addr;
            uwsgi_modifier1 9;
            uwsgi_pass unix:////var/run/luci-webui.socket;
        }
        location ~ /cgi-bin/cgi-(backup|download|upload|exec) {
            include uwsgi_params;
            uwsgi_param SERVER_ADDR $server_addr;
            uwsgi_modifier1 9;
            uwsgi_pass unix:////var/run/luci-cgi_io.socket;
        }

        location /luci-static {
                error_log stderr crit;
        }

        location /ubus {
            ubus_interpreter;
            ubus_socket_path /var/run/ubus/ubus.sock;
            ubus_parallel_req 2;
        }

        ssl_certificate /etc/nginx/conf.d/_lan.crt;
        ssl_certificate_key /etc/nginx/conf.d/_lan.key;
        ssl_session_cache shared:SSL:32k;
        ssl_session_timeout 62m;
        access_log off; # logd openwrt;
    }

    server { #see uci show 'nginx._redirect2ssl'
        listen 80;
        listen [::]:80;
        server_name _redirect2ssl;
        return 302 https://$host$request_uri;
    }

    include conf.d/*.conf;
}

最后修改于 2024-02-24