Nginx + Flask + MySQL 全栈部署 (CentOS 9)
(nginx,flask,mysql)
本项目记录了在 CentOS Stream 9 环境下,从零搭建一个完整任务管理系统(Todo List)的全过程。项目涵盖了数据库配置、后端接口开发、前端页面部署以及 Nginx 反向代理与 SELinux 安全加固。
Nginx web前端服务器的搭建
- 环境&文件配置
在开始前,要先部署好运行环境:
安装基础工具:Nginx 和 Node.js
sudo yum install -y epel-release
sudo yum install -y nginx nodejs npm
创建一个放前端文件的文件夹(存放 .html, .css, .js)
mkdir -p /data/todo/frontend
(我们将项目放置在 /data 目录而非用户的 home 目录,这是为了方便多用户协作及权限统一管理)
后写入前端测速的文件:
vi /data/todo/frontend/index.html
(配置文件会在上传的文件里)
给予文件夹执行权限:
chmod -R 755 /data/todo
-----------------------------------
4:读取---r
2:写入---w
1:执行---x
0:无权限 -
-----------------------------------
二.配置 Nginx 反向代理:
在 /etc/nginx/conf.d 下创建独立的配置文件:
vi /etc/nginx/conf.d/todo.conf
(配置文件会在上传的文件里)
加查语法和重启激活生效:
nginx -t (如果末尾显示 test is successful,则配置正确 )
systemctl restart nginx
三.在防火墙和SElinux上放行:
1.防火墙上放行80(http)端口:
firewall-cmd --permanent --add-port=80/tcp (永久开放 80 端口)
firewall-cmd --reload (重新加载防火墙使配置生效)
firewall-cmd --list-all (查看 80端口是否在列表中)
2.配置SELinux:
setenforce 0 (临时关闭)
setsebool -P httpd_can_network_connect 1 (运行进行网络连接)
chcon -Rt httpd_sys_content_t /data/todo(给SElinux打上正确的 Web 标签)
好了,现在可以去主机里去输入 http:// -前端web服务器IP- 来访问看看了
如果没有问题的是下面这个页面

可以f12打开网络这个模块看看


这里红色的报错是因为我们现在没有连接后端的aip
到这一步前端 Web 服务器(Nginx)配置已经基本完成了
好了接下来是在学习配置上遇到了一些问题和解决方法:
- 页面403(无法读取或发送这些文件):
首先直接去查看了/ var/log/nginx/ todo_error.log的报错日志文件:
2025/12/28 16:21:06 [error] 1550482#1550482: *1 open() "/data/todo/frontend/index.html" failed (13: Permission denied), client: 192.168.8.1, server: localhost, request: "GET / HTTP/1.1", host: "192.168.8.11"
核心报错翻译
open() "/data/todo/frontend/index.html" failed (13: Permission denied)
- 动作:Nginx 想要打开(open)你的首页文件。
- 路径:/data/todo/frontend/index.html。
- 原因:13: Permission denied(13号错误:权限不足)。
有三种原因:
- 文件本身的权限
- 路径上的文件夹权限
- SELinux 的标签不匹配
因为已经设置过路径文件夹和文件的权限了,所以这里直接锁定是问题 C
输入:setenforce 0 临时关闭后发现没有报错就确定是SELinux 的标签不匹配
chcon -Rt httpd_sys_content_t /data/todo
给目录打上正确的 Web 标签后解决
- 页面不更新:

使用ctrl+f5强制刷新无效后
删除原有的网页文件:
rm -f /data/todo/frontend/index.html
让后重新写入一个没有问题的文件(也许是代码格式有问题或者写错了?)
- 还有一些命令和代码的拼写错误导致各种的问题…..细心写入和仔细检查就能解决
flask后端服务器部署
- 环境的部署:
1.因为部署的是flask,所以要准备python环境
sudo dnf update -y
sudo dnf install python3 python3-pip python3-devel -y
2.创建专门放后端的文件:在todo文件夹下创建一个专门放后端文件的backend
(后面因为SElinux的原因把整个todo文件夹移去了/var/www下)
3.Cd到backend目录下创建并激活虚拟环境:
python -m venv venv
source venv/bin/activate
( 激活后,你的命令行提示符前会多出一个 (venv) )
4.安装flask:
pip install flask gunicorn
- 编写后端的python代码
在backend目录下创建一个app.py并写入文件:
vi app.py
接着就可以写入代码了:
------------------------------------------------------------------------------
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/api/tasks', methods=['GET'])
def hello():
return jsonify({
"status": "success",
"message": "这是来自后端的响应!",
"data": [1, 2, 3, 4, 5]
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000)
~
------------------------------------------------------------------------------
注意那俩处标记的地方:
/api/tasks要和前端的连接部分对应
端口也要和前后端都一至
三.为了能保证进程不会中断的正常和保证服务的自启动
1.我们要配一下systemd:
sudo vi /etc/systemd/system/flask_app.service
------------------------------------------------------------------------------
[Unit]
Description=Flask Backend
Service After=network.target
[Service]
User=qqq
Group=qqq
WorkingDirectory=/var/www/todo/backend
# 路径一定要写对,这是刚才新建的 venv 路径 ExecStart=/var/www/todo/backend/venv/bin/gunicorn -w 3 -b 127.0.0.1:3000 app:app
Restart=always
[Install]
WantedBy=multi-user.target
------------------------------------------------------------------------------
注意标记的地方
WorkingDirectory:后端的文件夹 3000:的前后端一至的端口
2.经典重启三连:
刷新配置:sudo systemctl daemon-reload
启动服务:sudo systemctl restart flask_app
查看状态:sudo systemctl status flask_app
(或许会报错,不过没关系,如果文件夹和文件权限问题的话大概率就是SE的问题,
可以先 sudo setenforce 0 看看,如果可以就是SE的问题,接下来就会去解决)
- 配置SElinux:
- 先把测试时关了的重新打开:
sudo setenforce 1
2. 解决 Nginx 的“断连”问题:
sudo setsebool -P httpd_can_network_connect 1
3.永久修复目录标签:
确保selinux管理工具没有问题:
sudo dnf install policycoreutils-python-utils -y
4. 告诉系统:/var/www/todo 及其子目录都是 Web 服务的内容
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/todo(/.*)?"
5. 允许虚拟环境 bin 目录下的东西可以被执行:
sudo semanage fcontext -a -t bin_t "/var/www/todo/backend/venv/bin(/.*)?"
6.应用这些刚刚配置的规则:
sudo restorecon -Rv /var/www/todo
接下来就是重启验证一下了:
sudo systemctl restart flask_app
sudo systemctl status flask_app
接下来就是去web看看结果了:

这样就是代表着已经成功接入后端了
接下来是我再学习配置时遇到的问题和解决方法
1. 进程管理问题:status=203/EXEC 报错
- 现象:手动运行 python app.py 成功,但执行 systemctl start flask_app 报错 203/EXEC。
- 原因:
- 路径错误:Systemd 找不到 gunicorn 的绝对路径。
- 权限不足:虽然文件属于你,但 Systemd 切换到 qqq 用户后,由于父目录没有 +x 权限,导致其无法“穿透”进入目录执行程序。
- 解决方法:
- 使用 which gunicorn 确定绝对路径并写入 .service 文件。
- 对父级目录执行 chmod +x,确保用户可以访问路径下的文件
2. 文件所有权(Ownership)混乱
- 现象:使用 sudo 操作后,原本属于 qqq 的文件变成了 root 所有,导致 qqq 运行的后端服务无法读取配置文件。
- 解决方法:使用 sudo chown -R qqq:qqq /var/www/todo 重新声明“主权”。
3. 目录架构问题:虚拟环境(venv)的“水土不服”
- 现象:将项目从 /data 搬到 /var/www 后,后端彻底无法启动。
- 原因:虚拟环境的路径依赖。venv 内部的脚本硬编码了创建时的绝对路径(/data/...),移动目录后,脚本找不到原来的 Python 解释器。
- 解决方法:彻底删除旧的 venv 文件夹,在新的 /var/www/todo 目录下执行 python3 -m venv venv 重新创建并重装依赖。
4. Nginx 配置逻辑冲突
- 现象 1 (502):服务显示绿色的 running,但网页报错 502。
- 原因:端口不匹配。后端配置了监听 5000 端口,但 Nginx 的 proxy_pass 写的是 3000。
- 解决:统两端的端口号(全部改回 3000)。
- 现象 2 (404):后端接口通了,但前端网页找不到了。
- 原因:项目搬家后,Nginx 配置文件里的 root 指令还指向旧的路径。
- 解决:修改 Nginx .conf 文件中的 root 路径并重启 Nginx:
打开 Nginx 配置:
sudo vi /etc/nginx/conf.d/todo.conf
找到 root 这一行,把路径改成新的路径:
root /var/www/todo/frontend
然后重启一下nginx:
sudo systemctl restart nginx
5. SELinux 安全策略拦截(最大挑战)
- 现象:在 setenforce 0(宽容模式)下一切正常,一旦 setenforce 1 就报错 502 或服务无法启动。
- 细分问题与对策:
- 拦截 Nginx 联网:SELinux 默认不准 Nginx 连接后端端口。
- 解决:执行 sudo setsebool -P httpd_can_network_connect 1。
- 拦截 Systemd 执行非标目录文件:SELinux 不信任 /data 下的二进制文件。
- 解决:将项目搬迁到标准 Web 目录 /var/www,并使用 restorecon 刷新文件安全上下文(标签)。
- 二进制文件标签不对:Gunicorn 被视为可疑程序。
- 解决:使用 chcon -t bin_t 或 semanage 永久标记其为合法的可执行程序。
- 拦截 Nginx 联网:SELinux 默认不准 Nginx 连接后端端口。
mysql数据库服务器部署
- 首先要为了防止冲突,先清理一下环境
sudo dnf remove mariadb mariadb-server -y
sudo dnf remove mysql mysql-server mysql-common mysql-libs -y
把系统默认的 MySQL 模块关了
sudo dnf module disable mysql -y
二.安装mysql数据库
导入mysql官方社区源
sudo dnf install https://dev.mysql.com/get/mysql80-community-release-el9-1.noarch.rpm -y
安装mysql服务器
sudo dnf install mysql-community-server -y
启动服务&设置自启
sudo systemctl start mysqld
sudo systemctl enable mysqld
三.配置sql密码:
寻找临时密码:
sudo grep 'temporary password' /var/log/mysqld.log
一般会有这样的信息
2025-12-18T09:00:00.123456Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: ;k2>f8#pX&z1
接下了我们拿这个临时密码进去修改密码
ALTER USER 'root'@'localhost' IDENTIFIED BY ' …… ' ;
四.创建数据库和用户
1. 创建业务数据库
CREATE DATABASE todo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
2.创建 Python 专用的用户(名字叫 qqq,密码设为 qqq)
——因为我把密码策略改了,所以这里密码可以设置的非常简单
CREATE USER 'qqq'@'127.0.0.1' IDENTIFIED BY 'qqq';
3. 给这个用户授权
GRANT ALL PRIVILEGES ON todo.* TO 'qqq'@'127.0.0.1';
4.接下来刷新权限就好了
Flush privileges ;
五.在数据库里插入点数据:
1. 切换到我们需要操作的数据库
USE todo;
2. 创建一张叫 tasks 的表
CREATE TABLE tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
content VARCHAR(255) NOT NULL,
status TINYINT(1) DEFAULT 0
);
-- id: 自动编号,content: 任务内容,status: 完成状态(0是未完成)
3. 往表里塞两条测试数据(上货)
INSERT INTO tasks (content) VALUES ('这是数据库里的第一条数据');
INSERT INTO tasks (content) VALUES ('这是数据库里的第二条数据');
4.验证一下有没有写进去:
SELECT * FROM tasks;
五.接下来编辑python文件
先进入虚拟环境
cd /var/www/todo/backend && source venv/bin/activate
安装一下依赖文件
pip install pymysql cryptography
接下来就是把之前后端的文件改一下
vi app.py
from flask import Flask, jsonify
import pymysql
app = Flask(__name__)
# 1. 配置数据库连接信息
db_config = {
'host': '127.0.0.1',
'user': 'qqq',
'password': 'qqq', # 刚才 SQL 语句里设的密码
'database': 'todo',
'charset': 'utf8mb4',
'cursorclass': pymysql.cursors.DictCursor
}
@app.route('/api/tasks')
def get_tasks():
try:
# 2. 建立连接
conn = pymysql.connect(**db_config)
with conn.cursor() as cursor:
# 3. 执行 SQL 查询
cursor.execute("SELECT * FROM tasks")
# 4. 获取所有结果
result = cursor.fetchall()
# 5. 关闭连接
conn.close()
# 6. 返回 JSON 数据
return jsonify({
"status": "success",
"data": result
})
except Exception as e:
# 如果连不上,把错误打印出来方便调试
print(f"数据库连接失败: {e}")
return jsonify({"status": "error", "message": str(e)}), 500
if __name__ == '__main__':
app.run()
改成这个注意标记的那些地方
接下来就是SE的授权
sudo setsebool -P httpd_can_network_connect_db 1
然后重启一下后端
sudo systemctl restart flask_app
就可以去浏览器看看了
接下来就是我在学习时遇到的问题和解决方案
1.Mysql密码不在日志里面,登陆不了
先把mysql服务停止了
sudo systemctl stop mysqld
然后去开启‘免密模式’
修改配置文件,告诉 MySQL 启动时不要查密码
sudo vi /etc/my.cnf
[mysqld] 这一行的下面写入
skip-grant-tables
进去重置密码
Mysql -u root (不需要密码了)
进来先刷新然后更改密码:
FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED BY ' …… ' ;
好了后还要把免密登陆关闭
Sudo vi/etc/my.cnf
把刚刚写入的那行 skip-grant-tables #注释掉或者直接删除这行
然后重启服务就好了
sudo systemctl restart mysqld
2.在配置后端文件时因为缩进问题导致flask启动失败和各种报错
解决方法:
vi自动缩进修复功能
快速输入 gg(跳转到文件开头)。
按下 等于号 键和 大写 G:=G
在粘贴之前,先开启 Paste 模式:
: set paste
这是前端优化后代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>我的全栈运维练习</title>
<style>
body { font-family: sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
h1 { color: #333; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; }
.status-box { background: #e8f5e9; padding: 15px; border-radius: 5px; color: #2e7d32; margin-bottom: 20px;}
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background-color: #f2f2f2; }
.tag { padding: 4px 8px; border-radius: 4px; font-size: 0.85em; }
.done { background: #d1fae5; color: #065f46; }
.todo { background: #fee2e2; color: #991b1b; }
</style>
</head>
<body>
<h1>📋 任务管理系统</h1>
<div id="status" class="status-box">正在连接后端 API...</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>任务内容</th>
<th>状态</th>
</tr>
</thead>
<tbody id="task-list">
<!-- 这里的 JavaScript 会自动填入数据 -->
</tbody>
</table>
<script>
// 1. 定义 API 地址
const apiUrl = '/api/tasks';
// 2. 获取数据
fetch(apiUrl)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP 错误! 状态码: ${response.status}`);
}
return response.json();
})
.then(res => {
const statusBox = document.getElementById('status');
const list = document.getElementById('task-list');
// 显示连接成功信息
statusBox.textContent = `✅ 后端连接成功!数据来源:${res.source || 'MySQL 数据库'}`;
// 3. 渲染列表
list.innerHTML = ''; // 清空旧数据
res.data.forEach(task => {
const row = document.createElement('tr');
const statusHtml = task.status
? '<span class="tag done">已完成</span>'
: '<span class="tag todo">待办</span>';
row.innerHTML = `
<td>${task.id}</td>
<td>${task.content}</td>
<td>${statusHtml}</td>
`;
list.appendChild(row);
});
})
.catch(error => {
const statusBox = document.getElementById('status');
statusBox.textContent = `❌ 连接失败: ${error.message}`;
statusBox.style.backgroundColor = '#fee2e2';
statusBox.style.color = '#991b1b';
});
</script>
</body>
</html>
更多推荐




所有评论(0)