简介:一份面向Linux运维与Django开发者的实战教程,针对在CentOS系统中使用宝塔面板部署Django项目这一典型需求,梳理了从环境准备到线上可访问的完整流程。压缩包内含单个PDF文档,大小139KB,内容以图文步骤为主,适合需要快速搭建生产环境的初中级开发者参考。目前已有2371人学习。教程覆盖基础环境搭建(安装宝塔、Python项目管理器、Nginx)、项目代码上传的两种方式,并明确推荐存放目录;通过Python项目管理器创建Django项目时,对项目名称、路径、Python版本、框架、启动方式(uwsgi)、启动文件(wsgi.py)、端口号及模块依赖安装等关键配置逐项说明;同时给出Nginx反向代理中static与media目录的映射方法,以及配置完成后的重启与重载步骤,可帮助读者避开常见部署坑点,实现Django项目的稳定上线。
1. 在 CentOS 与宝塔上部署 Django 项目,先别急着敲命令
Django 项目从本地跑通到远程可访问,中间隔着的不是代码,是环境配对。网上教程动辄让你配 uWSGI、写 XML 格式的 ini、再折腾 Nginx 正则,真按步骤走到一半,新手早就分不清是 Python 报错还是 Nginx 报错。而 CentOS + 宝塔这套组合,把部署路径压缩成了几条命令加几次点击,前提是你知道宝塔帮你做了什么、没做什么。这篇教程面向手里已有一个能本地运行的 Django 项目、想用宝塔面板快速把它推到生产环境的开发者,也适合被各种半截教程坑过的熟手回来对齐细节。先说结论:在宝塔面板上部署 Django,真正的工作量不在安装,而在路径、权限和进程守护这三件事上,这三件事理顺了,部署本身就结束了。
2. 初始化环境:CentOS 7.9 与宝塔面板的版本选型和安装
2.1 版本选型:CentOS 7.9、Python 版本与宝塔的兼容关系
部署 Django 遇到的第一个坑往往不是 Django 本身,而是操作系统与 Python 版本的选择。CentOS 7 系列里目前最稳妥的选择是 CentOS 7.9,原因是它的软件源里默认带的 Python 是 2.7,而宝塔面板安装时会额外拉取自己的 Python 环境,这几者之间经常打架。如果你买的是国内云厂商的 VPS,镜像库里通常直接提供 CentOS 7.9 x64,选这个版本比选 CentOS Stream 省心,后者滚动更新的特性容易让面板和运行库突然变化。
Django 的版本要求你心里要有数:Django 2.2 支持 Python 3.5 到 3.7,Django 3.2 LTS 支持 Python 3.6 到 3.9,Django 4.x 要求 Python 3.8 以上。CentOS 7.9 系统自带的是 Python 2.7,所以你要么用宝塔的 Python 项目管理器装一个 3.8 或 3.9,要么自己编译安装。我的经验是直接把宝塔的 Python 项目管理器当作版本管理工具用,不要碰系统自带的 Python,更不要手动替换/usr/bin/python,那个软链接换了之后yum、firewall-cmd全部会罢工。
宝塔面板自身的安装门槛也值得提前说:安装脚本会检查系统内存,如果你在安装时看到“宝塔至少需要 3700MB 内存才能安装”之类的提示,先别慌,这是脚本对内存的校验阈值,并不代表 2G 内存的机器跑不动面板。你可以在安装命令后面加--skip-memory-check跳过这个限制,但前提是你清楚面板本身会常驻一些服务,建议内存低于 1G 的机器不要装 MySQL 和 PHP,能省则省。顺便提一句,很多人纠结宝塔和 1Panel 选哪个,我的看法是:如果你熟悉 Linux 基础操作、愿意折腾 Docker,1Panel 更适合你;如果你就想快速看到网站页面、用图形界面管理文件数据库,宝塔的上手成本确实更低,这篇文章的步骤就按宝塔写。
2.2 安装宝塔面板:从远程连接到看到面板首页
拿到 CentOS 7.9 服务器后,用 SSH 工具登录,先更新系统源。国内服务器建议先把 yum 源换成网易源或阿里源,这一步能省下后面装依赖时的大量等待时间。网上有专门的 CentOS 修改网易源脚本,核心就是把/etc/yum.repos.d/CentOS-Base.repo里的 mirrorlist 替换成 mirrors.163.com 的路径,这里不展开,你直接搜“CentOS 修改网易源”照着做就行。
更新完源后执行宝塔的安装命令:
yum update -y wget -O install.sh https://download.bt.cn/install/install_6.0.sh bash install.sh安装过程会持续几分钟,脚本会检测 Python 环境、安装面板依赖包,最后在终端打印面板的访问地址和默认用户名密码。这里有两个要点:一是安装完成后立刻用浏览器访问面板地址,如果打不开,检查云服务器的安全组是否放行了 8888 端口;二是面板默认的入口是一串随机字符路径,不要改成纯端口访问,否则扫描器会疯狂试探你的面板登录页。登录后第一件事是在面板设置里绑定你的域名,并把登录地址改成/ 随机字符串,这是部署任何项目前的保命操作。
2.3 在宝塔上准备 Python 运行环境:版本管理器的取舍
宝塔面板的软件商店里有一个应用叫“Python 项目管理器”,这是部署 Django 的关键工具,它的作用类似于给每个 Python 项目单独开一个房间,互不干扰。安装这个管理器时面板会顺带安装 Python 3.7.9,但你要根据项目需求决定是否要额外装一个版本。比如你的项目依赖了 pydantic 2.x 或 Django 4.2,那就必须在管理器里再装一个 Python 3.9 或 3.11,因为 3.7 跑不动这些新依赖。
在 Python 项目管理器里添加 Python 版本时,我一般会选 3.9,兼容性处于一个舒服的位置:Django 3.2 和 4.2 都能跑,大部分第三方库的预编译 wheel 也覆盖到这个版本。装完之后它会在/www/server/pyproject下生成对应版本的 Python 解释器路径,后续创建项目时直接选用这个版本即可。这里有一个新手容易忽略的操作:创建完项目后不要急着传代码,先把依赖装好,否则项目文件里没有虚拟环境,Python 项目管理器无法识别项目类型。
3. 把 Django 项目送上服务器:依赖构建、数据库准备与静态文件收集
3.1 本地项目出发前的检查清单:settings.py 的三个改动
部署前先在本地把项目的部署开关打开,这一步做得好,服务器上能少踩一半坑。打开你的settings.py,按顺序检查三个地方。第一处是DEBUG = False,这是 Django 部署的红线,DEBUG开着部署,一旦用户请求触发异常,服务器会直接把完整堆栈和本地路径暴露在浏览器上,等于把源码结构告诉别人。第二处是ALLOWED_HOSTS,填上你的服务器 IP 和打算绑定的域名,格式是ALLOWED_HOSTS = ['123.45.67.89', 'www.example.com'],不然 Django 会拒绝处理请求并抛出 400 错误。
第三处是静态文件和媒体文件的路径设置:
import os BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 如果你的项目里还引用了 app 内部的 static 目录,需要这句 STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ]这段配置的逻辑是:STATIC_ROOT是collectstatic命令的收集目标目录,部署时 Nginx 会从这个目录里取 CSS、JS 和图片;STATICFILES_DIRS是你原本放在各个 app 或者项目根目录static文件夹里的源文件;MEDIA_ROOT是用户上传文件的落地目录。很多人在 VSCode 里写<img src="/static/images/logo.png">本地能显示,传到服务器后图片全挂,原因就是本地 Django 开发服务器会自动从STATICFILES_DIRS里找静态文件,而生产环境下 Django 默认不做这件事,必须由 Nginx 指向STATIC_ROOT,这一步在下文第 4 章会详细说明。
检查完这三处后,在本地执行python manage.py check --deploy,Django 会输出生产环境的额外警告。有一个警告很常见:SECRET_KEY不要硬编码在 settings.py 里。我一般习惯把它读环境变量,没有就从文件里读,服务器上写进.env文件,但这属于安全加固的范畴,新手阶段先保证能跑起来,这个警告可以暂时忽略。
3.2 上传代码与创建虚拟环境:用宝塔文件管理器还是 Git
把项目代码弄到服务器上有两种常见做法,一种是本地打成 tar.gz 包,通过宝塔的文件管理器上传再解压;另一种是在服务器上git clone,走版本管理流程。我更推荐用 Git,因为部署不是一次性的,之后每次改代码都要重新上传,走 Git 能省掉压缩、上传、解压三步。如果没有 Git 仓库,临时用文件管理器上传一次也够用,但项目规模稍微大一点(比如带 node_modules 或者媒体文件),上传速度会让人崩溃。
无论用哪种方式,最终代码落在/www/wwwroot/下即可,宝塔对站点的根目录默认就是这个路径下的一个子目录。假设你的项目放在/www/wwwroot/mydjango/,进入该目录后用 Python 项目管理器创建虚拟环境,它会自动在当前目录生成.venv文件夹,并把 Python 解释器地址填入环境配置。
cd /www/wwwroot/mydjango /www/server/pyproject/versions/3.9.12/bin/python3.9 -m venv .venv source .venv/bin/activate这里说明一下参数的含义:-m venv是用 Python 自带的模块创建虚拟环境,.venv是虚拟环境目录名,这是业内约定俗称的名字,宝塔的 Python 项目管理器默认也会识别这个目录。source命令是激活虚拟环境,激活后命令行前缀会变成(.venv),之后执行的pip、python都在这个隔离环境内,不会污染系统 Python。如果你的项目里直接复制了本地的venv目录上传,在服务器上必须要删掉重新建,因为虚拟环境里记录的是本机的 Python 绝对路径,换个机器就跑不起来了。
3.3 安装依赖与数据库准备:requirements.txt 的正确打开方式
虚拟环境建好后,下一步是安装依赖。不要手动一个包一个包地装,所有项目都要在本地生成requirements.txt,生成命令是pip freeze > requirements.txt。但我建议你自己精简一下这个文件,因为pip freeze会把所有传递依赖都列出来,有些包的版本号在服务器上可能装不上。比如某一次我部署的项目里锁定了numpy==1.24.3,在 Python 3.7 上没有对应的预编译包,源码编译又缺依赖,最后翻车翻在了一个无关紧要的传递依赖上。正确做法是只列项目直接依赖,传递给pip去自动解析。
服务器上的安装命令:
source .venv/bin/activate pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple-i参数是指定 pip 源为清华源,国内服务器不换源的话,从官方 PyPI 下载大包容易超时,特别是Pillow、psycopg2这种编译型库,等待时间非常考验耐心。如果你用的是 Python 3.9 且项目依赖的是 Django 3.2,这里基本不会出意外;如果项目用了mysqlclient,装之前先确认系统里有没有mysql-devel,没有的话要执行yum install gcc mysql-devel -y,否则编译阶段会直接报EnvironmentError: mysql_config not found。
数据库方面,如果你选 SQLite,部署后经常会遇到数据库文件权限不足导致 Django 报OperationalError: attempt to write a readonly database,这是 SQLite 文件的属主和 Nginx 运行用户不一致造成的。我的建议是生产环境直接用 MySQL,宝塔面板里装 MySQL 5.7 或 8.0 都行,装完后在面板的数据库页面新建一个数据库和专用账号,注意字符集选 utf8mb4,这个字符集才能正常存 emoji 和中文。然后在 Django 的settings.py里改成这样:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'mydjango_db', 'USER': 'mydjango_user', 'PASSWORD': '你的数据库密码', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'charset': 'utf8mb4', }, } }OPTIONS里的charset经常被漏掉,漏掉后在 Django shell 里执行查询-删除对象时,一旦数据里含中文或表情符号就会报Incorrect string value错误,这是新手排查数据库乱码时最容易忽略的细节。
3.4 执行迁移与收集静态文件:验证项目能否在服务器上独立运行
依赖装完、数据库配置好之后,先执行迁移和静态文件收集,再配置 Nginx,这样能提前暴露问题。执行顺序有讲究:
source .venv/bin/activate python manage.py makemigrations python manage.py migrate python manage.py collectstaticmakemigrations是根据模型变化生成迁移文件,你的项目如果是从别的地方拷贝过来的,本地生成的迁移文件已经随代码上传了,这一步通常输出No changes detected,这是正常现象。migrate才是真正把迁移应用到数据库,执行完成后会看到一长串OK列表。collectstatic是把你项目里所有 app 的static目录下的文件集中复制到STATIC_ROOT指定的文件夹里,这个过程会提示你是否覆盖已有文件,输入yes即可。
这三条命令跑完后,用开发服务器裸跑一次验证,命令是:
source .venv/bin/activate python manage.py runserver 0.0.0.0:8000然后在浏览器里访问http://服务器IP:8000,如果你已经在ALLOWED_HOSTS里填了 IP,应该能看到首页。看到首页说明项目代码、数据库、静态文件收集链路都通了,可以关掉这个进程进入下一步。这一步不要跳过,因为如果你在宝塔面板上配置了半天发现项目起不来,你根本分不清是 Nginx 配置错了还是 Django 本身就有问题。裸跑能通,后续就只需和 Nginx、进程守护工具打交道。
4. 宝塔核心配置:Nginx 反向代理、进程守护与域名绑定
4.1 用 Python 项目管理器启动 Django:从手动 uWSGI 到界面化守护
早些年部署 Django 的标准流程是装 uWSGI,再写一个uwsgi.ini文件,用uwsgi --ini uwsgi.ini启动,然后用 Supervisor 守着进程。这套流程没有错,但宝塔的 Python 项目管理器把这三步压成了图形界面上的几个输入框。它本质上还是用 Supervisor 那个思路来守护进程,只是不用你手动改配置文件了。
在 Python 项目管理器页面点击“添加项目”,填下面这几项:
- 项目路径:
/www/wwwroot/mydjango - Python 版本:你之前装的 3.9
- 框架:选择 Django
- 启动方式:我一般用 Gunicorn(一个 Python 写的 WSGI 服务器,替代不推荐的
runserver做生产服务) - 启动文件:
mydjango/wsgi.py(注意是项目配置目录下的那个 wsgi.py,不是 app 目录里的) - 监听端口:
8000(后续 Nginx 会反代到这个端口)
添加后你可以不立刻点启动,先手动生成并编辑一个run.py放项目根目录,让 Python 项目管理器调用它来启动服务。run.py的内容标准做法是这样的:
# -*- coding: utf-8 -*- import os from gevent import monkey monkey.patch_all() from gunicorn.app.base import BaseApplication from gunicorn.six import iteritems os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mydjango.settings') class DjangoApplication(BaseApplication): def __init__(self, app, options=None): self.options = options or {} self.application = app super(DjangoApplication, self).__init__() def load_config(self): for key, value in iteritems(self.options): if key in self.cfg.settings: self.cfg.set(key.lower(), value) def load(self): return self.application def main(): options = { 'bind': '0.0.0.0:8000', 'workers': 3, 'worker_class': 'gevent', 'timeout': 60, 'preload': True, } DjangoApplication(django_app).run()这段代码的逻辑解释一下:monkey.patch_all()是协同程序的补丁,让网络请求处理不阻塞;workers指定工作进程数,对普通机器来说 3 个足够,设得太多反而拖垮内存;timeout设成 60 秒,防止某个请求处理时间过长被强杀;preload是预加载模式,代码改动后重启不会把旧进程的引用带入新进程。这个文件是宝塔 Python 项目管理器启动 Django 项目时最常见的标准入口,你可以直接在管理器里添加项目后让它帮你生成,也可以手动放在项目根目录后手动指定启动方式。这个文件不一定每个项目都用得上,如果是简单项目,直接在 Python 项目管理器里配置 Gunicorn 参数也行。
4.2 Nginx 反向代理配置:site 配置文件的 location 与参数含义
进程守护搞定后,把 Nginx 这个门卫请出来。它的职责是:接收用户从 80 或 443 端口发来的请求,然后把请求转发给 127.0.0.1:8000 上运行的 Django 进程,同时把/static/和/media/开头的请求直接交给静态文件系统处理,不走 Python 进程。这样的设计让 Nginx 承担静态文件和高并发的短连接,Django 只专注业务逻辑。
在宝塔面板的“网站”页面添加一个站点,域名填你要绑定的域名,PHP 版本选纯静态(这里不需要 PHP)。创建完成后到网站的配置文件编辑页,核心配置片段如下:
server { listen 80; server_name www.example.com; # 防止上传大文件时 413 错误 client_max_body_size 20m; # 静态文件交给 Nginx 直接返回 location /static/ { alias /www/wwwroot/mydjango/staticfiles/; expires 7d; } # 媒体文件(用户上传的图片等)同样由 Nginx 返回 location /media/ { alias /www/wwwroot/mydjango/media/; expires 30d; } # 其余请求全部转发到 Django location / { proxy_pass http://127.0.0.1:8000; 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_connect_timeout 30s; proxy_read_timeout 120s; } }参数里最值得琢磨的是alias和proxy_pass的配对。location /static/的alias指向/www/wwwroot/mydjango/staticfiles/,访问example.com/static/css/app.css时,Nginx 会去服务器上读/www/wwwroot/mydjango/staticfiles/css/app.css。关键点:alias是直接替换,root是拼接前缀。如果你写的是root /www/wwwroot/mydjango/staticfiles/,那访问/static/css/app.css时 Nginx 会去读/www/wwwroot/mydjango/staticfiles/static/css/app.css,多了一层static,静态文件全 404。这是初始配置阶段最容易出现的“玄学错误”,实际上就是alias和root的行为差异。
X-Forwarded-For和X-Real-IP这两个请求头的作用是让 Django 能拿到用户的真实 IP。没有它们的话,request.META['REMOTE_ADDR']永远是 127.0.0.1,像django-ratelimit、登录日志里记录的全部是本机地址,排查线上问题时非常误导人。
4.3 域名解析与 HTTPS 证书:申请 SSL 的两个前置条件
站点配置保存后,先把本地电脑的hosts文件临时解析一下域名到服务器 IP,确认 Nginx 配置的转发链路通了再改线上 DNS。在hosts里加一行服务器IP www.example.com,然后浏览器访问http://www.example.com,能看到 Django 页面就说明反向代理没问题。
域名线上生效后,到宝塔网站的 SSL 页面申请 Let's Encrypt 证书。这里常见报错是allinssl 宝塔测试请求失败,我看过很多人因为这个问题放弃 HTTPS,其实原因基本就两个:一是域名解析没生效,证书申请机构验证域名所有权时访问不到你的服务器,安装前先在其它电脑上ping域名,确认已经解析到你服务器的 IP;二是服务器的 80 端口不通,Let's Encrypt 的证书签发验证走的是 80 端口的 HTTP 请求,如果服务器安全组只放行了 443 没放行 80,申请必失败。这两个条件都满足后,宝塔会自动申请并部署证书,同时在 Nginx 配置里生成 443 端口的 server 块,你只需要把配置里的proxy_pass等参数同样填到新生成的 443 配置块里即可。
5. 部署避坑:Django 项目起不来的五个常见问题
5.1 面板提示“内存不足”装不上:这个提示到底是不是硬门槛
现象:执行宝塔安装脚本时,终端输出类似于“对不起,您的服务器内存小于 3700MB,无法安装宝塔面板”的提示,然后安装中断。很多新手看到这行字就以为必须升级服务器内存,其实不是。
原因:这是宝塔安装脚本里的一个内存判断逻辑,阈值设得比较高,用来提醒小内存用户不要盲目装完整版面板。但对纯 Django 部署场景来说,面板本身加 Python 项目的内存占用通常在 1GB 到 1.5GB 之间,2GB 内存的机器跑起来很轻松。
解决:找到安装脚本里的内存检测代码段,或者直接在安装命令尾部添加跳过检测的参数:
wget -O install.sh https://download.bt.cn/install/install_6.0.sh echo -e "y" | bash install.sh --skip-memory-check注意,跳过检测不意味着可以肆无忌惮地在面板里安装 MySQL 8.0、PHP、Redis 全家桶。小内存机器上装面板后,只装 Nginx 和 Python 项目管理器这两个必备应用就够了,数据库建议用面板单独装 MySQL 5.7,其它用不到的服务一律不装。别同时开一堆服务,内存吃满后面板会直接打不开,Django 进程也会被系统 OOM Killer 杀掉,日志里到处是Killed字样,排查起来极其痛苦。
5.2 页面显示 502 Bad Gateway:进程没起来还是端口不对
现象:按教程配置完 Nginx,浏览器访问域名返回 Nginx 默认的 502 Bad Gateway 页面。
原因:502 的意思是 Nginx 无法连上上游应用。上游 Django 进程没启动、端口监听错误、或防火墙挡了 8000 端口,都会出现 502。
解决:首先到 Python 项目管理器页面确认项目状态是否显示“运行中”,如果显示“停止”,先看启动日志,日志里常见的报错是ModuleNotFoundError: No module named 'django'。这说明虚拟环境没有正确激活,检查项目的 Python 解释器路径是否指向了/www/wwwroot/mydjango/.venv/bin/python而不是系统 Python。日志没报错但状态还是停止,就手动在命令行跑一次启动命令:
cd /www/wwwroot/mydjango source .venv/bin/activate gunicorn mydjango.wsgi:application -b 0.0.0.0:8000 --workers 3 --timeout 60如果这个命令能正常运行不报错,说明 Python 项目管理器的配置有问题,重点检查“启动文件”字段是填了mydjango/wsgi.py还是wsgi.py,必须带项目配置目录名作为前缀。确认进程起来后,用curl http://127.0.0.1:8000测试本机回环地址,如果 curl 输出 404 或者 HTML 内容,说明端口没问题,问题出在 Nginx 的proxy_pass配置的端口号和 gunicorn 监听的端口号不一致。改掉其中一边即可。
5.3 collectstatic 后 CSS 依旧 404:alias 路径与 STATIC_ROOT 不一致
现象:部署完成后页面能打开,但所有 CSS、JS、图片全部 404,浏览器控制台报一堆Failed to load resource: the server responded with a status of 404。
原因:这是部署中最容易翻车的地方,本质是 Nginx 里写的别名路径和 Django 的STATIC_ROOT实际路径没对上。我见过有人改了STATIC_ROOT后忘了重新执行collectstatic,目录里根本没有文件,也有人把 Nginx 的alias路径写错了一层目录。
解决:按三步排查。第一步,在服务器上用ls /www/wwwroot/mydjango/staticfiles/确认收集后的静态文件确实存在,如果目录为空,重新执行一遍collectstatic。第二步,确认 Nginx 配置里location /static/的alias路径末尾的staticfiles目录名和你settings.py里的STATIC_ROOT一致,注意alias的路径必须以/结尾,否则 Nginx 拼接 URL 时会丢失最后一个斜杠。第三步,改完 Nginx 配置后执行nginx -t测试语法,然后nginx -s reload重载配置,这一步很多人忘掉,改了配置不重载,浏览器刷新出来还是旧页面。另外,VSCode 里写的<img>标签如果用的是绝对路径/static/images/logo.png,本地开发服务器能识别是因为 DEBUG 模式下的 Django 自带静态文件服务,生产环境必须依赖 Nginx 的alias,两者路径要一致。
5.4 页面上传文件后无法访问:media 目录没配 Nginx
现象:Django admin 或用户上传图片成功,但访问上传后的文件地址返回 404。比如上传了头像,控制台显示请求http://example.com/media/avatar/2024/a.png返回 404。
原因:Django 默认把上传文件存到MEDIA_ROOT目录,但生产环境下你只在 Nginx 里配置了location /static/,没有配置location /media/,所以浏览器请求/media/时 Nginx 找不到对应路径,自然返回 404。
解决:在 Nginx 配置文件里加上media的 location 块:
location /media/ { alias /www/wwwroot/mydjango/media/; expires 30d; }同时检查/www/wwwroot/mydjango/media/目录的属主是不是和运行 Nginx 的用户一致,宝塔安装后 Nginx 运行用户通常是www,如果项目文件是root用户上传的,Nginx 进程没有读取权限,也会导致 404。执行一条命令改变目录属主:
chown -R www:www /www/wwwroot/mydjango/media/ chown -R www:www /www/wwwroot/mydjango/staticfiles/记住一条原则:凡是 Nginx 要直接读取的目录,属主必须和 Nginx 运行用户一致,否则后面会陆陆续续遇到权限问题。
5.5 CSRF 验证失败与重定向错误:ALLOWED_HOSTS和CSRF_TRUSTED_ORIGINS的连带问题
现象:配置完 HTTPS 后,用户访问站点一切正常,但一旦提交登录表单或 POST 表单,页面报CSRF verification failed. Request aborted.,或者DisallowedHost提示把请求的主机名加入ALLOWED_HOSTS。
原因:Django 3.2 以上的版本对 CSRF 校验增加了Origin头检查。当你用 HTTPS 访问站点,页面里的表单请求源是https://www.example.com,如果settings.py里的CSRF_TRUSTED_ORIGINS没有把这个域名加进去,Django 会拒绝这个 POST 请求。至于DisallowedHost,则是ALLOWED_HOSTS里漏掉了带https://前缀的域名格式,或加入了www漏了裸域名。
解决:回到settings.py修改这两个配置:
ALLOWED_HOSTS = ['www.example.com', 'example.com'] CSRF_TRUSTED_ORIGINS = ['https://www.example.com', 'https://example.com']改完重新执行python manage.py collectstatic切记不需要,这一步不涉及静态文件,只需kill -HUP主进程平滑重载 Gunicorn,或者在 Python 项目管理器里点重启。这里有个实际开发中的坑:本地开发时DEBUG = True,Django 默认放行了本地 CSRF 校验,所有问题都不会暴露,一上服务器就原形毕露,所以部署前把这两个配置的域名写全,比事后去日志里翻报错快得多。
6. 收尾技巧:一键重启脚本、日志查看与状态确认
6.1 用 shell 脚本管理 Django 进程:重启、看日志、确认存活
部署完成的标志不是页面能打开,而是你能在这个跑起来的状态下自如地更新代码。Django 项目更新代码后,静态文件和 Python 代码需要不同的处理方式,每次手动敲collectstatic、重启 Gunicorn 很繁琐。我在服务器上习惯放一个deploy.sh,把这一步固定下来:
#!/bin/bash # deploy.sh - 部署完成后一键更新 cd /www/wwwroot/mydjango source .venv/bin/activate # 1. 拉取最新代码(如果走 Git 部署) # git pull origin master # 2. 安装新的依赖(如果有变动) pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple # 3. 数据库迁移 python manage.py migrate # 4. 重新收集静态文件 python manage.py collectstatic --noinput # 5. 重启进程(通过 Python 项目管理器的命令行工具,或直接 kill 主进程) pkill -f gunicorn sleep 2 gunicorn mydjango.wsgi:application -b 0.0.0.0:8000 --workers 3 --timeout 60 --daemon echo "部署完成,进程状态:" ps aux | grep gunicorn | grep -v grep这一步脚本是关键,尤其--daemon参数让 Gunicorn 在后台运行,不加这个参数,你 SSH 断开的时候进程就跟着一起被杀了。很多人部署完没问题,关掉终端再访问就 502,原因就是用了前台启动。在 Python 项目管理器里配置的话,你不需要这个脚本,手动点重启也行;但我始终觉得把部署动作用脚本固化下来,比每次去面板里点鼠标更可靠,特别是在改完代码反复调试的阶段。脚本里每一步都有明确输出,哪一步挂了你能立刻看到,不会出现“面板上显示运行中,但页面是旧的”这种模糊状态。
6.2 从日志定位问题:Gunicorn 错误日志与 Nginx error.log 的分工
最后记住一条日志排查的黄金法则:浏览器看到什么错误,先去 Nginx 日志里找答案,再顺藤摸瓜看 Django 日志。宝塔面板在网站页面的“日志”菜单里能直接看到 Nginx 的 access.log 和 error.log,但 Django 的应用日志不在这里,它需要你配置 Gunicorn 时指定日志文件。我一般在启动命令里加上两个参数:--error-logfile /www/wwwroot/mydjango/logs/gunicorn-error.log --access-logfile /www/wwwroot/mydjango/logs/gunicorn-access.log,这样 Django 的每个请求和异常都有记录。
看日志的习惯直接决定你排查问题的速度。502 优先看 Gunicorn 的错误日志,看它抛出的异常堆栈;404 优先看 Nginx 的 access log 和 Django 的 URL 配置,确认请求到达了哪一层;500 则直接翻 Django 异常页面——如果你设置了DEBUG = False且没配置LOGGING,500 时浏览器只会显示一个粗糙的“Server Error”页面,所以日志目录务必在部署前建好,否则出问题时会抓瞎。我的习惯是日志目录固定与项目路径同级,部署前先mkdir -p logs并chown -R www:www logs,确保 Gunicorn 和 Nginx 都有写入权限。
部署这条路,走通一次之后所有项目都是同一套动作:环境、依赖、Nginx、日志。我在这个流程上踩过最惨的一次跟今天说的完全一样---alias写错,静态文件 404 排查了一下午,最后发现就是root和alias的区别;从那以后我每次写 Nginx 配置前都会在心里默念一遍“alias 是替换、root 是拼接”,你也记住这句话,能少走很多弯路。希望帮到你。
本文还有配套的精品资源,点击获取