Django + UWSGI + NGINX 部署上线
原本简单部署后,日志里发现了好多漏扫以及各种报错,然后跟着AI折腾了大半天,终于重新部署完成,记录下。 1. UWSGI 参考配置文件 # UWSGI.INI 参考配置文件 [uwsgi] # --- 用户权限 --- uid = www-data gid = www-data # Django 项目配置 chdir = /srv/www/codenotes/ # Django 的 WSGI 入口文件 module = codenotes.wsgi # 虚拟环境路径 virtualenv = /srv/work_env/codenotes_env # python 插件 plugins = /usr/lib/uwsgi/plugins/python3 # 主进程 master = true # 工作进程数量:通常设置为 CPU 核心数 processes = 2 # 每个进程的线程数 threads = 2 # 显式启用线程支持 enable-threads = true # --- 通信配置 (Unix Socket 高性能)--- socket = /var/run/uwsgi/codenotes.sock # 设置 socket 权限,确保 Nginx 用户(通常是 www-data)可以读取 chmod-socket = 660 # 兜底权限 chown-socket = www-data:www-data # 当 uwsgi 停止时,删除 socket 文件 vacuum = true # --- 资源限制与稳定性 (防止内存泄漏) --- # 每个工作进程在处理指定数量的请求后自动重启 max-requests = 5000 # 重启进程时的平滑过渡,避免请求丢失 max-requests-delta = 500 # 如果单个请求超过 60 秒,杀死该进程 harakiri = 90 # 内存超1024MB自动重启 reload-on-rss = 1024 # 防止请求队列堆积导致 OOM listen = 100 # --- 日志配置 --- # 日志文件路径 logto = /srv/log/uwsgi.log # 日志轮转大小 30M,防止日志文件无限增长 log-maxsize = 31457280 # 日志级别 log-level = warning # 记录客户端IP log-x-forwarded-for = true # 建议:开启日志轮转时的信号处理,平滑切割 log-reopen = true # --- 其他性能优化 --- # 主进程加载应用,节省内存 lazy-apps = true buffer-size = 32768 # 静态文件转发缓冲区 post-buffering = 4096 ignore-sigpipe = true ignore-write-errors = true # --- 进程管理 --- # pidfile = /var/run/uwsgi/codenotes_uwsgi.pid # 优雅退出 die-on-term = true disable-logging = false # 【新增】优雅处理中断 thunder-lock = true 2. UWSGI 配置文件链接 # 配置文件链接至 etc/uwsgi/apps-enabled 目录 ln -s /srv/www/codenotes/uwsgi.ini /etc/uwsgi/apps-enabled/uwsgi.ini 3. 配置 UWSGI 自启动服务, 文件目录: /etc/systemd/system/codenotes.service 创建并配置完成后,重新加载下: systemctl daemon-reload 参考配置如下: [Unit] Description=uWSGI Emperor for CodeNotes Django Project Documentation=man:uwsgi(1) # 确保在网络就绪后启动,且必须在 Nginx 之前启动 After=network.target network-online.target syslog.target Wants=network-online.target [Service] # 强制服务以 root 启动 (这样 ExecStartPre 和主进程都有权限) User=root Group=root # 允许写入 /var/run ReadWritePaths=/var/run # 预执行命令 ExecStartPre=/bin/mkdir -p /var/run/uwsgi ExecStartPre=/bin/chown -R www-data:www-data /var/run/uwsgi ExecStartPre=/bin/chmod 755 /var/run/uwsgi # --- 启动命令 --- # 有链接配置文件到 /etc/uwsgi/apps-enabled 目录时,可以不需要下面方式,要不然会多启动一个。 # 方式 A: 直接指定 ini 文件路径 (推荐) # ExecStart=/srv/work_env/codenotes_env/bin/uwsgi --ini /srv/www/codenotes/uwsgi.ini # 方式 B: 如果使用全局安装的 uwsgi (非虚拟环境),则改为: # ExecStart=/usr/bin/uwsgi --ini /srv/www/codenotes/uwsgi.ini # 注意:请确保 ExecStart 指向的是真实的 uwsgi 二进制文件路径。 # 如果不确定,可以在终端运行 'which uwsgi' 或激活虚拟环境后运行 'which uwsgi' 查看路径。 # --- 重启策略 --- # 当进程意外退出时自动重启 Restart=always RestartSec=5s # --- 环境变量 (可选) --- # 如果 Django 需要特定的环境变量,可以在这里添加 Environment="DJANGO_SETTINGS_MODULE=codenotes.settings" Environment="PATH=/srv/work_env/codenotes_env/bin" # --- 资源限制 (可选,防止内存泄漏拖垮系统) --- # LimitNOFILE=65535 # LimitNPROC=65535 # --- 日志处理 --- # 将输出发送到 systemd journal (可以通过 journalctl -u uwsgi-codenotes 查看) StandardOutput=journal StandardError=journal # 如果你更倾向于 uwsgi.ini 中配置的 logto 文件,可以注释掉上面两行,或者保留它们作为备份 # --- 安全加固 --- NoNewPrivileges=true ProtectSystem=strict ProtectHome=true # 允许写入日志目录和 socket 目录 (需要根据实际路径调整 ReadWritePaths) ReadWritePaths=/srv/log /var/run/uwsgi /srv/www/codenotes [Install] # 开机自启目标 WantedBy=multi-user.target 4. Nginx 参考配置 # -------------------------- # 全局上游定义 (Upstream) # -------------------------- upstream django_codenotes { # 修改为 Unix Socket 路径 # 必须与你 uwsgi.ini 中的 socket 路径完全一致 server unix:/var/run/uwsgi/codenotes.sock; } # 定义恶意路径匹配规则 (大小写不敏感) # 如果 URI 匹配到以下任意正则,$block_scanner 变为 1,否则为 0 map $request_uri $block_scanner { default 0; # 常见后台/管理路径 ~*^/(wp-admin|wp-login|administrator|phpmyadmin|pma|mysql|webadmin|shell|cmd|console|manager|login|signin|auth|api) 1; # 常见敏感文件/备份 ~*^/.(php|git|svn|htaccess|htpasswd|env|config|bak|backup|sql|tar|gz|zip|rar|7z) 1; # 常见漏洞利用路径 ~*^/(solr|actuator|eureka|swagger|api-docs) 1; ~*^/(cgi-bin|scripts|bin|tmp) 1; ~*\.(asp|aspx|jsp|jspx|php5|phtml|pl|py|rb|sh|exe|bat|cmd)$ 1; # 特定漏洞探测字符串 ~*\$(\{|%24\{) 1; # Log4j JNDI 特征 ~*union.*select 1; # 基础 SQL 注入探测 ~*(\.\.\/|\.\.%2f|\%2e\%2e\/|\%2e\%2e\%2f) 1; # 目录遍历 } # -------------------------- # 1. HTTP 80 端口:强制跳转 HTTPS + 去除 www # -------------------------- server { listen 80; listen [::]:80; server_name codenotes.cn www.codenotes.cn ; # 逻辑: # 1. 如果是 www,跳转到非 www 的 https # 2. 如果不是 www,直接跳转到 https if ($host = www.codenotes.cn) { return 301 https://$host$request_uri; } # managed by Certbot if ($host = codenotes.cn) { return 301 https://$host$request_uri; } # managed by Certbot # 关闭日志(跳转请求无需记录) access_log off; log_not_found off; } # -------------------------- # HTTPS: codenotes.cn # -------------------------- server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name codenotes.cn; # 拦截恶意扫描路径 if ($block_scanner) { return 444; # 444表示直接关闭连接,不返回任何响应 } # 限制 HTTP 方法 (只允许常用方法) if ($request_method !~ ^(GET|POST|HEAD|OPTIONS|PUT|DELETE|PATCH)$) { return 405; } # SSL 证书 ssl_certificate /etc/letsencrypt/live/codenotes.cn/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/codenotes.cn/privkey.pem; # managed by Certbot # 引入通用 SSL 优化配置 (也可写在里面) ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; # OpenSSL 3.0 兼容的加密套件(优先 TLS1.3 套件,兼顾 TLS1.2) ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; # 曲线名 ssl_ecdh_curve X25519:prime256v1:secp384r1; ssl_session_timeout 1d; ssl_session_cache shared:SSL:50m; ssl_session_tickets off; # HSTS add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # 安全隐藏版本号 server_tokens off; # --- 性能优化:静态资源处理 --- # 开启零拷贝 sendfile on; tcp_nopush on; tcp_nodelay on; # 开启 Gzip 压缩 (显著减少传输体积) gzip on; gzip_vary on; gzip_min_length 1024; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml; # 静态文件 (Static) location /static/ { alias /srv/www/codenotes/static/; expires 30d; add_header Cache-Control "public, max-age=2592000" always; add_header X-Content-Type-Options nosniff always; access_log off; # 静态文件不记录日志,提升 IO 性能 } # 媒体文件 (Media) location /media/ { alias /srv/www/codenotes/media/; expires 30d; add_header Cache-Control "public, max-age=2592000" always; access_log off; } # 动态请求 (Django/uWSGI) location / { # 使用 uWSGI 协议 uwsgi_pass django_codenotes; include uwsgi_params; # uWSGI 专用参数设置 (比 proxy_set_header 更准确) uwsgi_param Host $host; uwsgi_param X-Real-IP $remote_addr; uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for; uwsgi_param X-Forwarded-Proto $scheme; # 优化 Buffer,避免大请求写入临时文件 uwsgi_buffer_size 128k; uwsgi_buffers 8 256k; uwsgi_busy_buffers_size 512k; # 超时设置 (根据业务调整,防止长任务被切断) uwsgi_read_timeout 60s; uwsgi_send_timeout 60s; uwsgi_connect_timeout 60s; } # 基础配置 client_max_body_size 5M; # 适当增大上传限制 charset utf-8; access_log /srv/log/codenotes.access.log main if=$block_scanner=0; error_log /srv/log/codenotes.error.log warn; } # -------------------------- # HTTPS: www 子域名跳转 # -------------------------- # www.codenotes.cn -> codenotes.cn server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name www.codenotes.cn; # 拦截恶意扫描路径 if ($block_scanner) { return 444; # 444表示直接关闭连接,不返回任何响应 } # 限制 HTTP 方法 (只允许常用方法) if ($request_method !~ ^(GET|POST|HEAD|OPTIONS|PUT|DELETE|PATCH)$) { return 405; } # 必须加载 codenotes 的证书,否则浏览器会报证书不安全,无法完成跳转 ssl_protocols TLSv1.2 TLSv1.3; ssl_certificate /etc/letsencrypt/live/codenotes.cn/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/codenotes.cn/privkey.pem; # managed by Certbot # 301 永久跳转到非 www return 301 https://codenotes.cn$request_uri; } 请一定注意各文件及文件夹的用户及权限,尤其像 /var/run/、 日志文件及文件夹等等,在这块折腾了好久… 发现 /var/share/uwsgi/conf 下还有个default.ini ,依据上述配置,会把这个也起来,删除后,需要把自启动服务里的 # ExecStart=/usr/bin/uwsgi --ini /srv/www/codenotes/uwsgi.ini 备注取消!!要不然会异常!! 验证各服务后完成部署。