Installation Issues

mod_wsgi is a small package but its build depends on both Apache and Python, and various install problems can arise as a result. Common causes include an incomplete Python installation, multiple Python versions on the host, or platform-specific build-toolchain quirks. This page lists known problems and workarounds.

If your problem is not covered here, also see Configuration Issues and Application Issues.

Missing Python Header Files

Compiling mod_wsgi from source requires Python’s development headers. On Linux distributions where Python is split into a runtime package and a separate developer package, the developer package is often not installed by default and the build will fail with errors like:

mod_wsgi.c:113:20: error: Python.h: No such file or directory
mod_wsgi.c:114:21: error: compile.h: No such file or directory
mod_wsgi.c:115:18: error: node.h: No such file or directory
mod_wsgi.c:116:20: error: osdefs.h: No such file or directory
mod_wsgi.c:119:2: error: #error Sorry, mod_wsgi requires at least Python 3.10.0.

Install the developer package for the Python you are using. The name varies by distribution but is typically the runtime package name with -dev appended (python3-dev on Debian/Ubuntu) or -devel (python3-devel on RHEL/Fedora).

Linker Relocation Errors Against The Python Static Library

When building mod_wsgi on a 64-bit Linux system against a Python installation that provides only a static library (libpython3.X.a), the link step can fail with an error of the following shape:

/usr/bin/ld: /path/to/libpython3.X.a(abstract.o): \
    relocation R_X86_64_32S against symbol `_Py_NoneStruct' \
    can not be used when making a shared object; \
    recompile with -fPIC
/usr/bin/ld: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status

The exact relocation type cited can be R_X86_64_32, R_X86_64_32S, or R_X86_64_PC32 depending on the linker version and which Python symbols are involved. The defining feature of the error is the trailing message “can not be used when making a shared object; recompile with -fPIC”.

The root cause is that the static Python library being linked into mod_wsgi.so was not compiled with position-independent code. The mod_wsgi module is itself a shared object, so every object linked into it must be position-independent. Object files inside libpython3.X.a were not, and the link fails.

Historically this error appeared when a 32-bit Python static library was being linked into a 64-bit mod_wsgi build, which is where the older form of this section’s title comes from. On modern systems the more common cause is simply that Python was built without --enable-shared: the resulting libpython.a is not position-independent and the same relocation error appears even on a fully 64-bit system.

The fix is to use a Python build that provides a shared library (libpython3.X.so) rather than only a static one. Most distribution Python packages, Homebrew Python, and python.org installers ship Python with --enable-shared and so already provide a shared library. If you are using a hand-built Python that does not, rebuild it with:

./configure --enable-shared
make
make install

See also the “Lack Of Python Shared Library” section below.

Lack Of Python Shared Library

A normally-built mod_wsgi.so is under 0.5MB. If your build comes out well over 1MB, the Python installation it was built against does not provide a shared library — only a static one — and libtool has embedded the static Python objects directly into mod_wsgi.so instead of linking against libpython3.X.so dynamically.

This has two practical consequences:

  • When Apache loads mod_wsgi.so, the dynamic linker performs address relocations on the embedded Python objects. Because relocations modify memory, the Python library becomes private to each Apache process rather than shared, adding 2-5MB per Apache child process on Linux.

  • Subsequent Python patch-level upgrades will not be picked up automatically. The static Python is baked into mod_wsgi.so and any changes in the upgraded Python library will not be reflected in the embedded copy. See “Python Patch Level Mismatch” below.

To check whether mod_wsgi.so is using a shared Python library, run ldd against it:

$ ldd mod_wsgi.so
 linux-vdso.so.1 =>  (0x00007fffeb3fe000)
 libpython3.12.so.1.0 => /usr/local/lib/libpython3.12.so.1.0 (0x00002adebf94d000)
 libpthread.so.0 => /lib/libpthread.so.0 (0x00002adebfcba000)
 libdl.so.2 => /lib/libdl.so.2 (0x00002adebfed6000)
 libutil.so.1 => /lib/libutil.so.1 (0x00002adec00da000)
 libc.so.6 => /lib/libc.so.6 (0x00002adec02dd000)
 libm.so.6 => /lib/libm.so.6 (0x00002adec0635000)
 /lib64/ld-linux-x86-64.so.2 (0x0000555555554000)

The presence of a libpython3.X.so line indicates a shared Python library is being used. If there is no such line, mod_wsgi is using the static library.

To get a shared library, build Python with --enable-shared. Most distribution Python packages, Homebrew Python, and python.org installers already do so.

Unable To Find Python Shared Library

When mod_wsgi is built against a Python that provides a shared library, that shared library must be in a directory the dynamic linker searches at runtime. If it is not, Apache will fail to load mod_wsgi.so with:

error while loading shared libraries: libpython3.12.so.1.0: \
 cannot open shared object file: No such file or directory

The simplest fix is to ensure the Python shared library is on the system-wide library search path. /lib and /usr/lib are typically always searched. /usr/local/lib is also searched on many systems, but only if it has been added to the loader’s configuration — on Linux this is typically /etc/ld.so.conf or a file under /etc/ld.so.conf.d/, with ldconfig rerun afterwards.

Alternatively, set LD_LIBRARY_PATH in Apache’s startup environment to include the directory containing the Python shared library. For an unmodified Apache distribution this goes in the envvars file in the same directory as the Apache executable. On RHEL-derived distributions the package may not ship an envvars file; use /etc/sysconfig/httpd instead.

A third option is to have Apache itself preload the Python shared library before loading mod_wsgi, using the LoadFile directive placed before the LoadModule wsgi_module line in the Apache configuration:

LoadFile /usr/local/lib/libpython3.12.so.1.0
LoadModule wsgi_module modules/mod_wsgi.so

By the time Apache reaches LoadModule wsgi_module, the Python shared library is already mapped into the Apache process and the linker resolves mod_wsgi’s references to it directly, without consulting the system library search path. This avoids needing root access to update ld.so.conf, avoids polluting the system loader’s configuration with a path that is only relevant to Apache, and makes the dependency explicit and local to the Apache configuration. mod_wsgi-express module-config already emits a LoadFile directive automatically on macOS and Windows; on Linux it is not emitted by default but can be added manually if needed.

The same LoadFile trick is also useful when the Python shared library is findable but the dynamic linker resolves to the wrong copy — for example on hosts that have multiple installations of the same Python major version, when rpath settings baked into a relocated Python installation no longer point at the right place, or when the Python shared library exists at an unusual location (such as inside a virtual environment) that is not on any system search path. Pointing LoadFile at the exact path of the Python shared library that mod_wsgi was built against forces that specific copy to be the one used.

Multiple Python Versions

If multiple Python installations are present on the host — for example a system Python plus a pyenv- or uv-managed Python, or a python.org installer alongside a Homebrew Python — and you need mod_wsgi to use a specific one, pass --with-python to configure when building:

./configure --with-python=/usr/local/bin/python3.12

This is enough when the chosen Python and any other Pythons on the host share the same installation prefix. If they do not — for example the chosen Python is at /usr/local while another Python is at /usr — Apache may fail to find the Python library files at startup.

The Python interpreter determines its installation prefix at startup by looking up its own executable on PATH and taking the parent of that location. The PATH Apache inherits at startup is typically only the system default, so if Python’s bin directory is not on Apache’s PATH the Python embedded in mod_wsgi will find /usr/bin/python3 rather than the intended /usr/local/bin/python3, and use /usr as its installation prefix instead of /usr/local.

If there is no Python installation under /usr matching the version mod_wsgi was built against, Apache’s error log will show:

'import site' failed; use -v for traceback

and any request to a WSGI application will fail because nothing beyond the built-in modules is importable.

If there is a Python installation under /usr of the same major and minor version (but not the one mod_wsgi was built against), startup may succeed but at runtime imports may resolve to a different installation than expected. Imports may fail entirely or — worse — silently pick up incompatible third-party modules.

To direct mod_wsgi to the right Python installation explicitly, use the WSGIPythonHome directive:

WSGIPythonHome /usr/local

The value should be a normalised path corresponding to the sys.prefix of the Python you built mod_wsgi against:

>>> import sys
>>> sys.prefix
'/usr/local'

A less-preferred alternative is to extend Apache’s PATH so the intended Python is found first. For a standard Apache installation, edit the envvars file in the same directory as the Apache executable:

PATH=/usr/local/bin:$PATH
export PATH

To verify which Python installation is actually being used at runtime, deploy a small WSGI script that prints sys.prefix and sys.path to stderr:

import sys

def application(environ, start_response):
    status = '200 OK'
    output = b'Hello World!'

    response_headers = [('Content-type', 'text/plain'),
                        ('Content-Length', str(len(output)))]
    start_response(status, response_headers)

    print('sys.prefix = %s' % repr(sys.prefix), file=sys.stderr)
    print('sys.path = %s' % repr(sys.path), file=sys.stderr)

    return [output]

Python Patch Level Mismatch

If the Python installation is upgraded to a newer patch-level revision without rebuilding mod_wsgi, you will likely see a warning of the following form (logged with the WSGI0099 error code; see Error Reference) in the Apache error log when Python is being initialised:

[Sat Jan 01 12:34:56.789012 2026] [wsgi:warn] [pid 12345] WSGI0099: Compiled for Python/3.12.0 but runtime using Python/3.12.1.

The warning indicates that a newer Python version is now being used than the one mod_wsgi was originally compiled against.

If both Pythons were installed with --enable-shared, this is generally harmless: the Python library is linked dynamically at runtime, so the upgrade is picked up automatically.

If --enable-shared was not used and the static Python library is embedded into mod_wsgi.so, the embedded library code will be older than the Python modules and extension modules now present in the Python installation. Behaviour in this case is undefined and you should rebuild mod_wsgi against the upgraded Python.

Anaconda Python Conflicting With System Shared Libraries

Anaconda Python ships its own copies of various third-party shared libraries inside the Anaconda installation — including SSL, image manipulation, cryptography, and others. When mod_wsgi is built against Anaconda Python and loaded into an Apache instance that also has another module loaded which links against the corresponding system shared library, the two modules can end up disagreeing about which copy of that library is in use within the same Apache process address space.

Two examples that have been seen in practice:

  • mod_ssl plus Anaconda Python’s ssl module. mod_ssl is built against the host’s system SSL libraries. Anaconda Python’s ssl module is built against Anaconda’s own SSL libraries. When both are loaded into the same Apache process — for example when an HTTPS-serving Apache also hosts a WSGI application that imports Python’s ssl module — this has been observed to cause crashes of the Apache worker process.

  • PHP plus Anaconda Python. mod_php (and other Apache modules) link against system libraries for image manipulation and similar. Loading both mod_php and an Anaconda-built mod_wsgi into the same Apache instance can result in disagreements over which copy of those libraries is in use.

The symptoms vary widely depending on which library the conflict is over. They range from runtime errors out of either Python or the conflicting module’s code through to outright crashes of the Apache worker process.

The conflict is fundamental: there is no way for mod_wsgi to mediate the disagreement between Anaconda’s shipped libraries and the system libraries the rest of Apache is using.

If you must use Anaconda Python for your WSGI application, do not load mod_wsgi into the same Apache instance as mod_ssl, mod_php, or other modules that overlap with Anaconda’s bundled native dependencies. Run the WSGI application in a separate Apache instance — for example via mod_wsgi-express on an unprivileged port behind a front-end Apache or nginx that terminates HTTPS — so the two sets of libraries are not loaded into the same process. Otherwise, use a system Python or a python.org installer for the mod_wsgi build instead of Anaconda.