nginx

常用命令

cd /usr/local/nginx/sbin/
./nginx  启动
./nginx -s stop  停止
./nginx -s quit  安全退出
./nginx -s reload  重新加载配置文件
ps aux|grep nginx  查看nginx进程

# 统计请求ip和个数
zcat *.log.gz | jq -r '.remote_addr' | sort | uniq -c | sort -nr

基本命令

sendfile on; #html下的配置,可以直接把文件发给用户,不经过nginx
listen 80; #开放端口
server_name 127.0.0.1; #域名或者ip
index index.html index.htm; # 默认页面
#root和alias的区别,如需要访问/usr/local/nginx/html/images,访问的路径是域名加/images:
#root的处理结果是: root路径+location路径
location /images {
	root /usr/local/nginx/html;
}
#alias的处理结果是:使用alias路径替换location路径
location /images {
	alias /usr/local/nginx/html/images;
}
include /etc/nginx/conf.d/*.conf; #写在http下,可以读取目录下的n多个配置文件,不需要改主文件

反向代理

最主要的是proxy_pass这个参数

location / {
    proxy_pass http://127.0.0.1:5230/;
    rewrite ^/(.*)$ /$1 break;
    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Upgrade-Insecure-Requests 1;
    proxy_set_header X-Forwarded-Proto https;
}
location /api/ {
    proxy_pass http://127.0.0.1:3000/api/
}

在后端的基础上增加层数

location / {
	rewrite  ^/proxy/(.*)$ /$1 break;
    proxy_pass http://127.0.0.1:3000/api/
}

动静分离

可以把静态文件部署在nginx上

location ~* \.(gif|jpg|jpeg)${
    root /html;
}
location ^~ /static/ {
    root /webroot/static/;
}
location ~* \.(html|gif|jpg|jpeg|png|css|js|ico)$ {
    root /webroot/res/;
}

重写(URLRewrite)

rewrite ^/[0-9]+.html$ /index.html?testParam=$1 break; //$1表示第一个匹配的字符串 

防盗链

写在location下

valid_referers none 192.168.1.1 #白名单ip
if ($invalid_referer) {
	return 403; # 返回错误码
}
rewrite ^/ /403.png break; #

keepalived高可用

global_defs {
   router_id LB_102 #名字
}

vrrp_instance VI_102 { #名字
    state MASTER  #MASTER和BACKUP表示主备优先级
    interface ens33 #网卡
    virtual_router_id 51 #id需一样
    priority 100 #优先级
    advert_int 1
    authentication { #组的账号和密码
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.8.200 #虚拟漂移的ip
    }
}

ssl证书的配置

http

server {
	listen 443 ssl;
	server_name localhost;  # 域名
	ssl_certificate xxx.pem; #证书
	ssl_certificate_key xxx.key; #秘钥
    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;

    location / {
        proxy_pass http://127.0.0.1:5230/;
    }
}

tcp

server {
	listen 443 ssl;
	ssl_certificate xxx.pem; #证书
	ssl_certificate_key xxx.key; #秘钥
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    proxy_pass 127.0.0.1:5230;
}

自签ssl证书

openssl genrsa -out server.key 2048
openssl req -x509 -new -nodes -key server.key -sha256 -days 3650 -out server.crt
Tip

由于http协议默认的端口是80,而https默认的端口是443,如果想让http的访问跳转到https的访问,可以做如下配置

server {
	listen 80;
	server_name ; #域名
	return 301 https://$server_name$request_uri;	
}

下载目录

location /data {
    alias   /data/res;  #定义网站根目录,目录可以是相对路径也可以是绝对路径。
    autoindex on; # 开启目录浏览功能;
    autoindex_exact_size off; # 关闭详细文件大小统计,让文件大小显示MB,GB单位,默认为b;
    autoindex_localtime on; # 开启以服务器本地时区显示文件修改日期!
    auth_basic "Please input password"; #密码访问
    auth_basic_user_file /data/web/passwd;
    charset  utf-8,gbk; #定义站点的默认页。
}

密码访问

centos
debian
yum install -y httpd-tools
htpasswd -c passwd user

美化下载目录

sendfile    on;
add_after_body /.autoindex.html;

在目录页放入文件,需要把autoindex_exact_size打开

美化文件
autoindex.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Nginx File Server</title>
    <style>
        :root {
            --bg: #f6f8fa;
            --card: #ffffff;
            --text: #24292f;
            --muted: #6e7781;
            --border: #d0d7de;
            --hover: #f3f4f6;
            --accent: #0969da;
        }

        body.dark {
            --bg: #0d1117;
            --card: #161b22;
            --text: #c9d1d9;
            --muted: #8b949e;
            --border: #30363d;
            --hover: #21262d;
            --accent: #2f81f7;
        }

        body {
            margin: 0;
            background: var(--bg);
            color: var(--text);
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
            display: flex;
            flex-direction: column;
            min-height: 100vh;
            transition: background 0.3s, color 0.3s;
        }

        body > hr { display: none; }

        .toolbar {
            position: sticky;
            top: 0;
            z-index: 10;
            background: var(--card);
            border-bottom: 1px solid var(--border);
            padding: 14px 18px;
            transition: background 0.3s, border-color 0.3s;
        }

        .toolbar-row {
            display: flex;
            gap: 10px;
            align-items: center;
        }

        .breadcrumb {
            font-size: 13px;
            margin-bottom: 10px;
        }

        .breadcrumb a { color: var(--accent); text-decoration: none; }
        .breadcrumb span { color: var(--muted); margin: 0 6px; }
        .breadcrumb .current { color: var(--muted); }

        input {
            height: 34px;
            padding: 0 12px;
            border-radius: 8px;
            border: 1px solid var(--border);
            background: var(--card);
            color: var(--text);
            width: 260px;
            transition: background 0.3s, border-color 0.3s, color 0.3s;
        }

        .list {
            max-width: 1400px;
            width: 95%;
            margin: 16px auto;
            background: var(--card);
            border: 1px solid var(--border);
            border-radius: 12px;
            overflow: hidden;
            transition: background 0.3s, border-color 0.3s;
            flex: none;
        }

        .row {
            display: grid;
            grid-template-columns: 1fr 200px 120px;
            gap: 12px;
            align-items: center;
            padding: 10px 16px;
            border-bottom: 1px solid var(--border);
            transition: background 0.3s, border-color 0.3s;
        }

        .row:hover { background: var(--hover); }
        .row a { color: var(--text); text-decoration: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

        a.folder::before { content: "📁 "; }
        a.file::before { content: "📄 "; }
        a.img::before { content: "🖼️ "; }
        a.code::before { content: "📜 "; }

        mark { background: #fde68a; border-radius: 4px; padding: 0 2px; }

        #theme-toggle {
            cursor: pointer;
            border: 1px solid var(--border);
            border-radius: 6px;
            background: var(--card);
            color: var(--text);
            padding: 4px 8px;
            transition: background 0.3s, color 0.3s, border-color 0.3s;
        }

        /* 页脚固定底部 */
        .footer {
            text-align: center;
            padding: 16px 0;
            font-size: 14px;
            color: var(--muted);
            background: var(--card);
            border-top: 1px solid var(--border);
            margin-top: auto;  /* 关键:把页脚推到底部 */
            transition: background 0.3s, border-color 0.3s, color 0.3s;
        }

        .footer-link {
            color: #3b82f6;
            text-decoration: none;
            margin-left: 4px;
        }

        .footer-link:hover {
            text-decoration: underline;
        }
    </style>
</head>
<body>

<div class="toolbar">
    <div class="breadcrumb" id="breadcrumb"></div>
    <div class="toolbar-row">
        <input id="search" placeholder="🔍 搜索文件(支持多关键词)">
        <button id="theme-toggle">🌓</button>
    </div>
</div>

<script>
    // 删除 nginx autoindex 标题
    document.querySelectorAll('h1').forEach(h => h.remove());

    // 文件浏览器逻辑
    const MONTH = { Jan:'01', Feb:'02', Mar:'03', Apr:'04', May:'05', Jun:'06', Jul:'07', Aug:'08', Sep:'09', Oct:'10', Nov:'11', Dec:'12' };
    function getType(name){if(name.endsWith('/')) return 'folder'; const ext=name.split('.').pop().toLowerCase(); if(['png','jpg','jpeg','gif','svg','bmp'].includes(ext)) return 'img'; if(['js','ts','go','py','sh','json','yaml','yml','md','html','css','conf','ini','log'].includes(ext)) return 'code'; return 'file';}
    function formatSize(bytes){const n=Number(bytes); if(!n||isNaN(n)) return ''; if(n>=1024**3) return (n/1024**3).toFixed(2)+' G'; if(n>=1024**2) return (n/1024**2).toFixed(2)+' M'; return (n/1024).toFixed(2)+' K';}
    const rows=[];
    function transform(){
        const pre=document.querySelector('pre');
        if(!pre) return;
        const list=document.createElement('div'); list.className='list'; let row;
        pre.childNodes.forEach(node=>{
            if(node.nodeType===1){
                row=document.createElement('div'); row.className='row';
                const a=document.createElement('a'); a.href=node.href; a.textContent=node.text; a.className=getType(node.text);
                row.appendChild(a); list.appendChild(row); row._name=node.text.toLowerCase(); rows.push(row);
            }
            if(node.nodeType===3 && row){
                const text=node.nodeValue.replace(/\s+/g,' ').trim(); if(!text) return;
                const p=text.split(' '); if(p.length<2) return;
                const [day,mon,year]=p[0].split('-'); const time=p[1];
                const date=`${year}-${MONTH[mon]||mon}-${day} ${time}`;
                let size=''; if(p[2]&&p[2]!=='-'){ size=/^\d+$/.test(p[2])?formatSize(p[2]):p[2]; }
                const d=document.createElement('div'); d.textContent=date;
                const s=document.createElement('div'); s.textContent=size;
                row.append(d,s);
            }
        });
        pre.remove(); document.body.appendChild(list); renderBreadcrumb();
    }

    // 搜索功能
    document.getElementById('search').addEventListener('input', e=>{
        const keys=e.target.value.toLowerCase().split(/\s+/).filter(Boolean);
        rows.forEach(r=>{
            const hit=keys.every(k=>r._name.includes(k));
            r.style.display=hit?'grid':'none';
            const a=r.querySelector('a');
            if(!keys.length) a.innerHTML=a.textContent;
            else a.innerHTML=a.textContent.replace(new RegExp(`(${keys.join('|')})`,'ig'),'<mark>$1</mark>');
        });
    });

    // 面包屑
    function renderBreadcrumb(){
        const el=document.getElementById('breadcrumb');
        const parts=decodeURIComponent(location.pathname).split('/').filter(Boolean);
        let html=`<a href="/">🏠 root</a>`; let acc='';
        parts.forEach((p,i)=>{ acc+='/'+p; html+='<span>/</span>'; html+=i===parts.length-1?`<span class="current">${p}</span>`:`<a href="${acc}/">${p}</a>`; });
        el.innerHTML=html;
    }

    transform();

    // 三模式主题按钮
    const themeToggle = document.getElementById('theme-toggle');
    const darkQuery = window.matchMedia('(prefers-color-scheme: dark)');

    let mode = localStorage.getItem('theme') || 'auto'; // dark | light | auto

    function updateButtonIcon(m){
        if(m==='dark') themeToggle.textContent='🌙';
        else if(m==='light') themeToggle.textContent='☀️';
        else themeToggle.textContent='🌓';
    }

    function applyTheme(m){
        if(m==='dark') document.body.classList.add('dark');
        else if(m==='light') document.body.classList.remove('dark');
        else document.body.classList.toggle('dark', darkQuery.matches);
        updateButtonIcon(m);
    }

    // 初始化主题
    applyTheme(mode);

    // 系统主题变化(仅 auto 生效)
    darkQuery.addEventListener('change', e=>{
        if(mode==='auto') applyTheme('auto');
    });

    // 按钮循环切换模式:dark → light → auto → dark
    themeToggle.addEventListener('click', ()=>{
        if(mode==='dark') mode='light';
        else if(mode==='light') mode='auto';
        else mode='dark';
        applyTheme(mode);
        localStorage.setItem('theme', mode);
    });
</script>

<!-- 页脚固定底部 -->
<footer class="footer">
    <span>© 2026 <a href="https://github.com/buyfakett" target="_blank" class="footer-link">buyfakett</a>. All rights reserved.</span>
</footer>

</body>
</html>

隐藏版本号

# 不显示 openrestry 版本及信息
server_tokens off;
more_clear_headers 'Server';

伪造请求头

Tip

当我们需要有换域名的需求的时候,就可以使用这个配置(在不改变原来nginx的情况下)

当我们修改了任何一个header参数,别的header参数就不会生效,需要重新设置

proxy_set_header Host xxx.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;

mirror 指令

nginx_http_mirror_module模块特性

利用 mirror 模块,可以将线上实时流量拷贝至其他环境同时不影响源站请求的响应,因为 Nginx 会丢弃 mirror 的响应

mirror 模块可用于以下几个场景:

  • 通过预生产环境测试来观察新系统对生产环境流量的处理能力
  • 复制请求日志以进行安全分析
  • 如用户行为日志,客户和自己都需要一份

将生产环境的流量拷贝到预上线环境或测试环境的好处:

  • 可以验证功能是否正常,以及服务的性能;
  • 用真实有效的流量请求去验证,又不用造数据,不影响线上正常访问;
  • 这跟灰度发布还不太一样,镜像流量不会影响真实流量;
  • 可以用来排查线上问题;
  • 重构,假如服务做了重构,这也是一种测试方式;

Nginx的流量镜像是只复制镜像发送到配置好的后端,但是后端响应返回到nginx之后,nginx是自动丢弃掉的,这个特性就保证了镜像后端的不管任何处理不会影响到正常客户端的请求

流量镜像配置

mirror中不支持配置access_log,解决方法:mirror-location跳转到server,在server中配置access_log

基础配置
能获取日志配置
server {
    listen       80;
    server_name 192.168.1.1;

    location = /mirror1 {
        internal;
        #### address1 ####
        proxy_set_header Host mirror1.com;
        proxy_pass http://192.168.1.1:10001/api/service/list;
    }

    location = /mirror2 {
        internal;
        #### address2 ####
        proxy_set_header Host mirror2.com;
        proxy_pass http://192.168.1.1:10002/api/service/list;
    }

    location /api/service/list {
        access_log /data/logs/nginx/json_test_to_mirror.log json;
        mirror /mirror1;
        mirror /mirror2;
        proxy_pass http://192.168.1.1:8007;
    }

    location / {
        access_log /data/logs/nginx/json_test.log json;
        proxy_pass http://192.168.1.1:8007;
    }

}
server {
    # server没法设置为内部
    listen 172.168.1.58:10001;

    location / {
        internal;
        access_log /data/logs/nginx/json_testMirror1.log json;
        proxy_pass http://192.168.1.1:8008;
    }
 
}
server {
    # server没法设置为内部
    listen 192.168.1.1:10002;

    location / {
        internal;
        access_log /data/logs/nginx/json_testMirror2.log json;
        proxy_pass http://192.168.1.1:8009;
    }
 
}

获取请求ip服务

get_ip.conf
server {
	listen 80;
	server_name xxx.top;

	location / {
		access_log /data/logs/nginx/json_ip.log json;
		proxy_set_header Host $http_host;
        proxy_set_header X-Real-Ip $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://127.0.0.1:9999;
	}
}

server {
	listen 9999;

	location / {
		access_log off;
		# HTTP 响应头中添加 Date
		add_header Date $date_gmt;
		default_type application/json;
		return 200 "{\"ip\":\"$http_X_Real_Ip\"}";
	}
}

全局模板

nginx.conf

user  root;
worker_processes  2;            # 工作的进程

events {
    # 每个worker允许连接的客户端连接数
    worker_connections  102400;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format      main       '$remote_addr - $remote_user [$time_local] upstream=$upstream_addr,$upstream_response_time "$request" '
                                   '$status $body_bytes_sent "$http_referer" '
                                   '"$http_user_agent" "$http_x_forwarded_for" |req_body: $request_body' ' $request_time' ' $host';

    log_format      default    '$remote_addr - $remote_user [$time_local] "$request" '
                                   '$status $body_bytes_sent "$http_referer" '
                                   '"$http_user_agent" "$http_x_forwarded_for" ' $request_time' ' $host';


    log_format      json  escape=json  '{"time_local":"$time_local","host":"$host","uri":"$uri","status":"$status","request_method":"$request_method","upstream_addr":"$upstream_addr","query_string":"$query_string","request_body":"$request_body","resp_body":"$resp_body","http_Authorization":"$http_Authorization","content_length":"$content_length","content_type":"$content_type","upstream_response_time":"$upstream_response_time","request_length":"$request_length","server_protocol":"$server_protocol","body_bytes_sent":"$body_bytes_sent","http_user_agent":"$http_user_agent","request_time":"$request_time","http_set_cookie":"$http_set_cookie","resp_cookies":"$resp_cookies","resp_content_type":"$sent_http_content_type","resp_content_length": "$sent_http_content_length","http_x_forwarded_for":"$http_x_forwarded_for","remote_addr":"$remote_addr"}';


    client_body_temp_path /var/run/openresty/nginx-client-body;
    proxy_temp_path       /var/run/openresty/nginx-proxy;
    fastcgi_temp_path     /var/run/openresty/nginx-fastcgi;
    uwsgi_temp_path       /var/run/openresty/nginx-uwsgi;
    scgi_temp_path        /var/run/openresty/nginx-scgi;

    sendfile        on;

    keepalive_timeout  3000;

    server_names_hash_max_size 4096;
    server_names_hash_bucket_size 128;

    # 客户端上传数据大小
    client_max_body_size 200m;
    client_body_buffer_size 1024k;

    # 转发参数
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header REMOTE-HOST $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_http_version 1.1;
    proxy_intercept_errors on;
    proxy_connect_timeout 90;
    proxy_send_timeout 120;
    proxy_read_timeout 300;
    proxy_buffer_size 4m;
    proxy_buffers 8 1024k;
    proxy_busy_buffers_size 4m;
    proxy_temp_file_write_size 16m;
    proxy_next_upstream off;
    proxy_max_temp_file_size 128m;

    # 开启gzip
    gzip on;
    # 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
    gzip_min_length 1k;
    # gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间,后面会有详细说明
    gzip_comp_level 1;
    # 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到。
    gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml;
    # 是否在http header中添加Vary: Accept-Encoding,建议开启
    gzip_vary on;
    # 禁用IE 6 gzip
    gzip_disable "MSIE [1-6]\.";
    # 设置压缩所需要的缓冲区大小
    gzip_buffers 32 4k;
    # 设置gzip压缩针对的HTTP协议版本,没做负载的可以不用
    # gzip_http_version 1.0;
    # 不显示 openrestry 版本及信息
    server_tokens off;
    more_clear_headers 'Server';
    # 解决 "Transfer-Encoding: chunked" 问题
    chunked_transfer_encoding off;
    # Nginx Vhost Traffic Status 流量监控
    vhost_traffic_status_zone;

    server {
        lua_need_request_body on;
        set $resp_body "";
        set $resp_cookies "";
        body_filter_by_lua '
        local resp_body = string.sub(ngx.arg[1], 1, 1000)
        ngx.ctx.buffered = (ngx.ctx.buffered or "") .. resp_body
        if ngx.arg[2] then
        ngx.var.resp_body = ngx.ctx.buffered
        end
        ';
    }

    # 加载其他配置
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/conf.d/*/*.conf;


}

stream{
    include /etc/nginx/conf.d/*.stream;
    include /etc/nginx/conf.d/*/*.stream;
}

编写逻辑

获取请求参数

使用 $http_ 变量获取 header

使用 $arg_ 变量获取 URL 参数

# 判断多个参数示例
set $flagts 0;
if ( $arg_aaa ~ "^aaa" ) {
    set $flagts "${flagts}1";
}
if ( $arg_bbb ~ "^bbb" ) {
    set $flagts "${flagts}1";
}
if ( $flagts = "011" ) {
    return 200;
}

同端口走不同逻辑

server {
    listen 80;
    server_name xxx.com;
    
    location / {
        return 404;
    }
}
server {
    listen 80 default_server;
    server_name _;
    
    location / {
        return "Hello, World!";
    }
}