简介围绕CentOS系统与宝塔面板部署Django应用的完整流程整理为一份简明PDF教程适合需要在Linux服务器上快速上线Django项目的开发人员、运维人员及刚接触服务器部署的初学者使用。教程从宝塔面板与Python项目管理器、Nginx的安装准备讲起覆盖项目代码上传的Git克隆与FTP两种方式、创建Django项目时路径、Python版本、uWSGI启动方式及端口等关键配置随后通过映射绑定域名或外网IP并在Nginx反向代理中补充static与media静态资源路径最后完成项目重启与Nginx配置重载。针对部署中常见的环境准备、端口选择、静态文件丢失等问题均有明确操作指引步骤清晰、路径标注具体可帮助读者从零开始将本地Django应用发布到公网。资源为单个PDF文件大小139KB便于本地查阅或打印对照操作。该教程已获得2371人学习下载尤其适合用于个人博客、小型Web应用的生产环境部署参考。1. CentOS下宝塔部署Django项目从零到上线一个下午能走完的路CentOS下宝塔部署Django项目本质上是在回答一个问题没有专职运维的情况下怎么用一台 Linux 服务器把 Django 业务跑稳。拿我自己的经历说2核4G的 CentOS 7从装系统到域名能访问一个下午能走完其中一半时间还花在等 pip 下载上。宝塔面板解决的是 Nginx、MySQL、日志这些系统级杂活Django 代码仍然走标准 WSGI 协议由 Gunicorn 承载两者各管一段。适合正在做 django 项目实战、想快速验证自己作品成品的开发者也适合给小团队内部系统当部署底座。这篇文章按真实部署顺序来系统、面板、Python、数据库、进程、反代、排错每一步都给出能直接抄的命令和参数解释。2. 先搭地基CentOS 7.9 镜像、yum 源与宝塔面板安装的三件事2.1 镜像选型为什么 CentOS 7.9 仍是部署默认项选系统时热词里的 centos 7.9下载 和 centos 8 stream下载 背后是同一类纠结。CentOS 8 在 2021 年底停止了维护Stream 是滚动更新版不适合当生产底座CentOS 7.9 作为最后的经典版本虽然官方源也进了 vault 归档但宝塔对 CentOS 7 的兼容性和国内镜像的覆盖仍然最完整。新手或者团队服务器建议直接选 7.9 x86_64如果拿到的机器是 ARM 架构就要去镜像站找 arm版centos下载 对应的 aarch64 镜像两者在宝塔安装上没有区别但 gunicorn 里的二进制依赖少ARM 版可能需要多编译一步。CentOS 和 Ubuntu 的选择也是老话题。Ubuntu 的 apt 源软件包新跑 Python 项目很顺但很多从教程抄来的 systemd 和防火墙写法在 Ubuntu 上会翻车因为它的网络管理默认是 netplan而 CentOS 7 是纯 network.service 体系。如果你只为了部署一个 Django 应用CentOS 7.9 的坑最少网上能找到的案例也最多这也是宝塔默认把这套组合当主场景的原因。安装时用最小化镜像即可不要选带桌面环境的版本服务器不跑图形界面。ISO 在阿里云镜像站或华为云镜像站都有历史目录文件名类似 CentOS-7-x86_64-Minimal-2009.iso。装完虚拟机之后如果发现主机内容不能复制到虚拟机里面这不是系统问题VMware 环境里要装 open-vm-tools服务器上用 git 拉代码、用 scp 传压缩包都比剪贴板可靠。2.2 换源CentOS 7.9 的 yum 到底怎么救CentOS 7.9 官方源已经停止更新直接 yum install 多半连不上 mirrorlist。先备份原源文件再切到国内镜像mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo yum clean all yum makecache yum install -y wget vim net-toolscurl 下载的是阿里云的 CentOS-7.repo 模板里面已经写好 baseurl 指向 mirrors.aliyun.com不需要再改。makecache 速度如果能跑到几 MB/s说明源没有问题。wget、vim、net-tools 这三件套是后面手动操作的基础宝塔安装脚本本身也要用 wget。如果手上是 CentOS 8 或者更老的版本换网易源或阿里源同样适用核心是修改 baseurl 指向镜像站对应版本的目录。改完源后检查一下磁盘占用df -h系统分区如果只有 20G后面 Python 虚拟环境和 npm 构建产物很容易把 / 打满这就是热词里 centos扩容 的来源。扩容要在系统装完、数据迁入之前做进 PE 或直接在面板的磁盘工具里扩根分区最稳。2.3 宝塔面板安装命令、登录和五条初始安全项安装宝塔面板只有一条命令yum install -y wget wget -O install.sh https://download.bt.cn/install/install_6.0.sh sh install.sh安装过程大概两到三分钟脚本会检测系统版本并装好 Nginx、MySQL 等组件的编译环境。结束时会打印面板访问地址、默认账号和密码这个信息只在终端出现一次务必复制到本地的密码管理工具里。如果中途断网重新执行 install.sh 可以续装宝塔的安装脚本是幂等的反复执行不会装出两份面板。登录面板后我一般先做四件事第一在面板设置里把默认 8888 端口改成不常见端口第二开启面板 SSL 并绑定一个域名第三在安全菜单里把 SSH 和面板的入口 IP 白名单配上第四到软件商店把 Nginx、MySQL、Python 项目管理器这三个插件装上。bt 命令是面板自带的命令行工具输入 bt 会列出 15 个左右的功能编号包括改端口、改密码、查看面板信息。这一步别跳过公网 IP 的 8888 端口几乎每分钟都在被扫描改端口是最基本的自我保护。宝塔面板搭建网站这个搜索词下很多人问网站和 Python 项目什么区别。简单说网站菜单只处理静态站点和 PHP 项目Django 应用要用软件商店里的 Python 项目管理器来管理进程所以下面一章先解决 Python 环境和依赖的问题。3. Django 项目上线前的三件套Python 版本、虚拟环境与 SQLite/MySQL 选型3.1 用面板的 Python 项目管理器装解释器为什么不要动系统 Python在开始部署 Django 之前先把一个血泪经验说在前面永远不要在 CentOS 7 上直接替换系统自带的 Python。CentOS 7 的 yum 核心工具链依赖 python2.7把 /usr/bin/python 软链改成 python3yum 立刻全部报错连面板安装脚本都会跟着挂。宝塔的 Python 项目管理器会把解释器编译到 /www/server/panel/pyenv 目录和系统 Python 完全隔离这才是安全做法。打开软件商店安装 Python 项目管理器版本选 3.9 或 3.10 都行。Django 3.2/4.x 对这两个版本支持很好不需要追最新的 3.12。面板默认提供的版本列表来自官方 pyenv编译一个解释器大概五分钟这期间能做的就是把项目代码先传到服务器。代码放 /www/wwwroot 下目录所有权改成 www 用户因为后面的 Nginx 和面板进程都是以 www 身份运行的。项目进来之后先用 SSH 进入项目目录创建虚拟环境cd /www/wwwroot/myproject python3 -m venv venv source venv/bin/activate pip install --upgrade pip pip install django gunicorn mysqlclient pip freeze requirements.txtpython3 这里指向的是面板编译的解释器如果你是在面板终端里操作环境变量已经配好如果用自己的 SSH 客户端用 which python3 确认路径包含 pyenv。创建虚拟环境后所有依赖都装在 venv 里将来删项目直接整目录删掉不会污染系统。pip install 后面跟的包循环安装失败时最常见原因是网络换成 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 再装。如果项目是 git 仓库直接 git pull 到 /www/wwwroot/myproject 即可不需要用宝塔的上传功能传整个包那样容易漏掉 .env 这类隐藏文件。传完之后检查一下项目的 manage.py 在不在项目根目录这个路径后面填面板表单要用。3.2 创建 Django 项目startproject 还是已有仓库settings 怎么改没有现成代码时在虚拟环境里直接创建项目source venv/bin/activate django-admin startproject config . python manage.py migrate python manage.py createsuperuserstartproject 后面的点号表示把 config 配置目录建在当前目录而不是再套一层子目录。migrate 先跑掉内置的 auth/session 等十几张表createsuperuser 创建后台管理员账号。这一步完成之后项目还不能对外提供服务Django 默认监听 127.0.0.1:8000只能本机访问。上线关键的 settings.py 改三项。ALLOWED_HOSTS 要加服务器 IP 和域名否则外网访问直接报 DisallowedHost 400DEBUG 改成 False开启后 Django 不再返回堆栈信息避免暴露项目结构同时必须在 settings.py 里配好静态文件收集路径ALLOWED_HOSTS [your_domain.com, 123.123.123.123] DEBUG False STATIC_ROOT BASE_DIR / static STATIC_URL /static/STATIC_ROOT 让 Django 能把 admin 自带样式和业务静态文件统一收集到项目目录下的 static 文件夹后续 Nginx 直接指向这个目录。如果项目里有上传图片功能MEDIA_ROOT 也要单独指定一个目录并确保 www 用户对该目录有写权限。这些参数改完再一次 python manage.py collectstatic --noinput 收集静态文件输出里会显示 copied N 个文件数量对得上 admin 资源就说明路径没配错。django 创建 app 是新手阶段最常做的事部署时每个 app 的 models 改动要靠 python manage.py makemigrations 和 migrate 同步到数据库上线前记得把所有未提交的迁移文件一起提交服务器拉取后统一执行 migrate。3.3 数据库选型SQLite 零配置起步MySQL 迁移的三个参数sqlite怎么在宝塔面板安装、宝塔面板安装sqlite 这类搜索词背后其实是同一种误解Django 的 SQLite 驱动不需要单独安装只要 Python 解释器编译时带了 sqlite3 模块虚拟环境里直接就能用。验证方式是在 Django shell 里执行import sqlite3 print(sqlite3.sqlite_version)能输出 3.x.x 就说明驱动可用。宝塔的 Python 项目管理器编译解释器时默认启用 sqlite3所以 SQLite 路线是零配置的数据库文件就是项目目录下的 db.sqlite3。SQLite 适合单机低并发项目Django admin 后台、个人博客、内部工具完全够用。提示如果 SQLite 频繁写入报 database is locked先检查项目目录的所有者是不是 www而不是急着换 MySQL。项目原本用 MySQL迁移时把 settings.py 的 DATABASES 改成引擎 mysql还要确认驱动。面板软件商店装 MySQL 5.7然后在虚拟环境里 pip install mysqlclient。mysqlclient 在 CentOS 上编译依赖 mysql-devel如果编译失败先 yum install -y mysql-devel gcc python3-devel。Django 连接 MySQL 时数据库名、用户、密码三项要和面板里建的一致DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: django_db, USER: django_user, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } }charset 用 utf8mb4 是为了存 emoji 和生僻字MySQL 5.7 默认字符集在部分版本里是 utf8mb3不显式指定容易乱码。数据库从 SQLite 迁到 MySQL 时注意 django 执行查询-删除对象 的级联行为SQLite 默认外键松散MySQL 的 InnoDB 会校验外键删除父表记录前要么显式指定 CASCADE要么先清掉关联子表。ORM 代码里用 on_deletemodels.CASCADE 的模型一般没问题手动执行 delete 时就要小心。4. 核心部署链路Gunicorn 启动、Nginx 反代与静态文件收集一次配通4.1 Python 项目管理器的启动表单入口路径和 worker 参数怎么填面板的 Python 项目管理器里添加项目最关键的两个字段是项目路径和启动命令。项目路径选 /www/wwwroot/myproject启动命令不要写 python manage.py runserver那是开发模式单进程扛不住访问也不能常驻。生产环境用 Gunicorngunicorn config.wsgi:application -b 127.0.0.1:8000 --workers 2 --threads 4 --timeout 30config.wsgi:application 是 WSGI 入口config 对应项目里的 config 目录wsgi 对应 wsgi.py冒号后面是 application 对象。写错入口最常见的错误是直接用 manage.pyGunicorn 不认识它。-b 127.0.0.1:8000 表示只监听本机 8000端口要等 Nginx 反代不要监听 0.0.0.0否则等于是把 Django 裸露到公网。workers 参数是进程数我的经验是 CPU 核心数乘以 2 加 12 核机器填 34 核填 5不是越多越好worker 多了内存直接翻倍。threads 参数用于并发Django 的 ORM 查询是 IO 密集填 4 到 8 够应付常规场景。项目里有耗时上报或没有同步长任务的workers 和 threads 可以适当调小2核机器填 24 很稳。填完启动以后面板这个项目卡片显示运行中进程列表能看到 master 和 worker。想验证服务真的起了SSH 里 curl 一下curl -I http://127.0.0.1:8000/返回 HTTP/1.1 200 就说明 WSGI 管线通了后面只差 Nginx。如果面板项目启动失败先别急着改面板配置在项目目录手动执行同样的 gunicorn 命令错误日志直接打在终端比面板日志直观得多。面板的日志一般存在 /www/wwwlogs 或项目目录下的 .python-manager 目录里排查时用 tail -f 跟着看。顺带提醒一句项目里如果用了 python django websocket 做后台数据推送前端Gunicorn 默认 worker 不支持 WebSocket 协议要换 uvicorn 或 daphne 作为启动命令Nginx 侧还要补一段 Upgrade 头配置这是后话但选型时得先知道。4.2 Nginx 反代的最小配置location 顺序、静态文件与请求头Nginx 安装好后到网站菜单添加站点域名填你的域名或服务器 IP根目录随便填一个项目外的目录接下来编辑配置文件把 server 块改成下面这样server { listen 80; server_name your_domain.com; client_max_body_size 20m; location /static/ { alias /www/wwwroot/myproject/static/; expires 7d; access_log off; } location /media/ { alias /www/wwwroot/myproject/media/; } 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; } }location /static/ 和 /media/ 两个块放在 location / 前面Nginx 匹配规则是前缀优先且最长匹配/static/ 开头的请求不会落到反代。alias 是替换关键/static/css/a.css 会映射到 /www/wwwroot/myproject/static/css/a.css这个路径要和实际 collectstatic 的输出目录完全一致。如果 alias 写成 /www/wwwroot/myproject/static/static访问 404那就是路径多套了一层。proxy_pass http://127.0.0.1:8000 不带 URINginx 会把请求原样转发给 GunicornDjango 拿到的是完整路径。四个 proxy_set_header 很关键Host 不传的话 Django 的 ALLOWED_HOSTS 校验会失败X-Forwarded-For 用来记录真实客户端 IPX-Forwarded-Proto 保证重定向时能拿到 https 前缀。这些头在 Django 侧通过 request.META 读取。保存配置后执行 nginx -t 检查语法再 reload。不要直接重启 Nginxreload 是平滑重载不中断已有连接。确认配置语法后浏览器访问域名能看到页面说明整条链路通了。此时 SSH 里执行 ss -lntp能看到 Nginx 监听 :80Gunicorn 监听 127.0.0.1:8000。很多框架的宝塔部署比如 nuxt4部署宝塔详细流程本质都是同一套 Nginx 反代手法只是后端进程从 gunicorn 换成了 node 的 pm2。理解 location 和 proxy_pass 的规则换个项目只是改入口进程的问题。4.3 防火墙、SELinux 与端口监听三个容易被忽略的环境变量CentOS 7 默认 firewalld检查一下端口开放情况firewall-cmd --state firewall-cmd --list-all如果只是通过域名访问80 和 443 通常默认放行不需要额外操作。如果想把 8000 暴露出来临时调试必须执行firewall-cmd --zonepublic --add-port8000/tcp --permanent firewall-cmd --reload测试完记得删掉这条规则Django 被公网直接访问没有任何安全套层所有请求都会绕过 Nginx 的日志和连接限制。SELinux 是比防火墙更隐蔽的坑。默认 Enforcing 模式下Nginx 作为非标准进程去连本机的 8000 端口会被 SELinux 拦截表现是页面上 502Nginx error.log 里报 connect() failed (13: Permission denied)。临时关掉试一下setenforce 0 getenforce如果确实是 SELinux 的问题用持久化配置解决不要每次重启都手动关。执行下面的命令允许 Nginx 访问本机网络端口setsebool -P httpd_can_network_connect 1这个布尔值允许 httpd 相关的 Nginx 进程访问外网网络。用面板部署时宝塔默认没有处理 SELinux遇到 502 先看这一项。系统的 /etc/selinux/config 可以改成 disabled但我不建议保持 Enforcing 配合 setsebool 更安全毕竟一台公网服务器上不止 Django 一个服务。5. 避坑清单宝塔上跑 Django 最常踩的 6 个翻车现场5.1 面板项目启动失败日志显示 ModuleNotFoundError现象在 Python 项目管理器点击启动项目状态一直在启动中两三分钟后变红。查看日志常见 ModuleNotFoundError: No module named django。原因面板启动项目时用了错误的环境。项目管理器默认关联刚创建的虚拟环境但如果你在面板里手动改了 Python 版本或项目是后面才放进来的面板没有自动绑定 venv。解决项目路径填完后版本选择要精确到当初建 venv 用的那个解释器然后在启动命令前显式激活虚拟环境手动启动验证source /www/wwwroot/myproject/venv/bin/activate pip list | grep django gunicorn config.wsgi:application -b 127.0.0.1:8000终端能起来再回面板把启动方式改成命令模式。如果终端也起不来pip list 里没有 django就是依赖没装进去直接 pip install -r requirements.txt 重新装。5.2 改完代码刷新页面没变化Gunicorn 不会自动重载现象本地 runserver 时代改个 views.py 保存就生效部署到宝塔后改完代码浏览器刷新还是旧页面。原因Gunicorn 是常驻进程worker 加载代码后不会监听文件变化runserver 的 auto reload 在 WSGI 生产模式下不存在。解决启动命令加 --reload 参数Gunicorn 会像开发模式一样监测文件变更但生产环境我不建议长开多一次文件监听就多一分系统调用开销。正确做法是改完代码后在面板项目页点重启或者 SSH 里给 Gunicorn master 进程发 HUP 信号kill -HUP $(cat /www/wwwroot/myproject/gunicorn.pid)HUP 会让 worker 优雅重启正在处理的请求不会中断比粗暴 kill 再启动要稳得多。gunicorn.pid 文件需要在启动命令里加 --pid 参数才会生成面板的命令模式默认不生成手动执行时记得带上。5.3 CSS/JS 全部 404DEBUGFalse 后静态文件没人管了现象页面结构正常但所有样式和图片 404admin 后台同样没有 CSS。原因DEBUGTrue 时 Django 自己托管静态文件部署时改成 FalseDjango 就不再处理 /static/ 请求必须交给 Nginx 或 whitenoise。解决确认三步都做过settings.py 里配了 STATIC_ROOT执行过 python manage.py collectstatic --noinputNginx 里 location /static/ alias 路径指到 collectstatic 输出的目录。alias 路径的检查办法是用浏览器直接访问 /static/admin/css/base.css如果 404说明 alias 写错如果是 200再看页面上的静态文件 URL 是什么开头两者要对得上。Django admin 后台样式丢了这个原因占了九成剩下的要看项目里是否自定义了 admin 静态文件目录有的话要确保 APP_DIRS 和 STATICFILES_DIRS 配置正确。5.4 SQLite 写操作报 database is locked现象用 Django admin 保存一条记录页面报 OperationalError: database is locked日志里频繁出现 SQLITE_BUSY。原因SQLite 在同一时间只允许一个进程写文件Gunicorn 开了多 worker两个 worker 同时写库就互相锁死。另一个常见原因就是数据库文件权限不对www 用户写不了 db.sqlite3。解决低并发项目直接把 workers 改成 1彻底避免写竞争保留多 worker 就迁移 MySQL。权限问题按这个命令处理chown -R www:www /www/wwwroot/myproject chmod 664 /www/wwwroot/myproject/db.sqlite3如果项目里还有文件上传media 目录也需要同样的 owner 调整。改完之后在 Django shell 里跑一次简单的模型查询确认连接正常。5.5 访问域名报 DisallowedHost 400现象浏览器访问域名直接出现 400 Bad Request页面显示 DisallowedHost 一行报错。原因Django 的 ALLOWED_HOSTS 校验了请求头里的 Host 字段没有把它加入名单就拒绝。解决先把 settings.py 里配成 [*] 验证问题是否消失确认是 Host 校验后改成实际的域名和 IP 数组不要长期用通配符。改完记得重启 Gunicorn这个参数在进程启动时加载不重启不生效。这一步也多发生在刚上传项目、还没改配置就直接访问的新手场景里。5.6 上传大文件返回 502 Bad Gateway现象上传 10MB 的附件请求发出去后 Nginx 直接返回 502小文件正常。原因Nginx 默认 client_max_body_size 是 1m请求体超过这个值Nginx 直接断开连接不回传给 Gunicorn日志里会有 client intended to send too large body。解决在 server 块里加 client_max_body_size 20mNginx reload 即可。同时注意 Gunicorn 的 timeout 参数上传处理超过 30 秒会被 Gunicorn 杀掉按业务调大。如果项目用了前端的 chunked 分片上传那还要检查 proxy_http_version 和 proxy_request_buffering普通表单上传改 client_max_body_size 就够了。6. 上线后的验证技巧一条 curl 命令、一次压测和三个维护习惯6.1 用一条 curl 判断链路健康部署完成第一时间不要急着刷新浏览器先 SSH 里 curl -I http://your_domain.com看返回的 HTTP 头。200 说明整条链路通502 去查 Nginx error.log504 去看 Gunicorn 的 timeout 和数据库连接。这个习惯能帮你把问题定位到具体段而不是在浏览器里反复刷新瞎猜。注意 curl 返回的 Server 头是 nginx而页面内容里有没有 Django 的尾巴并不能代表它是什么时候生成的真正判断 Django 是否响应要在服务器内部 curl 127.0.0.1:8000。6.2 用 ab 做一次小压测验证 worker 数压测工具 ab 在 httpd-tools 包里CentOS 上 yum install -y httpd-tools 就能装。这是最快的实测手段ab -n 1000 -c 50 http://your_domain.com/看 Failed requests 是不是 0Requests per second 是不是符合预期。并发 50 的情况下 2 核机器 Gunicorn 2 worker 4 thread 一般能跑到几百 QPS 的静态响应数据库查询多的接口会低一些。压测结果后调整 worker 数不要一上来就堆 8 个 worker内存会先被吃满。压测时注意 ab 的 User-Agent 会被宝塔的某些防爬插件拦截如果页面返回 403给 ab 加 -H User-Agent: Mozilla/5.0 再测。6.3 三个维护习惯备份、日志切割、面板安全更新上线后我固定的三个动作是面板计划任务里每周备份一次项目目录和数据库日志切割每周切一次 Nginx 日志避免 /www/wwwlogs 涨满磁盘每月进面板设置检查一次面板版本和 Python 项目管理器插件更新CentOS 7.9 的 yum 源不更新没关系面板组件走的是宝塔自己的源。磁盘空间紧张时先 df -h 看根分区再清 /www/server/panel/logs 里堆积的安装日志。如果项目里加了定时任务用面板的计划任务配合虚拟环境的 python 解释器执行不要把系统 python 当作跑项目脚本的默认环境。我现在的习惯是每次改完配置顺手跑一条 curl -I 验证十分钟就能发现大半问题希望帮到你。真遇到状态码异常先翻 /www/wwwlogs/ 里的 error.log九成答案都在里面。本文还有配套的精品资源点击获取