Apache mod_wsgi 是一个让 Apache HTTP Server 直接运行 Python WSGI 应用的模块。传统上 Apache 处理 PHP 有 mod_php,而 Python 应用通常需要额外启动 Gunicorn、uWSGI 之类的进程,再用反向代理把请求转过去。mod_wsgi 的思路不同,它把 Python 解释器嵌入 httpd 子进程,请求到达后直接调用应用对象,省去了一层网络转发,也能复用 Apache 成熟的身份认证、访问控制和静态文件服务能力。这种部署方式特别适合已有 Apache 环境、需要把 Python 服务与传统虚拟主机混合管理的场景。

嵌入模式与守护进程模式的核心区别
mod_wsgi 提供两种运行模式,理解它们的差异是正确配置的前提。嵌入模式是最早的实现方式,WSGI 应用直接运行在各个 Apache 子进程中。一个 httpd 子进程可能同时承载多个应用,所有应用共享同一个 Python 解释器。如果不同应用依赖同一个库的不同版本,或者依赖 C 扩展模块,很容易发生符号冲突。而且嵌入模式下应用代码一旦被加载,只有当对应的 Apache 子进程重启时才会重新读取,修改了 Python 代码后往往需要重启整个 Apache。
守护进程模式解决了这些问题。通过 WSGIDaemonProcess 指令可以创建一个独立进程组,应用只在指定的守护进程里运行,与 Apache 子进程完全隔离。每个应用可以拥有自己的进程数、线程数、运行用户和 Python 环境。崩溃只会影响当前应用,不会拖垮整个 Web 服务器。代码更新时只需要向 WSGI 脚本文件重新触发一次加载,不必重启 Apache。生产环境几乎都建议使用守护进程模式。
下面是一个最小化的守护进程配置示例,展示了如何让 Apache 把某个路径的请求交给 Python 应用处理:
LoadModule wsgi_module modules/mod_wsgi.so
WSGIDaemonProcess myapp python-home=/var/www/myapp-venv python-path=/var/www/myapp processes=2 threads=15
WSGIProcessGroup myapp
WSGIScriptAlias / /var/www/myapp/myapp.wsgi
<Directory /var/www/myapp>
Require all granted
</Directory>
这里 WSGIDaemonProcess 定义了一个名为 myapp 的守护进程组,使用两个进程、每个进程十五个线程,并指定了虚拟环境路径。WSGIProcessGroup 告诉 Apache 后续请求应该交给哪个进程组。WSGIScriptAlias 则把站点根路径映射到具体的 WSGI 脚本文件。
从安装到可访问的完整配置流程
在 Debian 或 Ubuntu 系统上,安装过程比较直接。需要同时安装 Apache、mod_wsgi 模块以及 Python 虚拟环境工具。对于使用 Python 3 的环境,通常安装 libapache2-mod-wsgi-py3。安装完成后模块并不会自动启用,需要确认 Apache 配置中已经加载了 wsgi_module,或者对应的 .load 文件存在。
sudo apt update sudo apt install apache2 libapache2-mod-wsgi-py3 python3-venv sudo a2enmod wsgi sudo systemctl restart apache2
接下来准备 Python 应用。推荐使用虚拟环境隔离依赖,否则多个应用共享系统 Python 会导致包版本混乱。创建虚拟环境并安装 Flask 或 Django 等框架后,需要写一个 WSGI 入口文件。这个文件的作用是暴露一个名为 application 的可调用对象,mod_wsgi 会调用它处理每个请求。
import sys # 使用 python-home 指令指定虚拟环境时,无需手动激活 sys.path.insert(0, '/var/www/myapp') from myapp import app as application
如果应用包位于 /var/www/myapp 目录下,sys.path.insert 可以让 Python 找到包。对于 Flask 项目,假设 myapp 包里的 __init__.py 创建了 app 对象,上述入口文件就能直接使用。要注意 WSGI 脚本文件的权限,Apache 子进程或守护进程的运行用户必须能够读取该文件及其所在目录,否则会返回 403 Forbidden。
Apache 配置文件可以放在独立的 .conf 文件中,然后在主配置里 Include。常见做法是在 /etc/apache2/sites-available/ 下创建站点配置,再用 a2ensite 启用。配置中必须包含 WSGIDaemonProcess、WSGIProcessGroup、WSGIScriptAlias 三组核心指令。如果应用需要处理 URL 子路径,例如 /blog,可以把 WSGIScriptAlias 的第一个参数改成 /blog,但要注意应用内部路由需要知道挂载点,否则生成的重定向地址会出错。
虚拟环境与依赖隔离的细节
mod_wsgi 对虚拟环境的支持经历过多次变化。早期版本需要在 WSGI 脚本里手动执行 activate_this.py 来激活虚拟环境,但这种方式在 Python 3 和较新版本的 mod_wsgi 中已经不再推荐。现在更可靠的做法是在 WSGIDaemonProcess 指令中使用 python-home 参数,直接指定虚拟环境根目录。这样守护进程启动时就会把 Python 解释器切换到虚拟环境,后续 import 的包都来自该环境。
WSGIDaemonProcess myapp python-home=/var/www/myapp-venv python-path=/var/www/myapp processes=2 threads=15
python-home 的值必须是虚拟环境的根目录,而不是里面的 bin 目录。如果写错路径,Apache 日志会出现 ImportError 或模块解析失败。python-path 用于追加额外的 Python 搜索路径,适合应用代码并不在虚拟环境 site-packages 中的情况。两者可以同时使用,但作用不同。python-home 切换解释器和标准库,python-path 只是向 sys.path 插入目录。
权限问题是使用虚拟环境时最常见的坑。Apache 默认以 www-data 用户运行,而 python3 -m venv 创建的目录往往属于创建它的普通用户,权限可能是 700 或 750。需要确保 www-data 对虚拟环境目录及其内部所有文件拥有读取和执行权限。可以用 chown 或 chmod 放开权限,但更安全的做法是把整个应用目录和虚拟环境目录都设置为 www-data 可读,并限制写权限。
进程线程调优与常见错误排查
守护进程模式下的性能主要由 processes 和 threads 两个参数决定。进程数决定并发处理能力,线程数决定每个进程内部能同时处理多少请求。如果应用主要是 I/O 密集型的数据库查询或外部 API 调用,适当增加 threads 能提高吞吐量。如果是 CPU 密集型计算,由于 CPython 的 GIL 限制,盲目增加线程没有意义,应该增加 processes。一个常用的起点是每核一个进程、每个进程八到十五个线程,然后根据压测结果调整。
还可以通过 max-requests、inactivity-timeout、deadlock-timeout 等参数增强稳定性。max-requests 设置每个守护进程处理多少个请求后自动重启,可以缓解内存泄漏。inactivity-timeout 指定进程空闲多久后退出,deadlock-timeout 用于检测应用是否发生死锁并自动重启。这些参数都写在 WSGIDaemonProcess 指令中,可以用空格分隔多个选项。
WSGIDaemonProcess myapp python-home=/var/www/myapp-venv python-path=/var/www/myapp processes=4 threads=8 max-requests=1000 inactivity-timeout=600 deadlock-timeout=60
排查问题时优先查看 Apache 错误日志,路径通常是 /var/log/apache2/error.log。出现 403 错误大多与文件或目录权限有关,确认 Apache 运行用户能否进入 WSGI 脚本所在目录。出现 504 Gateway Timeout 往往意味着应用阻塞超时,需要检查数据库连接、外部依赖或死锁。还有一类非常隐蔽的问题是 mod_wsgi 与 Python 版本不匹配,比如 mod_wsgi 编译时链接的是 Python 3.8,但虚拟环境实际使用 Python 3.10,启动时可能报 undefined symbol 之类的错误。此时需要重新安装与当前 Python 版本匹配的 mod_wsgi 包,或者重新编译模块。

