Fix "Target WSGI Script Cannot Be Loaded as Python Module" in Apache
Fix the Apache mod_wsgi error Target WSGI script cannot be loaded as Python module for Django: match mod_wsgi to your Python version, set python-home and python-path, fix permissions on the project and virtualenv, and read the real exception below it.
Problem statement
The site shows Apache's 500 Internal Server Error page, and the Apache error log contains:
[wsgi:error] mod_wsgi (pid=1234): Failed to exec Python script file '/srv/app/config/wsgi.py'.
[wsgi:error] mod_wsgi (pid=1234): Exception occurred processing WSGI script '/srv/app/config/wsgi.py'.
[wsgi:error] Target WSGI script '/srv/app/config/wsgi.py' cannot be loaded as Python module.
[wsgi:error] Traceback (most recent call last):
[wsgi:error] ...
[wsgi:error] ModuleNotFoundError: No module named 'django'
mod_wsgi tried to import your wsgi.py and an exception was raised. The "cannot be loaded" line is a wrapper; the real cause is the traceback that follows it. Most often mod_wsgi runs a different Python than your virtualenv, cannot find your project on sys.path, or cannot read the files.
- Read the last line of the traceback in the Apache error log; that is the actual error.
No module named 'django': setpython-hometo your virtualenv inWSGIDaemonProcess.No module named 'config': setpython-pathto the project root.- Version mismatch: mod_wsgi must be compiled for the same Python as the virtualenv; install it with
pip install mod_wsgiinto the venv. Permission denied: the Apache user needs execute permission on every parent directory and read on the files.
Step-by-step solution
1. Read the full traceback
sudo tail -n 60 /var/log/apache2/error.log # Debian/Ubuntu
sudo tail -n 60 /var/log/httpd/error_log # RHEL family
Note the final exception. The fixes below map to the common ones.
2. Point mod_wsgi at the virtualenv and project
Use daemon mode with explicit paths:
<VirtualHost *:443>
ServerName example.com
WSGIDaemonProcess example python-home=/srv/app/.venv python-path=/srv/app \
user=deploy group=www-data processes=2 threads=15
WSGIProcessGroup example
WSGIApplicationGroup %{GLOBAL}
WSGIScriptAlias / /srv/app/config/wsgi.py
<Directory /srv/app/config>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
</VirtualHost>
python-home is the virtualenv root (the directory containing bin/ and lib/), not bin/python. python-path adds the project root so import config works. WSGIApplicationGroup %{GLOBAL} avoids problems with C extensions such as psycopg and NumPy that do not work in sub-interpreters.
3. Match mod_wsgi and Python versions
The distribution package libapache2-mod-wsgi-py3 is compiled against the system Python, for example 3.12 on Ubuntu 24.04. A virtualenv made with Python 3.13 will not work with it. Check:
apache2ctl -M | grep wsgi
ls /srv/app/.venv/lib/
To use a different Python, install mod_wsgi into the virtualenv and load that module instead of the system one:
/srv/app/.venv/bin/pip install mod_wsgi
/srv/app/.venv/bin/mod_wsgi-express module-config
Put the printed LoadModule and WSGIPythonHome lines in an Apache config file and disable the distribution module (sudo a2dismod wsgi).
4. Fix permissions
Apache's daemon process runs as the user= you set or as www-data. It needs x on every directory from / down to the project, and r on the files:
namei -l /srv/app/config/wsgi.py
sudo chmod o+x /srv /srv/app
Projects under /home/youruser/ often fail because home directories are 0750. Moving the project to /srv avoids it.
5. Check wsgi.py itself
Errors in settings (missing environment variables, syntax errors) surface here too. Test the import as the Apache user with the same environment:
sudo -u deploy /srv/app/.venv/bin/python -c "import sys; sys.path.insert(0, '/srv/app'); import config.wsgi"
Environment variables from your shell are not visible to Apache, and SetEnv values do not reach os.environ under mod_wsgi either. Load them from a file instead, for example with django-environ reading /srv/app/.env at the top of your settings.
Explanation
mod_wsgi embeds a Python interpreter inside Apache. Which Python, and which version, is fixed when mod_wsgi is compiled. In daemon mode, it starts separate processes that import your wsgi.py using that interpreter, with the virtualenv and paths you configure. Any exception during import is logged with the "cannot be loaded as Python module" wrapper, followed by the original traceback. Because the interpreter and environment are Apache's, not your shell's, imports that work in a terminal can fail there.
Edge cases / notes
- SELinux. On RHEL and Rocky Linux, files need the
httpd_sys_content_tcontext and the venv may needhttpd_sys_script_exec_t; checkausearch -m avc. - Multiple sites. Use a separate
WSGIDaemonProcessname per site so their Python environments never mix. - Reloads. Touching
wsgi.pyreloads a daemon-mode application without restarting Apache.
Next steps
- Review the full setup with Deploy Django With Apache and mod_wsgi.
- Prefer a self-contained approach? See Deploy Django With mod_wsgi-express.
- API tokens missing behind Apache? Read Fix Django Authorization Header Missing.
FAQ
What does "cannot be loaded as Python module" mean?
mod_wsgi tried to import your WSGI file and an exception occurred. The traceback below it shows the real error.
Why does it work with runserver but not Apache?
Apache uses its own Python interpreter, user, and environment, which can differ from your shell's virtualenv and variables.
Should python-home point to bin/python?
No. It should point to the virtualenv directory itself, such as /srv/app/.venv.
Can I use a newer Python than the system one?
Yes, by installing mod_wsgi with pip into a virtualenv of that Python and loading that module in Apache.