在Web开发中,文件上传是一个基础但至关重要的功能。传统方案通常依赖后端语言处理,但借助nginx-upload-module模块,我们可以直接在Nginx层面实现高效的文件上传功能。这种方案特别适合静态资源服务器、CDN边缘节点等场景,能显著降低后端服务压力。
我最近在一个内部文件共享系统中实践了这种方案,单台4核8G的Nginx服务器轻松扛住了200+并发上传请求。下面将完整分享配置细节和踩坑经验。
nginx-upload-module是第三方模块,需要重新编译Nginx。推荐使用动态模块方式加载:
bash复制# 下载模块源码
wget https://github.com/fdintino/nginx-upload-module/archive/refs/tags/2.3.0.tar.gz
tar -zxvf 2.3.0.tar.gz
# 查看现有Nginx编译参数
nginx -V
# 重新编译(示例参数)
./configure --add-dynamic-module=../nginx-upload-module-2.3.0 \
--with-http_ssl_module \
--with-stream
make && make install
关键提示:生产环境建议保留原有编译参数,只追加新模块。动态模块方式更灵活,可通过
load_module指令加载。
合理的目录结构能避免权限问题:
code复制/root/nginxShare/
├── upload/ # 上传文件存储目录(755权限)
├── uploadfile.html # 前端上传页面
└── conf/ # 自定义配置文件目录
设置目录权限:
bash复制mkdir -p /root/nginxShare/upload
chown -R nginx:nginx /root/nginxShare
chmod 755 /root/nginxShare/upload
nginx复制user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
# 加载动态模块
load_module modules/ngx_http_upload_module.so;
events {
worker_connections 2048;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 上传相关全局配置
client_max_body_size 100m;
client_body_buffer_size 128k;
client_body_temp_path /var/cache/nginx/client_temp;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
# 上传服务器配置
server {
listen 80;
server_name upload.example.com;
# 上传页面
location / {
root /root/nginxShare;
index uploadfile.html;
}
# 文件上传接口
location /upload {
if ($request_method = GET) {
return 405;
}
upload_pass @upload_complete;
upload_store /root/nginxShare/upload;
upload_store_access user:rw group:r all:r;
# 文件信息回传
upload_set_form_field $upload_field_name.name "$upload_file_name";
upload_set_form_field $upload_field_name.path "$upload_tmp_path";
# 元数据收集
upload_aggregate_form_field $upload_field_name.md5 "$upload_file_md5";
upload_aggregate_form_field $upload_field_name.size "$upload_file_size";
upload_pass_form_field "^.*$"; # 透传所有表单字段
upload_cleanup 400 404 499 500-505;
}
# 上传完成处理
location @upload_complete {
# 可对接后端API或直接返回结果
add_header Content-Type text/plain;
return 200 "Upload success: $upload_file_name ($upload_file_size bytes)";
}
}
}
client_max_body_size
upload_limit_rate配合限制上传速度upload_store
upload_set_form_field
upload_cleanup
html复制<!DOCTYPE html>
<html>
<head>
<title>安全文件上传</title>
<style>
.upload-box {
max-width: 500px;
margin: 2rem auto;
padding: 2rem;
border: 1px dashed #ccc;
}
.progress {
height: 20px;
background: #f5f5f5;
margin-top: 1rem;
}
.progress-bar {
height: 100%;
background: #4CAF50;
width: 0%;
}
</style>
</head>
<body>
<div class="upload-box">
<h2>文件上传</h2>
<form id="uploadForm" action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="userfile" required>
<div class="form-group">
<label>文件描述:</label>
<input type="text" name="description">
</div>
<button type="submit">上传</button>
</form>
<div class="progress" id="progressContainer" style="display:none;">
<div class="progress-bar" id="progressBar"></div>
</div>
<div id="result"></div>
</div>
<script>
document.getElementById('uploadForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const xhr = new XMLHttpRequest();
// 显示进度条
document.getElementById('progressContainer').style.display = 'block';
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
const percent = (event.loaded / event.total) * 100;
document.getElementById('progressBar').style.width = percent + '%';
}
};
xhr.onload = function() {
document.getElementById('result').innerHTML =
`<div class="alert alert-success">${xhr.responseText}</div>`;
};
xhr.open('POST', this.action, true);
xhr.send(formData);
});
</script>
</body>
</html>
文件类型限制
nginx复制upload_pass_form_field "^content_type$";
upload_set_form_field $upload_field_name.content_type "$upload_content_type";
location @upload_complete {
if ($upload_content_type !~* "^(image/|application/pdf)") {
return 415 "Unsupported Media Type";
}
# 正常处理...
}
自定义文件名
nginx复制upload_store /root/nginxShare/upload 1;
upload_set_form_field $upload_field_name.new_name "${upload_file_md5}${upload_file_ext}";
限制HTTP方法
nginx复制limit_except GET POST { deny all; }
防盗链设置
nginx复制valid_referers none blocked server_names *.example.com;
if ($invalid_referer) {
return 403;
}
病毒扫描集成
bash复制location @upload_complete {
access_by_lua_block {
local scan = os.execute("/usr/bin/clamscan "..ngx.var.upload_tmp_path)
if scan ~= 0 then
ngx.exit(ngx.HTTP_FORBIDDEN)
end
}
}
IP速率限制
nginx复制limit_req_zone $binary_remote_addr zone=upload:10m rate=5r/s;
location /upload {
limit_req zone=upload burst=10;
# 其他配置...
}
文件扩展名过滤
nginx复制location @upload_complete {
if ($upload_file_name ~* "\.(php|jsp|sh)$") {
return 403 "Forbidden file type";
}
}
内核参数调整
bash复制# 增加文件描述符限制
echo "fs.file-max = 100000" >> /etc/sysctl.conf
sysctl -p
# 调整TCP堆栈
echo "net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.conf
磁盘IO优化
nginx复制aio on;
directio 4m;
output_buffers 4 256k;
连接池配置
nginx复制upstream upload_backend {
server 127.0.0.1:9000;
keepalive 32;
}
缓存策略
nginx复制proxy_cache_path /var/cache/nginx/upload levels=1:2 keys_zone=upload_cache:10m inactive=1h;
location @upload_complete {
proxy_cache upload_cache;
proxy_cache_valid 200 302 1h;
}
Prometheus监控配置
nginx复制location /metrics {
stub_status on;
access_log off;
}
日志格式优化
nginx复制log_format upload_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'upload_time=$request_time file="$upload_file_name" '
'size=$upload_file_size md5=$upload_file_md5';
自定义错误页面
nginx复制error_page 413 /error/413.html;
location = /error/413.html {
root /usr/share/nginx/html;
internal;
}
断点续传支持
nginx复制location /upload {
upload_resumable on;
upload_state_store /var/lib/nginx/upload_state;
}
症状:413 Request Entity Too Large
client_max_body_sizedf -hdmesg | grep nginx症状:权限拒绝
bash复制# 检查SELinux状态
getenforce
# 临时关闭
setenforce 0
# 或添加策略
chcon -R -t httpd_sys_content_t /root/nginxShare
慢查询定位
bash复制# 统计处理时间超过2秒的请求
awk '$NF > 2 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr
连接数监控
bash复制watch -n 1 "netstat -ant | grep ':80' | awk '{print \$6}' | sort | uniq -c"
高可用架构
灾备方案
bash复制# 每日增量同步
rsync -avz --delete /root/nginxShare/upload/ backup-server:/backup/upload/
自动化部署
yaml复制# Ansible示例
- name: Deploy upload config
template:
src: templates/upload.conf.j2
dest: /etc/nginx/conf.d/upload.conf
notify: reload nginx
经过多个项目的实践验证,这套方案在保证安全性的前提下,单节点可支持500+ QPS的文件上传。对于需要更高性能的场景,建议在前端增加分片上传功能,后端采用对象存储服务。