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 备注取消!!要不然会异常!!

验证各服务后完成部署。