前后端分离的项目,部署到服务器上的时候,最怕的就是环境不一致、配置改来改去然后崩掉。今天这篇博文,我想把自己在实际服务器部署 Nginx + Django + Vue 这个组合的经验完整梳理一遍,从架构思路到每一步实操,再到我踩过的坑,一次性讲清楚。适合刚接触服务器部署、或者已经能写前后端代码但不知道怎么跑在公网上的朋友参考,同时也给已经部署过但总遇到 502、静态文件 404 这类问题的人一些排查思路。
我默认你本地开发环境已经能跑通 Django 后端和 Vue 前端,服务器用的是常见的 Linux 发行版。整个部署链路拆开看就是:Vue 打包成静态文件交给 Nginx 托管,Django 用 Gunicorn 跑起来处理动态请求,Nginx 负责对外接收请求并转发给 Gunicorn。下面我按我实际操作的顺序来写。
1. 部署架构与整体方案选型
1.1 这套组合的请求链路和组件职责
很多新手部署前后端分离项目,脑子里只有一个模糊的概念:“我把后端跑了,前端也跑了,是不是就行了?” 实际上,生产环境里的请求链路比本地开发要长很多。
我画的最终形态是这样:
用户浏览器 ↓ HTTPS请求 Nginx(服务器 80/443 端口) ├── 路径 /api/ 或 /admin/ → Gunicorn(Django 应用) ├── 路径 / 或 /assets/ → Vue 打包后的静态文件 └── 静态资源 → 直接返回 /static/xxx.jsDjango 在生产环境里扮演的是纯后端角色,它接受从 Nginx 转发过来的请求,用 Gunicorn 这样的 WSGI 服务器承载 Django 应用逻辑,只处理需要计算、数据库读写、权限鉴权等动态请求。Vue 项目则在本地构建成 dist 目录,里面是纯静态文件,Nginx 直接托管这些文件,连 Python 进程都不需要碰。
为什么一定要让 Nginx 挡在最前面?原因有几点。第一,Django 自带的开发服务器性能很差,只适合本地调试,直接暴露到公网既慢又不安全;第二,静态文件本该由 Nginx 这种高性能 Web 服务器直接返回,Django 处理静态文件是白白浪费计算资源;第三,一个 Nginx 可以同时代理多个服务,后面接 Django、接 Node、接其他语言的服务都可以,以后扩展很方便。
1.2 为什么不用 Django 开发服务器直接上生产
我见过一些朋友把项目扔到服务器上,直接执行python manage.py runserver 0.0.0.0:8000,访问倒是能访问,但这属于给自己埋雷。
开发服务器的设计目标是在本地开发时自动重载代码、输出详细调试信息,它不是并发模型。生产环境上一个页面几十个静态文件请求,runserver 单线程处理,稍微人多一点就直接卡死。而且 runserver 把 DEBUG 信息全部暴露出去,服务器上出现一个报错页,数据库连接信息、文件路径、代码片段全给你显示出来,这对生产环境来说是灾难。
Gunicorn 是一个成熟的 WSGI 服务器,用多进程模型处理并发请求,Django 项目接入 Gunicorn 只需要几行命令,再用 systemd 管住进程生命周期,宕机了自动拉起,比 runserver 可靠太多。
我在方案选型时也考虑过 Daphne、Uvicorn 这类 ASGI 服务器。如果项目里用了 Django Channels 做 WebSocket,那必须上 Daphne 或 Uvicorn;如果只是普通的 HTTP 接口,Gunicorn 完全够用,而且配置简单、生态成熟、坑少。我没有推荐直接让 Nginx 用 uWSGI 协议对接 Django,原因是现在 Gunicorn 更主流,而且全用 HTTP 协议转发,以后后端从 Django 换成别的框架也不受影响,Nginx 不用改协议配置。
2. 服务器初始化和环境准备
2.1 服务器基础配置建议
我自己的习惯是先配置一台干净的云服务器,操作系统选 Ubuntu 22.04 LTS 或 Debian 12,这两款系统资料多、包管理器好用、遇到问题容易搜到解决方案。如果你手头是 CentOS 7 那类老系统,命令会有差别,建议还是用 Debian 系,别把时间耗在兼容性问题上。
内存方面,一个 Django 进程加上 Gunicorn 的 worker,再算上系统本身占用,1G 内存的服务器跑个小项目很紧张,2G 以上会更舒服。如果只有 512M 内存的机器,建议把 Gunicorn 的 worker 数设成 1,同时关闭 MySQL 的缓存调大内存等优化项,不然很容易 OOM。
服务器到手第一件事不是装环境,而是先建一个专门的部署用户。我不会直接用 root 跑业务服务,而是创建一个普通用户,比如就叫 deploy,赋予 sudo 权限。这样做的好处是即使某个服务被入侵,攻击者拿到的也只是普通用户的权限,而且后续用 systemd 管理服务时,指定 User=deploy 可以最小化权限范围。
创建用户和基础工具链的命令大致是:
# 更新系统源 sudo apt update && sudo apt upgrade -y # 创建部署用户 sudo adduser deploy sudo usermod -aG sudo deploy # 安装基础工具:curl, git, vim/nano, ufw 防火墙 sudo apt install -y curl git vim ufw2.2 Python 和 Node 环境搭建
Django 项目依赖 Python 环境,我强烈建议不要动系统自带的 Python,因为系统工具很多依赖特定版本的 Python,你用 pip 装包装乱了,可能把系统搞坏。正确做法是装一个独立的 Python 版本管理工具。
我比较常用的是直接编译安装 Python,或者用 pyenv 管理多版本。在生产服务器上,我更倾向用 pyenv,因为项目如果多了,不同项目可能需要不同 Python 版本,pyenv 可以自由切换,不会互相污染。
具体步骤大概是:
# 安装编译依赖 sudo apt install -y build-essential zlib1g-dev libncurses5-dev \ libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev libbz2-dev # 安装 pyenv curl https://pyenv.run | bash # 把 pyenv 环境变量写入 shell 配置 echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc echo 'eval "$(pyenv init -)"' >> ~/.bashrc source ~/.bashrc # 安装 Python 3.11 pyenv install 3.11.9Node 环境同理,我推荐用 nvm 而非系统 Node。因为 Vue 项目构建时的 Node 版本如果太低,可能出现语法不支持、构建报错;太高又可能有原生模块编译问题。nvm 可以给每个项目锁定 Node 版本。
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash # 重开终端后 nvm install 18.20.0 nvm alias default 18.20.0装好之后检查几个命令,python --version、node -v、npm -v都正常输出就说明环境基本就绪。
2.3 数据库准备
Django 项目十有八九都要用数据库。我习惯用 PostgreSQL,如果项目原本是 SQLite 开发的,迁移到 PostgreSQL 之后要注意字段类型差异,比如 SQLite 没有原生的 JSONField 类型处理方式不同,Django 会做适配,但要跑一遍完整测试才放心。如果你图省事想继续用 SQLite,生产环境我不建议,并发写入性能差,备份也不方便。
创建数据库的要点是分离账号权限。我会创建一个专属于该 Django 应用的数据库账号,只授予这一个数据库的权限,不给它整个数据库集群的管理权限。这样即使后端代码里数据库密码泄露,攻击者也不能通过这个账号删掉同服务器上的其他数据库。
CREATE DATABASE myproject_db CHARACTER SET utf8mb4; CREATE USER 'myproject_user'@'localhost' IDENTIFIED BY '强密码'; GRANT ALL PRIVILEGES ON myproject_db.* TO 'myproject_user'@'localhost'; FLUSH PRIVILEGES;数据库装好后先用命令确认账号能正常登录,别等部署完 Django 才发现连不上数据库,查问题会非常痛苦。
3. Django 后端部署核心实操
3.1 代码同步与依赖安装
把代码弄到服务器上有两种主流方式:一是直接在服务器上 git clone 仓库,二是本地打包上传。前者更规范,我推荐在服务器上配置好 SSH 私钥,直接拉取代码,后续更新只需要 git pull 再重启服务。
我习惯把代码放在/home/deploy/projects/myproject这种目录下,不要放在 /root 下,也不要放在 /tmp 下,部署用户要能读写这个目录。拉取代码后第一步是创建虚拟环境:
cd /home/deploy/projects/myproject python -m venv venv source venv/bin/activate pip install --upgrade pip pip install -r requirements.txt如果你的项目依赖了某些需要编译的包(比如 Pillow、psycopg2),编译失败时先装一下系统依赖库,Ubuntu 下一般是sudo apt install libjpeg-dev libpq-dev python3-dev。装依赖的过程能跑通,后面就省心很多。
依赖装完,先手动跑一遍 Django 的检查命令:
python manage.py check python manage.py migrate这两个命令如果报错,先排查,别急着配置 Gunicorn。migrate 跑完确认数据库里有表了,再继续下一步。
3.2 生产环境关键配置调整
本地开发的 settings.py 直接拿到生产环境一定不能跑,我每次部署都要改这几个配置:
ALLOWED_HOSTS:加服务器的 IP 和域名。
ALLOWED_HOSTS = ['yourdomain.com', '服务器公网IP']DEBUG:必须改为 False。改完 DEBUG 之后,Django 不会再帮你处理静态文件,也不会输出详细错误页,这时如果没有配好 Gunicorn 和 Nginx,页面会显示 500。
数据库配置:换成独立的数据库账号,不要用 root 账号。同时把密码放到环境变量或 .env 文件里,不要硬编码到代码库,尤其项目会推到 GitHub 的时候,密码泄露是真实风险。
静态文件目录:我习惯设置:
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')接着执行:
python manage.py collectstatic --noinput这个命令会把 Django admin 等应用自带的静态文件收集到一个目录,之后 Nginx 直接托管这个目录就行。
其他安全配置:如果项目开启了跨域相关的处理,保证跨域设置是白名单域名而不是 *;SESSION_COOKIE_SECURE、CSRF_COOKIE_SECURE 在开启 HTTPS 后设成 True,HTTP 阶段先不设,否则会被浏览器拦截导致登录失效。
这些配置改完后,最好用python manage.py check --deploy检查一遍,它会提示你哪里配置不安全,比如 DEBUG 未关闭、缺少 SECURE_HSTS_SECONDS 等。
3.3 Gunicorn 启动与 systemd 服务配置
刚才我们手动验证完 migrate 和 collectstatic,下面把 Django 交给 Gunicorn 跑。先用一条命令测试能不能正常启动:
/home/deploy/projects/myproject/venv/bin/gunicorn myproject.wsgi:application \ --bind 0.0.0.0:8000 \ --workers 3 \ --timeout 60在本机 curl 一下:
curl -I http://127.0.0.1:8000/如果返回 HTTP/1.1 200,说明 Gunicorn 和 Django 已经跑通了。我用0.0.0.0:8000绑定是因为 Nginx 可能和 Django 不在同一台服务器,如果用 127.0.0.1 只能本机访问,反向代理就转发不过来。如果服务器上还有别人,也可以把 bind 地址改成内网 IP。
到这里服务还是跑在前台的,一旦关掉终端服务就没了,所以要用 systemd 管理。我写一个服务文件:
[Unit] Description=Gunicorn service for myproject After=network.target [Service] User=deploy Group=deploy WorkingDirectory=/home/deploy/projects/myproject Environment="PATH=/home/deploy/projects/myproject/venv/bin" EnvironmentFile=/home/deploy/projects/myproject/.env ExecStart=/home/deploy/projects/myproject/venv/bin/gunicorn myproject.wsgi:application \ --bind 127.0.0.1:8000 \ --workers 3 \ --timeout 60 [Install] WantedBy=multi-user.target这里我把 bind 改成了 127.0.0.1,因为 Nginx 和 Django 跑在同一台服务器,没必要让服务对公网开放。把服务文件放到/etc/systemd/system/gunicorn.service,然后:
sudo systemctl daemon-reload sudo systemctl enable gunicorn sudo systemctl start gunicorn sudo systemctl status gunicorn--workers 3这个参数不是随便定的,经验公式是2 * CPU核心数 + 1。一台 1 核 2G 的服务器建议用 3 个 worker,如果你业务代码有线程安全问题,可能还要降低。worker 多不代表更快,因为每个 worker 都是独立进程,内存占用翻倍,1G 内存的机器 3 个 worker 加上系统开销已经吃紧,调成 1-2 个反而更稳。
还有一点:如果代码更新了,需要重启 Gunicorn 才能生效:
sudo systemctl restart gunicorn4. Vue 前端构建与发布
4.1 构建配置要点
Vue 项目部署最核心的点是构建产物的路径问题。本地开发时 Vue Router 可能用的 history 模式,访问地址直接是/home、/about这种,打包之后,这些路径是相对于服务器根路径的。如果你打算把前端部署在域名根路径下,不用改配置;如果要放在子路径下,比如https://yourdomain.com/app/,就要在vue.config.js里设置publicPath: '/app/'。
我大多数项目默认部署在根路径,这里的示例也按根路径来。我要强调几个细节:
第一,API 请求地址不能写死成 localhost。很多项目本地开发时用.env.development里的变量指向http://localhost:8000/api,但打包的时候会用.env.production。部署前确认生产环境变量里的 API 地址填的是什么。如果你打算让 Vue 和 Django 同域名部署,即前端页面在/api/xxx的请求由 Nginx 转发给后端,那 API 地址可以直接写/api(相对路径),这样最省事,不用考虑跨域问题。如果前后端不同域名,就要配置跨域,这条路相对复杂一些。
第二,路由模式。如果你用了 history 模式(推荐),Nginx 必须把所有前端路径都 f