ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Nginx+Django生产部署全流程:WSGI、Gunicorn与排错实战

Nginx+Django生产部署全流程:WSGI、Gunicorn与排错实战 项目标题是一篇搞懂nginx与Django部署这个组合在国内的中小型Web项目里出镜率极高几乎每个从“本地runserver能跑”迈向“线上能扛住访问”的Python开发者都得过这道关。我前后在十几台服务器上反复折腾过这套组合从最初被502折腾到凌晨到后来能在半小时内把一套新项目稳稳当当地架起来中间踩的坑足够写一本小册子。这篇就把整个流程从头到尾拆开讲不管是刚接触Django的新手还是想把手上项目正经部署起来的老手都能直接照着做。核心关键词就三个nginx、Django、部署全文围绕它们展开不绕弯子。1. 部署架构怎么选先把数据流向理清楚1.1 为什么Django不能直接对外从WSGI说起很多人第一次写Django用的是python manage.py runserver浏览器一打开127.0.0.1:8000就能看到页面于是产生一个错觉这东西本来就能跑为什么还要多装一个Nginx答案藏在runserver的实现里。Django自带的这个开发服务器是单进程、单线程后来加了--nothreading相关选项才略有变化、没有并发保护、没有静态资源优化、没有任何安全加固的调试工具官方文档里反复强调它“不适用于生产环境”。你把它直接暴露到公网随便一个爬虫或者压测工具发来几百个并发请求进程就卡死了更别提它还会把详细的错误堆栈打印到页面上暴露项目路径、依赖版本甚至部分配置信息。那Django在生产环境里靠什么对外服务答案是通过WSGI协议交给专门的WSGI服务器。WSGI全称Web Server Gateway Interface是Python定义的一套“Web服务器”和“Web应用”之间的调用规范。Django本身实现了这个规范的应用端接口而Gunicorn、uWSGI、Waitress这些工具实现了服务器端负责管理进程池、处理并发连接、把HTTP请求翻译成Django能理解的对象。你可以把它理解成餐厅Django是后厨的厨师只负责做菜不负责站在门口迎客、排号、递菜单。真正站在门口的那个“服务员”就是Nginx。这里顺便说一句MTV模式。Django的MTVModel-Template-View里View才是接收请求、返回响应的那个环节它和WSGI服务器之间通过一个可调用对象通常叫application连接起来。理解了这层关系你就明白为什么部署时出的问题一半在Nginx那边一半在WSGI服务器和Django的配置这边两边各管一段排查时必须先确定故障出在哪一段。1.2 Nginx在整套架构里到底干了什么Nginx在这个架构里扮演的角色很多人简单概括成“反向代理”但实际职责远不止转发请求这么简单。我把它拆成五件事来看第一反向代理与请求分发。浏览器访问的是Nginx监听的80或443端口Nginx根据location规则决定这个请求是直接返回静态文件还是转发给后端的Gunicorn。域名、路径、请求头都在这一层做判断Django那边完全不用关心。第二静态资源直出。Django处理一个CSS文件请求要经过完整的中间件链路而Nginx读一个文件几乎是内存级操作。生产环境里把/static/和/media/交给Nginx直接返回是整个部署里性价比最高的一次优化页面加载速度的差距是数量级的。第三并发连接管理与慢请求缓冲。Nginx基于事件驱动模型一个worker能扛住成千上万的并发连接。它先把客户端请求完整接收下来再转发给后端这样即使客户端网络很慢也不会长时间占着后端进程。这个特性在处理移动端弱网请求时特别有用。第四安全边界。限流、限制请求体大小、屏蔽恶意User-Agent、隐藏后端真实端口、统一处理HTTPS这些都在Nginx层完成后端服务只需要监听127.0.0.1根本不暴露在公网上。第五日志与监控入口。Nginx的access log和error log是最可靠的流量记录来源排障时第一时间就是看这两个文件。把这五件事想明白配置Nginx时你就不会照抄网上的模板而是知道每一行在干什么。1.3 两种主流组合GunicornNginx 与 uWSGINginxLinux环境下最主流的两套组合是GunicornNginx和uWSGINginx。Gunicorn的优势是配置简单、纯Python实现、和Django的兼容性好、进程管理直观同步worker模型对绝大多数业务足够用uWSGI性能上限更高、功能更全但配置项繁多参数之间还有依赖关系新手容易配错。我的建议是除非你有明确的性能压测数据表明Gunicorn不够用否则优先选Gunicorn把精力花在业务上而不是调参上。如果是Windows Server环境情况就不一样了。Gunicorn依赖fcntl等Unix系统调用在Windows上跑不起来这时候常见的替代方案是Waitress它是一个纯Python的WSGI服务器跨平台配置极简配合Nginx for Windows或者直接用它监听端口都可以。不过Windows下Nginx的并发能力和稳定性都不如Linux如果是正式生产环境我还是建议上一台Linux机器Windows更多用于内网系统或者过渡阶段。还有一类项目走的是ASGI路线比如用了Django Channels做WebSocket、长连接推送这时候要用Uvicorn或DaphneNginx配置里的Upgrade和Connection头转发就成了必需项后面配置章节会提到。2. 环境准备与依赖安装的实际操作2.1 服务器基础环境与Nginx安装方式选择拿到一台干净的Linux服务器第一步不是急着装Nginx而是先把系统基础环境理顺。我习惯先做四件事更新系统包索引、确认时区、配置好防火墙规则、创建一个专用的非root用户来跑应用。用root跑Web服务是安全大忌一旦应用被攻破攻击者直接拿到系统最高权限。Nginx的安装方式主要有三种选择哪种取决于你的网络环境和运维习惯安装方式适用场景优点缺点系统包管理器内网、离线环境、追求稳定一条命令搞定自动处理依赖和开机自启版本偏旧某些新指令不支持官方预编译包仓库需要较新版本、有外网访问版本新升级方便需要额外配置软件源源码编译需要自定义模块、极致控制可裁剪模块定制性强步骤多升级麻烦依赖需手动装对绝大多数项目系统包管理器就够用。比如在基于RPM的发行版上执行dnf install nginx或者在基于Debian的发行版上执行apt install nginx装完之后用systemctl enable --now nginx启动并设置开机自启。这里有个容易忽略的点有些内网服务器是完全离线的包管理器拉不到源这时候就需要提前在有网的机器上下载好对应的rpm或deb包及其依赖用离线方式安装。这个过程没什么技巧关键是用--downloadonly先把依赖一并下载下来否则装到一半报缺依赖会非常痛苦。装完之后先别急着改配置用nginx -v看一下版本再用nginx -t测试默认配置是否正常。有些人装完发现80端口被占用多半是系统里已经有别的Web服务在跑比如Apache或者某个默认站点先用ss -lntp | grep :80确认一下。2.2 Python虚拟环境与项目依赖的冻结Django项目强烈建议跑在虚拟环境里原因很简单不同项目对同一个库的版本要求经常冲突全局安装迟早出乱子。我习惯在项目目录下用python3 -m venv venv创建激活后用pip install -r requirements.txt安装依赖。这里有个非常实用的操作在开发机上先冻结依赖清单。执行pip freeze requirements.txt把这个文件一起传到服务器。这样做的好处是版本锁定避免“开发环境好好的服务器上装完就报错”这种经典问题。如果你的项目里有些包只在开发时需要比如调试工具、测试框架可以用requirements-dev.txt单独分离出来生产环境只装主清单。生产环境的依赖清单里WSGI服务器本身要加进去比如gunicorn21.x.x。数据库驱动也要注意MySQL项目通常用mysqlclient或PyMySQL前者性能好但需要系统里装了MySQL的开发头文件编译时会用到后者是纯Python实现部署省事但性能略低。我一般优先选mysqlclient在安装前先装好mysql-devel或者default-libmysqlclient-dev这类系统包能省掉很多编译报错。还有一个细节虚拟环境的Python版本要和开发时保持一致。Django 4.x要求Python 3.8以上Django 5.x要求Python 3.10以上版本不匹配会出现一些莫名其妙的语法错误。部署前用python3 --version和python --version分别确认一下特别是系统里同时装了多个Python版本的情况。2.3 数据库与缓存服务的准备Django项目跑起来之前数据库得先能连上。以MySQL为例需要提前做三件事创建数据库、创建专用用户并授权、确认字符集配置。字符集这块我踩过坑早期用默认的latin1存中文直接乱码后来统一在创建数据库时指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci一劳永逸。utf8mb4比utf8多支持四字节字符能完整存下表情符号现在的项目没有理由还用三字节的utf8。授权的时候不要图省事给应用账号ALL PRIVILEGES加%通配主机。正确的做法是限定到具体的主机地址权限只给业务需要的SELECT, INSERT, UPDATE, DELETE迁移时临时用高权限账号执行执行完切回来。这是安全基线别嫌麻烦。如果项目用到Redis做缓存或者消息队列同样要确认服务已启动、密码配置正确、bind地址限制在内网。Django里的缓存配置我通常写成环境变量读取的形式这样开发、测试、生产三套环境不用改代码。数据库连接数也值得提前规划。Django每个Gunicorn worker都会维持自己的数据库连接池worker数量乘以每连接的连接数就是峰值。MySQL默认max_connections是151如果worker开到10个以上就要留意了要么调大数据库上限要么给Django加上连接复用CONN_MAX_AGE减少频繁建连的开销。这个参数设成60左右比较稳妥设太大又可能遇到连接被数据库主动断开的问题。3. Django项目的生产化改造3.1 settings.py里必须改的几个配置项开发环境的settings.py如果原封不动搬到生产基本等于把家门钥匙挂在门上。下面这几个配置是必改项我一条条说原因。DEBUG必须设为False。这个不用多解释开着DEBUG意味着任何报错都会把完整堆栈、配置信息、SQL语句暴露给访问者。改成False之后还要正确配置ALLOWED_HOSTS否则Django会直接拒绝所有请求返回400。ALLOWED_HOSTS里填域名和服务器IP不要用[*]图省事那等于把Host头校验关掉了会带来缓存投毒等风险。SECRET_KEY不能硬编码在代码里也不要用开发时自动生成的那一串。正确做法是从环境变量或者独立的配置文件读取这个文件不纳入版本控制。很多人把项目代码传到代码托管平台时忘了处理这个导致密钥泄露后果是很严重的。STATIC_ROOT必须显式设置。开发时Django会自动从各个app的static目录找静态文件生产环境不这么做需要先用python manage.py collectstatic把所有静态文件汇总到STATIC_ROOT指定的目录然后交给Nginx直接服务。这个目录我习惯放在项目根目录下的static_collected或者/var/www/项目名/static。MEDIA_ROOT和MEDIA_URL同样要配置用户上传的文件都落在这里。注意一点MEDIA目录的权限要允许运行应用的Linux用户读写否则上传功能会直接报权限错误。我一般把MEDIA目录的属主改成跑Gunicorn的那个用户。数据库配置、缓存配置、日志配置建议全部走环境变量或独立的settings_prod.py用DJANGO_SETTINGS_MODULE环境变量切换。这样本地开发和线上部署共用一套代码不会出现“改配置改错文件”的事故。3.2 static与media的分离与收集静态文件和媒体文件是两回事部署时经常被混为一谈。静态文件是随代码发布的CSS、JS、图片、字体内容基本不变媒体文件是用户上传的内容随时在增加。分开处理的原因是它们的缓存策略、备份策略、权限要求都不一样。collectstatic这个命令的执行时机有讲究。它必须在每次代码更新、依赖更新之后执行因为新版前端资源可能改动了文件名或者内容。执行前确认STATIC_ROOT是空目录或者可以安全覆盖Django默认会提示覆盖确认脚本化部署时加上--noinput参数跳过交互。收集完之后可以用ls看一下目录结构确认文件确实都过去了。常见问题是有app把静态文件放在非标准路径下collectstatic找不到。这时候要在settings.py里用STATICFILES_DIRS显式声明额外的查找路径。媒体文件方面如果项目规模不大直接让Nginx服务本地目录就够。规模上来了、或者做了多机部署之后本地磁盘方案就撑不住了同一张图片在不同机器上不一致用户体验会崩。这时候要么上对象存储要么用NFS之类的共享存储。这个决策点建议在项目早期就想清楚后期迁移成本不小。还有个小细节值得注意Django的static模板标签在DEBUGFalse且没配STATIC_ROOT的情况下会静默失败页面能打开但样式全无排查时容易一头雾水。所以遇到“页面结构在但没样式”的问题第一反应就是检查静态文件路径。3.3 用systemd把Gunicorn做成守护进程在命令行直接敲gunicorn启动一旦你关掉SSH会话进程就跟着没了。生产环境必须用进程管理器托管systemd是Linux上的标准选择。一个可用的service文件大致长这样[Unit] DescriptionGunicorn daemon for myproject Afternetwork.target [Service] Userwww-data Groupwww-data WorkingDirectory/srv/myproject EnvironmentDJANGO_SETTINGS_MODULEmyproject.settings_prod ExecStart/srv/myproject/venv/bin/gunicorn \ --workers 3 \ --bind unix:/run/myproject.sock \ --timeout 60 \ --access-logfile - \ --error-logfile - \ myproject.wsgi:application Restartalways RestartSec3 [Install] WantedBymulti-user.target几个参数的解释这些都是我在实际调优中总结出来的--workers的数量经验公式是CPU核心数 × 2 1。比如2核机器开5个worker。这个公式的来源是假设一半时间在等IO那么每个核心上跑2个进程能充分利用CPU多出来的1个用于应对突发。但这不是铁律如果你的项目是CPU密集型比如大量图片处理、复杂计算worker数就该逼近核心数而不是两倍如果是IO密集型大量数据库查询、外部接口调用可以适当开多些。--bind用Unix socket而不是127.0.0.1:8000性能略好也避免了端口占用冲突。socket文件放在/run下这个目录重启会清空systemd每次启动都会重建正好。--timeout默认是30秒对于有慢接口的项目太短了会导致worker被强杀然后返回502。我一般设到60秒同时确认Nginx那边的proxy_read_timeout不小于这个值两边要配套。--access-logfile -表示把访问日志输出到标准输出由systemd的journal统一收集用journalctl -u myproject就能看到比单独配日志文件更好管理。写完service文件后systemctl daemon-reload重载配置systemctl enable --now myproject启动并开机自启。改完代码重启服务用systemctl restart myproject。这里提醒一点改了service文件必须daemon-reload不加这一步改动不生效我见过有人反复重启服务却以为配置写错了其实就是漏了这一条命令。4. Nginx配置逐行拆解4.1 反向代理核心配置与常见参数Nginx的配置文件通常是/etc/nginx/nginx.conf加上/etc/nginx/conf.d/或者/etc/nginx/sites-enabled/下的站点配置。我习惯每个项目单独一个配置文件放在conf.d/下主配置里用include引入这样多项目共存时互不干扰。一个基础的反向代理配置server { listen 80; server_name example.com www.example.com; client_max_body_size 20m; location /static/ { alias /srv/myproject/static_collected/; expires 30d; add_header Cache-Control public, immutable; } location /media/ { alias /srv/myproject/media/; expires 7d; } location / { proxy_pass http://unix:/run/myproject.sock; 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 X-Forwarded-Proto $scheme; proxy_redirect off; proxy_read_timeout 60s; proxy_connect_timeout 10s; } }逐项说明。client_max_body_size默认只有1M用户上传超过1M的文件就会收到413错误这是新手最常撞的墙之一。设成20m还是100m看业务但要注意这个值只是Nginx允许的大小Django那边还有DATA_UPLOAD_MAX_MEMORY_SIZE和FILE_UPLOAD_MAX_MEMORY_SIZE两个限制大文件上传的场景要一起调。proxy_set_header Host $host这行非常重要。如果不传Django收到的Host是后端socket的地址配合ALLOWED_HOSTS校验就会返回400。X-Real-IP和X-Forwarded-For用于让Django知道真实客户端IP否则所有请求的来源IP都是127.0.0.1日志分析和限流都没法做。Django侧要配合使用SECURE_PROXY_SSL_HEADER和中间件才能正确解析这块配不好会导致CSRF校验失败表现为表单提交报403。X-Forwarded-Proto在配置了HTTPS之后必须传否则Django会认为请求是HTTP的生成的回调地址、重定向地址都会是http开头在强制HTTPS的站点上会形成无限重定向。alias和root的区别值得单独说。root是把location的路径拼接到目录后面alias是直接替换。上面配置里location是/static/用alias指向static_collected/访问/static/css/app.css时Nginx实际读的是/srv/myproject/static_collected/css/app.css。如果用root /srv/myproject/static_collected/实际读的会变成/srv/myproject/static_collected/static/css/app.css多了一层直接404。这个坑非常常见。4.2 静态资源直出与缓存策略静态资源的缓存策略直接影响用户感知速度。我的经验配置是带哈希指纹的构建产物比如app.a3f9c2.js设长缓存一年都不过分因为文件名变了就是新文件不带指纹的比如logo、favicon设短缓存一天到一周。expires 30d配合add_header Cache-Control public, immutable能让浏览器在缓存期内完全不发请求。immutable这个指令告诉浏览器即使用户手动刷新也不要重新验证适合确定不会变的资源。这里要注意add_header的一个特性如果某个location里出现了add_header它会覆盖掉上级作用域的同类头而不是叠加。如果你在server块里定义了安全相关的头比如X-Content-Type-Options又在location里加了缓存头安全头在静态资源响应里就丢了。解决方法是把安全头在每个location里重复声明或者用include引入一个公共片段。另外Nginx默认会对静态文件开启sendfile和tcp_nopush这两个默认值是开着的对静态资源吞吐提升明显一般不用动。但如果你的静态文件放在网络挂载的存储上sendfile可能反而拖慢速度那时候要关掉。压缩也是必开的。gzip on;加上gzip_types text/css application/javascript application/json;能显著减小传输体积。注意不要对图片、视频这类已经压缩过的格式再开gzip纯属浪费CPU。较新的Nginx版本还支持Brotli压缩率更高但需要编译模块看情况取舍。4.3 多项目共存与HTTPS配置一台服务器跑多个Django项目是很常见的需求。做法是每个项目一个server块用不同的server_name区分或者同一个域名下用不同的路径前缀区分。用域名区分更干净配置也简单。如果要用路径前缀区分比如/app1/和/app2/要注意Django那边的FORCE_SCRIPT_NAME或者SCRIPT_NAME设置否则Django生成的所有URL都不会带前缀前端资源全部404。这个问题调试起来很烦建议除非有强需求否则还是用独立域名或子域名。HTTPS配置方面正式环境用正规证书签发机构的证书配置大概是这样server { listen 443 ssl; http2 on; server_name example.com; ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # 其余location配置同上 } server { listen 80; server_name example.com; return 301 https://$host$request_uri; }内网测试环境没有正规证书可以用自签名证书。生成方式用一行命令就行openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt -subj /CNyour-domain。自签名证书浏览器会报不安全警告正式环境不要用测试环境可以手动信任。配置完记得用nginx -t测试语法通过后再nginx -s reload平滑重载。永远不要直接restart生产环境正在服务的Nginxreload是平滑的不会中断现有连接。还有一个常见问题配置文件的权限和证书路径的可读性Nginx的worker进程以非root用户运行如果证书文件权限是600且属主是rootNginx读不到会启动失败报错信息里会说权限被拒绝。把证书文件设成644、目录设成755即可。5. 踩坑实录部署后最常见的几类问题排查5.1 502与504先分清是谁的锅502 Bad Gateway和504 Gateway Timeout是部署后最高频的两个错误含义不同排查方向也不同。502表示Nginx成功连上了后端但后端返回了无效响应或者根本没响应就断开了。常见原因有三个后端进程挂了用systemctl status看服务状态、socket文件路径不匹配或者权限不对Nginx的worker用户要能读写这个socket、后端启动时报了异常直接退出用journalctl -u 服务名 -n 100看最后一百行日志。我遇到过最常见的一种是改了代码之后忘了重启GunicornPython是解释型语言代码改了不重启服务是不会生效的但同时又因为某些原因进程重启失败变成新旧混杂的诡异状态。504表示Nginx等了超过超时时间后端还没返回。这时候要查的是慢在哪是数据库查询慢还是外部接口调用慢。把Nginx的proxy_read_timeout和Gunicorn的--timeout调大只是权宜之计根本解法是找出那个慢操作。我习惯在Nginx日志里加上$request_time和$upstream_response_time前者是客户端感知的总耗时后者是后端处理耗时。两个数一对比就知道慢在网络还是慢在后端。还有一种容易被忽略的情况多个worker同时处理长请求把worker池占满新请求排队等待表现就是间歇性的502和整体变慢。这种问题看日志是有规律的往往集中在某个时间点对应某个定时任务或者批量接口。解法是把长耗时任务挪到异步队列里处理或者单独给这类接口分配资源。5.2 静态文件404与样式丢失页面能打开但样式全无、控制台一堆404这个问题的排查顺序我固定为四步第一确认collectstatic执行过且STATIC_ROOT指向的目录里确实有文件。用ls -la看一眼是空的就说明收集没成功。第二确认Nginx配置里静态资源的alias路径和STATIC_ROOT完全一致注意结尾斜杠。第三确认Nginx进程有权限读取这些文件。Linux的权限问题经常是“文件在、路径对、就是读不到”用sudo -u nginx-worker-user cat 文件名模拟检查一下。第四确认DEBUGFalse之后Django不再自动服务静态文件这是设计行为不是bug。有人反复改Django配置想让它继续服务静态文件方向就错了。另一个变体是CSS、JS能加载但字体文件404这是跨域问题。字体文件的请求会受到CORS限制Nginx里需要给字体文件加上Access-Control-Allow-Origin头或者把字体和页面放在同一个域下。这个问题在用了CDN之后特别常见。媒体文件的404还有个专属原因用户上传目录的权限。Django以某个用户身份运行如果这个用户对MEDIA_ROOT没有写权限上传会失败如果Nginx的worker用户没有读权限已上传的文件访问会404。两个权限都要检查。5.3 大文件上传、流式响应与响应头设置大文件相关的问题集中在两类上传被拦截、下载体验差。上传被拦截基本就是client_max_body_size没改返回413。改完Nginx还要看Django的两个参数DATA_UPLOAD_MAX_MEMORY_SIZE控制非文件字段的总大小FILE_UPLOAD_MAX_MEMORY_SIZE控制多大以内放内存、超过就写临时文件。这两个值配得不合理会导致内存暴涨或者上传失败。我的经验是把FILE_UPLOAD_MAX_MEMORY_SIZE设小一点比如2.5M让大文件都走磁盘临时文件避免多个并发上传把内存吃光。下载这边有个很典型的坑和StreamingHttpResponse有关。这个类的用途是边生成边发送适合大文件下载、实时数据推送。但Nginx默认会开启响应缓冲proxy_buffering on它会把后端返回的内容先攒在缓冲区里攒够了再发给客户端。这一缓冲流式响应就变成了“等全部生成完再一次性发出”用户看到的就是转圈半天然后突然下载完成完全失去了流式的意义。解法有两种。全局或者针对某个location关掉缓冲location /export/ { proxy_pass http://unix:/run/myproject.sock; proxy_buffering off; proxy_cache off; }或者在Django响应里加一个X-Accel-Buffering: no的响应头Nginx识别到这个头会自动关闭该响应的缓冲。后者更灵活因为可以按请求粒度控制只对需要流式的接口关缓冲其他接口继续享受缓冲带来的后端保护。StreamingHttpResponse的两个参数也值得说明。content_type决定了浏览器怎么处理这个响应普通下载用application/octet-streamCSV导出用text/csvJSON流用application/json。如果类型说错了浏览器可能尝试在线预览而不是下载或者直接显示成一堆乱码。content_disposition控制的是浏览器弹出的保存框设置attachment; filename报表.csv就会触发下载文件名可以自由指定。这里有个中文文件名的坑直接写中文在部分浏览器上会乱码标准做法是用filename*UTF-8加上URL编码后的文件名同时保留一个ASCII的filename作为兜底。这个细节不注意用户下载下来的文件名字就是一串问号。还有一个配套项是X-Content-Type-Options: nosniff加上它浏览器就不会自作主张去猜测文件类型安全性和一致性都更好。5.4 部署问题速查表把上面这些高频问题整理成一张表出问题时可以按图索骥现象最可能的原因排查命令或位置502 Bad Gateway后端进程未启动或socket路径/权限错误systemctl status 服务名、journalctl -u 服务名、检查socket文件权限504 Gateway Timeout后端处理超时对比Nginx日志中$request_time与$upstream_response_time页面无样式、控制台404静态文件未收集或Nginx alias路径错误检查STATIC_ROOT目录内容、对比Nginx配置路径返回400 Bad RequestALLOWED_HOSTS未包含当前域名或Host头未转发检查settings配置和proxy_set_header Host上传返回413client_max_body_size太小修改Nginx配置并reload表单提交403 CSRF失败HTTPS下X-Forwarded-Proto未传或SECURE_PROXY_SSL_HEADER未配检查Nginx头转发和Django安全配置流式响应变成整体返回Nginx响应缓冲未关闭加X-Accel-Buffering: no或关proxy_buffering中文文件名下载乱码Content-Disposition未做RFC 5987编码改用filename*UTF-8格式数据库连接报错连接数超限或CONN_MAX_AGE设置不当检查MySQLmax_connections和Django连接配置重启服务后配置未生效忘记daemon-reload或Python代码未重启systemctl daemon-reload后重启服务这张表覆盖了我在实际部署中90%以上的问题场景剩下的10%基本都是业务逻辑层面的和部署本身没关系了。关于部署这条路我个人最大的体会是动手之前先把数据流向在纸上画一遍。请求从浏览器出发经过DNS、Nginx、socket、Gunicorn、Django中间件、视图、数据库响应再原路返回每一段都有自己的职责和出错方式。把这条链路记住任何问题都能按段定位而不是漫无目的地改配置。另一个实用习惯是每次改动只改一个地方改完立刻验证同时改五处配置然后重启一旦出问题你根本不知道是哪处的锅。这套流程我在十几次部署里反复验证过虽然第一次配置花的时间长一些但后面维护和迁移的成本会低很多。
返回列表